diff --git a/src/hooks/useVideoEditor.ts b/src/hooks/useVideoEditor.ts index a86c35f2..f114be1e 100644 --- a/src/hooks/useVideoEditor.ts +++ b/src/hooks/useVideoEditor.ts @@ -9,7 +9,7 @@ import { suggestPreset } from "@/lib/presetSuggestion"; import { validateDimensions, getDownscaledDimensions } from "@/utils/video-validation"; const DEFAULT_TITLE = "Reframe — Resize, trim, and export videos in your browser"; - const STORAGE_KEY = "reframe:recipe"; +const STORAGE_KEY = "reframe:recipe"; export function extractMetadata(file: File): Promise<{ width: number; height: number; duration: number }> { return new Promise((resolve, reject) => { @@ -17,12 +17,12 @@ export function extractMetadata(file: File): Promise<{ width: number; height: nu const video = document.createElement("video"); const timeout = setTimeout(() => { URL.revokeObjectURL(url); - reject( new Error("Video metaData load timeout — the file may be too large or the device too slow. Please try again.") ); + reject(new Error("Video metaData load timeout — the file may be too large or the device too slow. Please try again.")); }, 5000); video.preload = "metadata"; video.onloadedmetadata = () => { - clearTimeout(timeout) + clearTimeout(timeout); resolve({ width: video.videoWidth, height: video.videoHeight, @@ -31,7 +31,7 @@ export function extractMetadata(file: File): Promise<{ width: number; height: nu URL.revokeObjectURL(url); }; video.onerror = () => { - clearTimeout(timeout) + clearTimeout(timeout); URL.revokeObjectURL(url); reject(new Error("Failed to load video metadata")); }; @@ -64,7 +64,7 @@ function verifyMagicBytes(file: File): Promise { }); } -function validateRecipe(recipe: EditRecipe, duration: number ): string | null { +function validateRecipe(recipe: EditRecipe, duration: number): string | null { const validations: Array<[boolean, string]> = [ [ recipe.trimStart < 0, @@ -100,12 +100,10 @@ function validateRecipe(recipe: EditRecipe, duration: number ): string | null { recipe.brightness < -1 || recipe.brightness > 1, "Brightness must be between -1 and 1.", ], - [ recipe.contrast < 0 || recipe.contrast > 2, "Contrast must be between 0 and 2.", ], - [ recipe.saturation < 0 || recipe.saturation > 3, "Saturation must be between 0 and 3.", @@ -174,16 +172,18 @@ export function useVideoEditor() { const [overlaySize, setOverlaySize] = useState(150); const [overlayOpacity, setOverlayOpacity] = useState(100); const [currentTime, setCurrentTime] = useState(0); - const updateRecipe = useCallback((patch: Partial) => { - setRecipe((prev) => { - const next = { ...prev, ...patch }; - // GIF has no audio — force keepAudio off - if (next.format === "gif") { - next.keepAudio = false; - } - return next; - }); -}, []); + + const updateRecipe = useCallback((patch: Partial) => { + setRecipe((prev) => { + const next = { ...prev, ...patch }; + // GIF has no audio — force keepAudio off + if (next.format === "gif") { + next.keepAudio = false; + } + return next; + }); + }, []); + const isValidValue = (key: keyof EditRecipe, val: any): boolean => { switch (key) { case "preset": @@ -352,11 +352,19 @@ export function useVideoEditor() { }, [videoMetadata]); const handleFileSelect = useCallback(async (selectedFile: File) => { + // Reset all state when a new file is selected setResult(null); setStatus("idle"); setError(null); setFile(null); setVideoMetadata(null); + setDuration(0); + setOverlayFile(null); + setOverlayPosition("bottom-right"); + setOverlaySize(150); + setOverlayOpacity(100); + setRecipe(DEFAULT_RECIPE); + if (!selectedFile.type.startsWith("video/")) { setFileError("Please upload a video file only."); return; @@ -411,20 +419,13 @@ export function useVideoEditor() { setDuration(dur); setVideoMetadata({ width, height, duration: dur }); setFile(selectedFile); - - if (dimensionCheck === "warning") { - console.warn(`[Reframe] High resolution video detected (${width}×${height}). Export may be slow.`); - } - setRecipe((prev) => { - const suggestedPreset = suggestPreset(width, height); - const shouldApplySuggestion = prev.preset === DEFAULT_RECIPE.preset; - - return { - ...prev, - trimStart: 0, - trimEnd: null, - ...(shouldApplySuggestion ? { preset: suggestedPreset } : {}), - }; + // Apply suggested preset based on video dimensions + const suggestedPreset = suggestPreset(width, height); + setRecipe({ + ...DEFAULT_RECIPE, + trimStart: 0, + trimEnd: null, + preset: suggestedPreset, }); } catch (err) { setError(`Layer 4 Validation Failed: ${err instanceof Error ? err.message : "Unknown error"}`); @@ -489,7 +490,7 @@ export function useVideoEditor() { exportDurationMs: Date.now() - startedAt, }); setStatus("done"); - } catch (err) { + } catch (err) { if (exportCancelledRef.current) return; console.error("export failed:", err); @@ -504,8 +505,7 @@ export function useVideoEditor() { } setExportStartedAt(null); setStatus("error"); - } - finally { + } finally { if (exportAbortControllerRef.current === abortController) { exportAbortControllerRef.current = null; } @@ -526,7 +526,6 @@ export function useVideoEditor() { status, ]); - useEffect(() => { if (status === "exporting") { document.title = `Exporting ${progress}% | Reframe`; @@ -559,7 +558,7 @@ export function useVideoEditor() { window.addEventListener("beforeunload", handler); return () => window.removeEventListener("beforeunload", handler); }, [status]); - + useEffect(() => { const handleKeydown = (e: KeyboardEvent) => { if ( @@ -604,13 +603,13 @@ export function useVideoEditor() { }; }, [file]); - useEffect(()=>{ - return ()=>{ - if(result?.blobUrl){ + useEffect(() => { + return () => { + if (result?.blobUrl) { URL.revokeObjectURL(result.blobUrl); } - } - },[result?.blobUrl]) + }; + }, [result?.blobUrl]); useEffect(() => { return () => { @@ -638,7 +637,6 @@ export function useVideoEditor() { setExportStartedAt(null); }, []); - const reset = useCallback(() => { if (result?.blobUrl) URL.revokeObjectURL(result.blobUrl); setFile(null); @@ -657,15 +655,16 @@ export function useVideoEditor() { } }, [result]); - useEffect(() => { localStorage.setItem("soundOnCompletion", String(recipe.soundOnCompletion)); }, [recipe.soundOnCompletion]); + const seekTo = useCallback((time: number) => { if (videoRef.current) { videoRef.current.currentTime = time; } }, []); + useEffect(() => { const video = videoRef.current; if (!video) return; @@ -675,8 +674,8 @@ export function useVideoEditor() { },[]); const toggleSound = useCallback(() => { - updateRecipe({ soundOnCompletion: !recipe.soundOnCompletion }); -}, [recipe.soundOnCompletion, updateRecipe]); + updateRecipe({ soundOnCompletion: !recipe.soundOnCompletion }); + }, [recipe.soundOnCompletion, updateRecipe]); return { file,