Merge pull request #505 from marcgabe15/fix/decodeEarlyBug
Fix/decode early bug
This commit is contained in:
@@ -1404,6 +1404,12 @@ export default function VideoEditor() {
|
||||
const timestamp = Date.now();
|
||||
const fileName = `export-${timestamp}.gif`;
|
||||
|
||||
if (result.warnings) {
|
||||
for (const warning of result.warnings) {
|
||||
toast.warning(warning);
|
||||
}
|
||||
}
|
||||
|
||||
const saveResult = await window.electronAPI.saveExportedVideo(arrayBuffer, fileName);
|
||||
|
||||
if (saveResult.canceled) {
|
||||
@@ -1538,6 +1544,12 @@ export default function VideoEditor() {
|
||||
const timestamp = Date.now();
|
||||
const fileName = `export-${timestamp}.mp4`;
|
||||
|
||||
if (result.warnings) {
|
||||
for (const warning of result.warnings) {
|
||||
toast.warning(warning);
|
||||
}
|
||||
}
|
||||
|
||||
const saveResult = await window.electronAPI.saveExportedVideo(arrayBuffer, fileName);
|
||||
|
||||
if (saveResult.canceled) {
|
||||
|
||||
@@ -118,6 +118,9 @@ export class GifExporter {
|
||||
async export(): Promise<ExportResult> {
|
||||
let webcamFrameQueue: AsyncVideoFrameQueue | null = null;
|
||||
|
||||
const warnings: string[] = [];
|
||||
const onWarning = (message: string) => warnings.push(message);
|
||||
|
||||
try {
|
||||
const platform = await getPlatform();
|
||||
|
||||
@@ -220,6 +223,7 @@ export class GifExporter {
|
||||
}
|
||||
queue.enqueue(webcamFrame);
|
||||
},
|
||||
onWarning,
|
||||
)
|
||||
.catch((error) => {
|
||||
webcamDecodeError = error instanceof Error ? error : new Error(String(error));
|
||||
@@ -279,6 +283,7 @@ export class GifExporter {
|
||||
webcamFrame?.close();
|
||||
}
|
||||
},
|
||||
onWarning,
|
||||
);
|
||||
|
||||
if (this.cancelled) {
|
||||
@@ -325,7 +330,7 @@ export class GifExporter {
|
||||
this.gif!.render();
|
||||
});
|
||||
|
||||
return { success: true, blob };
|
||||
return { success: true, blob, warnings: warnings.length > 0 ? warnings : undefined };
|
||||
} catch (error) {
|
||||
if (error instanceof BackgroundLoadError) {
|
||||
throw error;
|
||||
|
||||
@@ -68,7 +68,7 @@ type EarlyDecodeEndCheck = {
|
||||
};
|
||||
|
||||
const EARLY_DECODE_END_THRESHOLD_SEC = 1;
|
||||
const METADATA_TAIL_TOLERANCE_SEC = 1.5;
|
||||
const METADATA_TAIL_TOLERANCE_SEC = 2;
|
||||
const STREAM_DURATION_MATCH_TOLERANCE_SEC = 0.25;
|
||||
const DURATION_DIVERGENCE_THRESHOLD_SEC = 1.5;
|
||||
// Fallback upper bound for the packet scan when no reliable duration hint is
|
||||
@@ -129,11 +129,8 @@ export function shouldFailDecodeEndedEarly({
|
||||
const decodedNearStreamEnd =
|
||||
Math.abs(lastDecodedFrameSec - streamDurationSec) <= STREAM_DURATION_MATCH_TOLERANCE_SEC;
|
||||
|
||||
if (
|
||||
decodedNearStreamEnd &&
|
||||
metadataTailSec > 0 &&
|
||||
metadataTailSec <= METADATA_TAIL_TOLERANCE_SEC
|
||||
) {
|
||||
const maxTailSec = Math.max(METADATA_TAIL_TOLERANCE_SEC, requiredEndSec * 0.01);
|
||||
if (decodedNearStreamEnd && metadataTailSec > 0 && metadataTailSec <= maxTailSec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -295,6 +292,7 @@ export class StreamingVideoDecoder {
|
||||
trimRegions: TrimRegion[] | undefined,
|
||||
speedRegions: SpeedRegion[] | undefined,
|
||||
onFrame: OnFrameCallback,
|
||||
onWarning?: (message: string) => void,
|
||||
): Promise<void> {
|
||||
if (!this.demuxer || !this.metadata) {
|
||||
throw new Error("Must call loadMetadata() before decodeAll()");
|
||||
@@ -606,8 +604,6 @@ export class StreamingVideoDecoder {
|
||||
}
|
||||
this.decoder = null;
|
||||
|
||||
const isWindows = typeof navigator !== "undefined" && /Windows/.test(navigator.userAgent);
|
||||
|
||||
if (
|
||||
shouldFailDecodeEndedEarly({
|
||||
cancelled: this.cancelled,
|
||||
@@ -618,22 +614,9 @@ export class StreamingVideoDecoder {
|
||||
) {
|
||||
const decodedAtLabel =
|
||||
lastDecodedFrameSec === null ? "no decoded frame" : `${lastDecodedFrameSec.toFixed(3)}s`;
|
||||
const decodeGapSec =
|
||||
lastDecodedFrameSec === null ? Infinity : requiredEndSec - lastDecodedFrameSec;
|
||||
|
||||
// On Windows, tolerate a small decode gap: up to 10% of required duration, capped at 3 seconds.
|
||||
const maxToleratedGap = Math.min(3.0, requiredEndSec * 0.1);
|
||||
|
||||
if (isWindows && lastDecodedFrameSec !== null && decodeGapSec <= maxToleratedGap) {
|
||||
console.warn(
|
||||
`[StreamingVideoDecoder] Decode ended early on Windows with a gap of ${decodeGapSec.toFixed(2)}s ` +
|
||||
`(max tolerated: ${maxToleratedGap.toFixed(2)}s) – proceeding anyway.`,
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Video decode ended early at ${decodedAtLabel} (needed ${requiredEndSec.toFixed(3)}s).`,
|
||||
);
|
||||
}
|
||||
const message = `Decode ended early at ${decodedAtLabel} (needed ${requiredEndSec.toFixed(3)}s) – export may be slightly shorter than expected.`;
|
||||
console.warn(`[StreamingVideoDecoder] ${message}`);
|
||||
onWarning?.(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface ExportResult {
|
||||
success: boolean;
|
||||
blob?: Blob;
|
||||
error?: string;
|
||||
warnings?: string[];
|
||||
}
|
||||
|
||||
export interface VideoFrameData {
|
||||
|
||||
@@ -112,6 +112,8 @@ export class VideoExporter {
|
||||
let webcamDecodeError: Error | null = null;
|
||||
let webcamDecodePromise: Promise<void> | null = null;
|
||||
let webcamDecoder: StreamingVideoDecoder | null = null;
|
||||
const warnings: string[] = [];
|
||||
const onWarning = (message: string) => warnings.push(message);
|
||||
|
||||
this.cleanup();
|
||||
this.cancelled = false;
|
||||
@@ -199,6 +201,7 @@ export class VideoExporter {
|
||||
}
|
||||
queue.enqueue(webcamFrame);
|
||||
},
|
||||
onWarning,
|
||||
)
|
||||
.catch((error) => {
|
||||
webcamDecodeError = error instanceof Error ? error : new Error(String(error));
|
||||
@@ -303,6 +306,7 @@ export class VideoExporter {
|
||||
webcamFrame?.close();
|
||||
}
|
||||
},
|
||||
onWarning,
|
||||
);
|
||||
|
||||
if (this.cancelled) {
|
||||
@@ -359,7 +363,7 @@ export class VideoExporter {
|
||||
}
|
||||
|
||||
const blob = await muxer.finalize();
|
||||
return { success: true, blob };
|
||||
return { success: true, blob, warnings: warnings.length > 0 ? warnings : undefined };
|
||||
} finally {
|
||||
stopWebcamDecode = true;
|
||||
webcamFrameQueue?.destroy();
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user