Merge pull request #536 from yusufm/codex/export-diagnostics
Improve export failure diagnostics
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user