diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 574ecd4a0..00a5f28e1 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -383,6 +383,10 @@ export default function VideoEditor() { const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); + const [sourceVideoDimensions, setSourceVideoDimensions] = useState<{ + width: number; + height: number; + } | null>(null); const [wallpaper, setWallpaper] = useState(initialEditorPreferences.wallpaper); const [shadowIntensity, setShadowIntensity] = useState( initialEditorPreferences.shadowIntensity, @@ -635,6 +639,10 @@ export default function VideoEditor() { } const [timelineCollapsed, setTimelineCollapsed] = useState(false); + const effectiveSourceVideoDimensions = sourceVideoDimensions ?? { + width: 1920, + height: 1080, + }; useEffect(() => { void window.electronAPI?.getPlatform?.()?.then((platform) => { @@ -1243,6 +1251,25 @@ export default function VideoEditor() { setPreviewVersion((version) => version + 1); }, []); + useEffect(() => { + setSourceVideoDimensions(null); + }, [videoPath]); + + const handleVideoMetadataChange = useCallback( + (metadata: { width: number; height: number }) => { + const width = Math.floor(metadata.width); + const height = Math.floor(metadata.height); + if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { + return; + } + + setSourceVideoDimensions((current) => + current?.width === width && current.height === height ? current : { width, height }, + ); + }, + [], + ); + const clearPendingProjectAutosave = useCallback(() => { if (projectAutosaveTimeoutRef.current !== null) { window.clearTimeout(projectAutosaveTimeoutRef.current); @@ -1363,22 +1390,30 @@ export default function VideoEditor() { const gifOutputDimensions = useMemo( () => calculateOutputDimensions( - videoPlaybackRef.current?.video?.videoWidth || 1920, - videoPlaybackRef.current?.video?.videoHeight || 1080, + effectiveSourceVideoDimensions.width, + effectiveSourceVideoDimensions.height, gifSizePreset, GIF_SIZE_PRESETS, ), - [gifSizePreset], + [ + effectiveSourceVideoDimensions.height, + effectiveSourceVideoDimensions.width, + gifSizePreset, + ], ); const desiredMp4SourceDimensions = useMemo( () => calculateMp4SourceDimensions( - videoPlaybackRef.current?.video?.videoWidth || 1920, - videoPlaybackRef.current?.video?.videoHeight || 1080, + effectiveSourceVideoDimensions.width, + effectiveSourceVideoDimensions.height, aspectRatio, ), - [aspectRatio], + [ + aspectRatio, + effectiveSourceVideoDimensions.height, + effectiveSourceVideoDimensions.width, + ], ); const mp4OutputDimensions = useMemo(() => { @@ -4763,8 +4798,8 @@ export default function VideoEditor() { return; } - const sourceWidth = video.videoWidth || 1920; - const sourceHeight = video.videoHeight || 1080; + const sourceWidth = sourceVideoDimensions?.width || video.videoWidth || 1920; + const sourceHeight = sourceVideoDimensions?.height || video.videoHeight || 1080; const settings = resolveExportStartSettings({ sourceWidth, sourceHeight, @@ -4794,6 +4829,8 @@ export default function VideoEditor() { gifSizePreset, exportBackendPreference, exportPipelineModel, + sourceVideoDimensions?.height, + sourceVideoDimensions?.width, handleExport, ]); @@ -5853,15 +5890,13 @@ export default function VideoEditor() { aspectRatio: getAspectRatioValue( aspectRatio, (() => { - const previewVideo = - videoPlaybackRef.current?.video; if ( - previewVideo && - previewVideo.videoHeight > 0 + sourceVideoDimensions && + sourceVideoDimensions.height > 0 ) { return ( - previewVideo.videoWidth / - previewVideo.videoHeight + sourceVideoDimensions.width / + sourceVideoDimensions.height ); } return 16 / 9; @@ -5878,6 +5913,7 @@ export default function VideoEditor() { ref={videoPlaybackRef} videoPath={videoPath || ""} onDurationChange={setDuration} + onVideoMetadataChange={handleVideoMetadataChange} onPreviewReadyChange={setIsPreviewReady} onTimeUpdate={setCurrentTime} currentTime={currentTime} diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index c64e6b425..91a670afe 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -311,6 +311,11 @@ function getEffectiveNativeAspectRatio( interface VideoPlaybackProps { videoPath: string; onDurationChange: (duration: number) => void; + onVideoMetadataChange?: (metadata: { + width: number; + height: number; + duration: number; + }) => void; onPreviewReadyChange?: (ready: boolean) => void; onTimeUpdate: (time: number) => void; currentTime: number; @@ -389,6 +394,7 @@ const VideoPlayback = forwardRef( { videoPath, onDurationChange, + onVideoMetadataChange, onPreviewReadyChange, onTimeUpdate, currentTime, @@ -2539,6 +2545,11 @@ const VideoPlayback = forwardRef( const handleLoadedMetadata = (e: React.SyntheticEvent) => { const video = e.currentTarget; onDurationChange(video.duration); + onVideoMetadataChange?.({ + width: video.videoWidth, + height: video.videoHeight, + duration: video.duration, + }); // Push video info to extension host for query APIs extensionHost.setVideoInfo({