diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 7bc75733..113cff3d 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -99,6 +99,7 @@ const PhSettings = (props: { className?: string; weight?: "fill" | "regular" }) import { extensionHost } from "@/lib/extensions"; import { resolveAutoCaptionSourcePath } from "./autoCaptionSource"; import { CropControl } from "./CropControl"; +import { resolveExportSourcePath } from "./exportSourcePath"; import { ExportSettingsMenu } from "./ExportSettingsMenu"; import ExtensionManager from "./ExtensionManager"; import { loadEditorPreferences, saveEditorPreferences } from "./editorPreferences"; @@ -3657,7 +3658,12 @@ export default function VideoEditor() { const handleExport = useCallback( async (settings: ExportSettings) => { - if (!videoPath) { + const exportVideoSource = resolveExportSourcePath({ + videoSourcePath, + videoPath, + }); + + if (!exportVideoSource) { toast.error("No video loaded"); return; } @@ -3732,7 +3738,7 @@ export default function VideoEditor() { if (settings.format === "gif" && settings.gifConfig) { // GIF Export const gifExporter = new GifExporter({ - videoUrl: videoPath, + videoUrl: exportVideoSource, width: settings.gifConfig.width, height: settings.gifConfig.height, frameRate: settings.gifConfig.frameRate, @@ -3896,7 +3902,7 @@ export default function VideoEditor() { ); const exporterConfig = { - videoUrl: videoPath, + videoUrl: exportVideoSource, width: exportWidth, height: exportHeight, frameRate: selectedMp4FrameRate, @@ -4118,6 +4124,7 @@ export default function VideoEditor() { [ clearPendingExportSave, videoPath, + videoSourcePath, wallpaper, trimRegions, shadowIntensity, diff --git a/src/components/video-editor/exportSourcePath.test.ts b/src/components/video-editor/exportSourcePath.test.ts new file mode 100644 index 00000000..a6477901 --- /dev/null +++ b/src/components/video-editor/exportSourcePath.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; + +import { resolveExportSourcePath } from "./exportSourcePath"; + +describe("resolveExportSourcePath", () => { + it("prefers the loaded local source path over a preview media-server URL", () => { + expect( + resolveExportSourcePath({ + videoSourcePath: "C:\\Users\\Admin\\Videos\\recording.mp4", + videoPath: + "http://127.0.0.1:42637/video?path=C%3A%5CUsers%5CAdmin%5CVideos%5Crecording.mp4", + }), + ).toBe("C:\\Users\\Admin\\Videos\\recording.mp4"); + }); + + it("falls back to a decoded local file path when only a file URL is available", () => { + expect( + resolveExportSourcePath({ + videoPath: "file:///Users/test/Videos/capture.mp4", + }), + ).toBe("/Users/test/Videos/capture.mp4"); + }); + + it("leaves a non-file URL untouched when no local source path is known", () => { + expect( + resolveExportSourcePath({ + videoPath: "http://127.0.0.1:42637/video?path=%2Ftmp%2Fcapture.mp4", + }), + ).toBe("http://127.0.0.1:42637/video?path=%2Ftmp%2Fcapture.mp4"); + }); + + it("returns null when no export source is available", () => { + expect(resolveExportSourcePath({})).toBeNull(); + }); +}); diff --git a/src/components/video-editor/exportSourcePath.ts b/src/components/video-editor/exportSourcePath.ts new file mode 100644 index 00000000..0e67c092 --- /dev/null +++ b/src/components/video-editor/exportSourcePath.ts @@ -0,0 +1,22 @@ +import { fromFileUrl } from "./projectPersistence"; + +type ExportSourceOptions = { + videoSourcePath?: string | null; + videoPath?: string | null; +}; + +/** + * Prefer the real local source path for export so the exporter can read the file + * directly instead of going back through the preview media-server URL. + */ +export function resolveExportSourcePath(options: ExportSourceOptions): string | null { + if (options.videoSourcePath) { + return options.videoSourcePath; + } + + if (!options.videoPath) { + return null; + } + + return fromFileUrl(options.videoPath); +}