Merge pull request #536 from yusufm/codex/export-diagnostics

Improve export failure diagnostics
This commit is contained in:
Sid
2026-05-10 12:03:24 -07:00
committed by GitHub
2 changed files with 104 additions and 13 deletions
+3 -1
View File
@@ -161,7 +161,9 @@ export function ExportDialog({
<div className="p-1 bg-red-500/20 rounded-full">
<X className="w-3 h-3 text-red-400" />
</div>
<p className="text-sm text-red-400 leading-relaxed">{error}</p>
<p className="whitespace-pre-wrap break-words text-sm text-red-400 leading-relaxed">
{error}
</p>
</div>
</div>
)}
+101 -12
View File
@@ -84,6 +84,53 @@ import {
import { UnsavedChangesDialog } from "./UnsavedChangesDialog";
import VideoPlayback, { VideoPlaybackRef } from "./VideoPlayback";
interface ExportDiagnostics {
formatLabel: "GIF" | "Video";
reason?: string;
sourcePath?: string | null;
width?: number;
height?: number;
frameRate?: number;
codec?: string;
bitrate?: number;
}
function getFileNameForDiagnostics(filePath?: string | null) {
if (!filePath) return "unknown";
try {
const url = new URL(filePath);
if (url.protocol === "file:") {
return decodeURIComponent(url.pathname).split(/[\\/]/).pop() || filePath;
}
} catch {
// Treat non-URL values as filesystem paths.
}
return filePath.split(/[\\/]/).pop() || filePath;
}
function buildExportDiagnosticMessage(diagnostics: ExportDiagnostics) {
const details = [
diagnostics.reason ? `Reason: ${diagnostics.reason}` : null,
`Source: ${getFileNameForDiagnostics(diagnostics.sourcePath)}`,
diagnostics.width && diagnostics.height
? `Output: ${diagnostics.width}x${diagnostics.height}${
diagnostics.frameRate ? ` @ ${diagnostics.frameRate} fps` : ""
}`
: null,
diagnostics.codec ? `Codec: ${diagnostics.codec}` : null,
diagnostics.bitrate ? `Bitrate: ${Math.round(diagnostics.bitrate / 1_000_000)} Mbps` : null,
`VideoEncoder: ${"VideoEncoder" in window ? "available" : "unavailable"}`,
].filter(Boolean);
return `${diagnostics.formatLabel} export failed\n${details.join("\n")}`;
}
function buildSaveDiagnosticMessage(formatLabel: "GIF" | "Video", reason?: string) {
return `${formatLabel} export save failed${reason ? `\nReason: ${reason}` : ""}`;
}
export default function VideoEditor() {
const {
state: editorState,
@@ -1411,11 +1458,21 @@ export default function VideoEditor() {
setUnsavedExport(null);
handleExportSaved(unsavedExport.format === "gif" ? "GIF" : "Video", saveResult.path);
} else {
toast.error(saveResult.message || "Failed to save export");
toast.error(
buildSaveDiagnosticMessage(
unsavedExport.format === "gif" ? "GIF" : "Video",
saveResult.message || "Failed to save export",
),
);
}
} catch (error) {
console.error("Error saving unsaved export:", error);
toast.error("Failed to save exported video");
toast.error(
buildSaveDiagnosticMessage(
unsavedExport.format === "gif" ? "GIF" : "Video",
error instanceof Error ? error.message : "Failed to save exported video",
),
);
}
}, [unsavedExport, handleExportSaved]);
@@ -1527,12 +1584,24 @@ export default function VideoEditor() {
handleExportSaved("GIF", saveResult.path);
} else {
setUnsavedExport({ arrayBuffer, fileName: targetFileName, format: "gif" });
setExportError(saveResult.message || "Failed to save GIF");
toast.error(saveResult.message || "Failed to save GIF");
const message = buildSaveDiagnosticMessage(
"GIF",
saveResult.message || "Failed to save GIF",
);
setExportError(message);
toast.error(message);
}
} else {
setExportError(result.error || "GIF export failed");
toast.error(result.error || "GIF export failed");
const message = buildExportDiagnosticMessage({
formatLabel: "GIF",
reason: result.error || "GIF export failed",
sourcePath: videoSourcePath ?? videoPath,
width: settings.gifConfig.width,
height: settings.gifConfig.height,
frameRate: settings.gifConfig.frameRate,
});
setExportError(message);
toast.error(message);
}
} else {
// MP4 Export
@@ -1668,12 +1737,26 @@ export default function VideoEditor() {
handleExportSaved("Video", saveResult.path);
} else {
setUnsavedExport({ arrayBuffer, fileName: targetFileName, format: "mp4" });
setExportError(saveResult.message || "Failed to save video");
toast.error(saveResult.message || "Failed to save video");
const message = buildSaveDiagnosticMessage(
"Video",
saveResult.message || "Failed to save video",
);
setExportError(message);
toast.error(message);
}
} else {
setExportError(result.error || "Export failed");
toast.error(result.error || "Export failed");
const message = buildExportDiagnosticMessage({
formatLabel: "Video",
reason: result.error || "Export failed",
sourcePath: videoSourcePath ?? videoPath,
width: exportWidth,
height: exportHeight,
frameRate: 60,
codec: "avc1.640033",
bitrate,
});
setExportError(message);
toast.error(message);
}
}
@@ -1688,8 +1771,13 @@ export default function VideoEditor() {
toast.error(message);
} else {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
setExportError(errorMessage);
toast.error(t("errors.exportFailedWithError", { error: errorMessage }));
const message = buildExportDiagnosticMessage({
formatLabel: settings.format === "gif" ? "GIF" : "Video",
reason: errorMessage,
sourcePath: videoSourcePath ?? videoPath,
});
setExportError(message);
toast.error(t("errors.exportFailedWithError", { error: message }));
}
} finally {
setIsExporting(false);
@@ -1702,6 +1790,7 @@ export default function VideoEditor() {
},
[
videoPath,
videoSourcePath,
webcamVideoPath,
wallpaper,
zoomRegions,