From 3fc20aa2160cfee7fda06d361598476c30c18d62 Mon Sep 17 00:00:00 2001 From: srinidhi-2006-bit Date: Thu, 21 May 2026 16:12:53 +0530 Subject: [PATCH 1/4] fix: render image overlay in live preview --- e -i HEAD~2 | 209 +++++++++++++++++++++++++ src/components/VideoEditor.tsx | 21 ++- src/components/VideoPreview.tsx | 268 ++++---------------------------- 3 files changed, 259 insertions(+), 239 deletions(-) create mode 100644 e -i HEAD~2 diff --git a/e -i HEAD~2 b/e -i HEAD~2 new file mode 100644 index 00000000..1b3de070 --- /dev/null +++ b/e -i HEAD~2 @@ -0,0 +1,209 @@ +da86bd2 (HEAD -> feat/custom-presets, origin/feat/custom-presets) feat: add custom preset support +034a64a (main) feat: add dismissible privacy banner +f3b7ebe (upstream/main) fix: remove duplicate file format text in upload area (#777) +fc2b628 feat: export file size estimation (#744) +c4e3d76 fixed the inverted slider (#749) +e9e2fc7 feat(shortcut): add M key to toggle audio mute (#173) (#751) +a4201a1 fix(mobile): ensure export button is full-width with 44px touch target (#57) (#772) +7167080 fix(ui): hide number input spinners to prevent value visibility overlap (#776) (#778) +d36ae7f Feat/title highlight box 775 (#780) +6577296 nvda screen reader testing (#785) +f8e0f12 feat: add asynchronous audio waveform canvas rendering to trim timeline (#794) +9af2dab feat/shareable-settings-url (#799) +d4eabaf Enhanced navigation hover animations (#801) +1afb79c feat: audio normalization (#761) +07a6084 feat: implement high-quality GIF export using two-pass FFmpeg pipeline (#724) +38ab0f4 feat: add audio waveform trim visualization (#380) +056eefe feat: show ffmpeg engine download progress (#381) +af76141 feat: export preview frame as png (#725) +09af95c feat: integrate ffmpeg deshake filter for video stabilization (#695) +8bd0f91 fix: prevent video preview panel overflow by adding min-w-0 to grid column\n\n- Ensures video preview scales down to fit within its container\n- Keeps editor UI inside viewport regardless of uploaded video resolution\n- Fixes layout overflow caused by large video intrinsic sizes (#652) +7274e01 docs: require screen recordings for UI/feature PRs (#651) +0b710cb feat: editor onboarding tour — 4-step spotlight guide for first-time users (#689) +5d4f7ca feat: add formatted video and clip duration display (#690) +8d3ae74 feat: add frame grab PNG export for video preview (#691) +343c1e2 feat: implement auto-detect preset suggestion locally (#692) +a18c844 fix: resolve video title text invisibility on dark background (#647) +8de98e8 feat: implement auto-detect preset suggestion locally (#641) +63190c7 feat(video): add floating resolution badge to preview (#305) +667e3e7 feat: add multi preset batch export support (#455) +df08eae feat: add premium animated tips and facts carousel to export overlay (#635) +2e5300f Updated: navbar, Footer, customized scrollbar (#428) +ba8cf99 Feat/export size estimation clean (#446) +701af32 fix: improve visibility and contrast of EXPORT button in light mode (#467) (#511) +44c62c1 Fix output size card alignment (#442) +8e71c75 fix: improve privacy policy page layout and styling (#522) +c95c30a feat: enhance TrimControl with accessible validation states and ARIA attributes (#543) +5e90619 feat: warn user before closing tab during export or when download is ready (#629) +92a3658 fix: add input validation for all EditRecipe fields before export (#484) +8dda402 feat: show export progress percentage in browser tab title (#626) +d262479 fix: add security headers using vercel.json (#555) +6a709d5 feat: add 4K/8K dimension safety limits with auto-downscale (#384) +e1ea9f3 fix: resolve blob URL memory leak and syntax errors (#387) +2744518 fix: implement CORS validation when loading FFmpeg from CDN (#368) +5e25d51 security: pin FFmpeg CDN version and add SRI integrity hashes (#377) +686b587 feat: add real-time aspect ratio display for custom preset inputs (#473) +66952b0 feat: add image overlay support for exported videos (#623) +fee5c1f fix: standardize Tailwind colors to use theme CSS variables in VideoEditor (#561) +d657702 fix: Export and Download Button not visible in the Light Mode #456 (#565) +e4e46cf feat: premium drag and drop shimmer interaction and fix tailwind colors (#567) +445ef74 fix: move Adjustments section outside Audio & Speed section in VideoEditor (#577) +d2f191d feat: add responsive-modern-footer (#584) +1a8b8e7 feat: add SRI verification for FFmpeg CDN resources (#624) +aae933a feat: add high contrast accessibility theme (#603) +0a1d4a5 fix: reset dragging state when file dropped outside upload zone (#614) +46d0b4e fix: add aria-hidden to SVG icons for screen reader accessibility (#445) +f7889f7 feat: add 500mb max file size limit (#620) +fcc0f43 feat: save export settings to localstorage (#621) +b7b4b66 fix: improve upload card responsive layout (#622) +6e82516 feat: export complete sound notification (#392) +b88f916 feat: add preset search and filter (#114) +7dca818 feat: implement interactive and responsive footer (#591) +14a6142 chore(deps): bump actions/checkout from 4 to 6 +02069a3 chore(deps-dev): bump @types/node from 22.19.19 to 25.8.0 +06aeb53 chore(deps): bump oven-sh/setup-bun from 1 to 2 +071edc9 chore(deps): bump actions/github-script from 7 to 9 +fa198c5 chore(deps): bump actions/stale from 9 to 10 +9316fe0 fix: light mode button visibility by adding film colour palette +cf7b15d feat: add thumbnail strip for video frame navigation +3a09798 refactor: eliminate duplicated default configurations and utility constants +8e602c2 a11y: standardize minimum text scale to 14px to pass WCAG validation +71b77e8 fix: footer adapts properly to light and dark themes +e5737d3 docs: add visual tech stack badges to README +d8caaf2 chore: add dependabot configuration +d6daf03 fix: show validation error for non-video uploads and improve error handling +1b84e7f security: override postcss to ^8.5.3 to patch CVE-2025-26923 +f903bda docs: consolidate duplicated Vercel deployment sections in README +b278af6 fix: show progress while FFmpeg engine downloads (#563) +2a4e49a refactor: add CSS variables for warning and error states (#587) +ae961e3 fix: revoke blob URL on result change and unmount, add metadata timeout guard (#585) +b38b059 a11y: add aria-invalid, aria-label and label associations to form inputs (#574) +88c0651 feat: add mobile-only scroll-to-top button (#532) +6e1b098 test: add unit tests for formatBytes utility (#569) +d1ac597 docs: improve stats section in README (#572) +510dd71 fix: add -b:v 0 flag to VP9 encoder for constant quality mode (#564) +9e77d3f feat: add Ctrl+O / Cmd+O keyboard shortcut to open file dialog (#566) +68c09f9 test: add unit tests for getPresetById (#355) +afe7bc1 fix: add aria-describedby to speed slider and quality control inputs (#364) +17a5e14 feat: add letterbox/crop framing overlay to video preview (#550) +3b55afb feat: add estimated processing time to large file warning and enforce 2GB size limit (#531) +73f85fa feat: show video file type badge in file info bar (#533) +a63ffed fix: add inputMode="numeric" to width and height inputs for mobile (#534) +4347318 feat: disable spellcheck on numeric inputs (#535) +16f4e59 feat: scroll to export result on completion with reduced-motion support (#536) +1c139fd fix: consolidate DEFAULT_RECIPE and SPEED_STEPS to single source of truth (#539) +bb83199 feat: add Space shortcut for video play/pause (#544) +b35b5e4 feat: add confirmation dialog before resetting video (#549) +f7f2575 fix: prevent speed tick labels overflow on narrow screens (#580) +0d605d2 fix: add transformOrigin center to fix Safari SVG rotation rendering (#581) +6beda45 feat: add video stabilization (deshake) filter option (#317) +00cf70d docs: add JSDoc to formatBytes utility function in utils.ts +1cdb321 docs: add JSDoc to cn utility function in utils.ts +d4869f3 style: use double quotes for imports in presets.test.ts +2464fbd style: remove trailing whitespace in ternary expression in ExportSettings +f2dc1df a11y: remove redundant role='main' from
element in layout +1bb564a style: remove extra space in skip-navigation anchor tag in layout +d1aa15e fix: correct DEFAULT_RECIPE contrast and saturation defaults to 1 for FFmpeg eq filter +3da7f36 style: use double quotes in verifyMagicBytes function in useVideoEditor +fc57f3f style: use double quotes in extractMetadata function in useVideoEditor +94c9930 fix: add missing type='button' attribute to ThemeToggle button +76bb1e4 style: remove extra blank line after useState declaration in VideoEditor +d1fc87a style: use double quotes for string comparisons in aria-live div in VideoEditor +fae48fe fix: add missing type='button' to Retry Export button in VideoEditor +5acd5da fix: add missing type='button' to Copy error button in VideoEditor +fc9f174 refactor: replace template literal with cn() in VideoPreview +755c8a7 style: fix missing space after comma in import statement in PresetSelector +a1e6208 style: fix spacing around modulo operator in customHeight check +1551332 style: fix spacing around !== operator in customWidth check +99ee5ef fix: remove debug console.log from handleWidthChange in PresetSelector +3d49483 fix: remove debug console.log from PresetSelector +e00f723 a11y: add aria-label to drop zone button in FileUpload +b61b78f style: fix indentation of Preview anchor closing tag in DownloadResult +b1fdf12 fix: remove duplicate resolution display from DownloadResult header +eaf4fd6 fix: use × instead of x for resolution display in DownloadResult stat card +3551e22 style: use double quotes for imports in AudioSpeedControl for consistency +5d14b18 refactor: rename name to filename in handleFileSelect for clarity +2fddc9e style: remove redundant text-muted class shadowed by text-film-600 in ExportOverlay +a894cb3 docs: add JSDoc to getPresetById in presets.ts +453d35e refactor: replace template literal className with cn() in FormatSelector +909265a style: use double quotes for import in constants.ts for consistency +7319c01 style: fix indentation of eq filter push in buildVideoFilter +e7b58fb docs: add JSDoc to buildAudioFilter in ffmpeg.ts +fe21cd0 docs: add JSDoc to buildVideoFilter in ffmpeg.ts +c9f13f9 docs: add JSDoc to buildSessionId in ffmpeg.ts +2067a30 docs: add JSDoc to terminateFFmpeg in ffmpeg.ts +7a6f2d4 docs: add JSDoc to formatBytes in ffmpeg.ts +b445f1c fix: improve FFmpeg load error message for clarity +bbb59b3 style: reformat handleEnd one-liner to multiline in TrimControl +51ddd31 style: add missing spaces before braces in TrimControl if-blocks +b1daffc style: fix spacing around = in useState declarations in TrimControl +e92e687 docs: fix YAML front matter parse error in CONTRIBUTING.md banner +c61cf6e docs: add community banner to top of CONTRIBUTING.md +09ab672 chore: update issue template config with discussion links and star CTA +58bc39f fix: fixed audio filter chaining and add Vitest for unit testing (#440) +3ccf1fb fix: remove unrelated changes (#524) +7777eae Add Typescript typecheck Github Actions workflow (#366) +1d4a668 docs(README): add contributors section with contributor graph link (#330) +d3f1618 docs: add Vercel deployment guide to README (#357) +f6e662c fix: add aria-live region for export status screen reader announcements (#348) +84d60e5 docs: add MIT LICENSE, CODE_OF_CONDUCT, and CONTRIBUTING files (#313) +598750a docs: add GSSoC PR template for contributors (#312) +0c15c6f fix: show warning for odd width/height inputs in PresetSelector (#447) +d9e248b fix: improve export error messages to be more actionable (#469) +8ebc7d3 fix: add back navigation button to privacy policy page (#505) +0f28c65 feat: add character count and validation to filename input (#513) +c5800d5 fix: add contact page to prevent footer 404 (#516) +cd55f94 feat: add reset to default button for audio and speed controls (#518) +70edc7a fix: remove duplicate footer copyright text from layout.tsx (#523) +fea6107 fix: hide Star on GitHub button below 300px to prevent theme toggle overlap (#525) +c1cbc2c Updated (#320) +d2f7c60 fix: remove npm lockfile and use bun lockfile (#297) +5a0585f Fix/corrupted assignment bot (#503) +57d06a1 feat: add descriptive tooltips to Fit/Fill framing buttons (#194) (#489) +60ad08b feat: add file size limit and warning for large video uploads (#475) +4c94b15 docs: add bot-based issue assignment guide to CONTRIBUTING.md (#494) +d9179ec ci: fix label automation — keyword bot + force Claude API calls (#492) +5d94bee fix: replace corrupted issue-assignment-bot.yml with clean version (#488) +0bb25d1 Ci/issue assignment bot v2 (#487) +bb5b27d ci: replace AI-based issue bot with pure github-script implementation (#485) +0c0cb60 ci: add issue assignment bot with auto-assign and inactivity checks +f6c16ec feat: improve footer with modern, responsive, and unified design (#468) +adb4b93 fix(ffmpeg): resolve vfs file collisions and lifecycle memory leaks (#412) (#466) +260803c feat: add 'What is CRF?' tooltip to quality slider (#193) (#470) +75d0514 feat: add 'Copy error' button with 2s confirmation for export errors (#72) (#472) +0919eaa fix: move GitHub star button left to prevent overlap with theme toggle +113e133 fix: prevent body scroll when ExportOverlay is active +c97714b chore: remove unused lottie-react dependency +6f57517 chore: add prettier configuration for consistent code formatting +70c7624 fix: resolve race condition and improve cleanup in VideoPreview +83284b3 perf: add preconnect and dns-prefetch for jsdelivr CDN +e50da65 fix: revoke blob URLs to prevent memory leaks in video exports +90cbdf4 fix: add opacity-40 to disabled export button state +7064766 fix: correct useEffect dependency array in LottiePlayer +f28debe fix: prevent duplicate exports when export button pressed multiple times +5d812c0 feat: keyboard-accessible preset categories and custom dimensions UX +3d3f859 fix: truncate long filenames in upload UI +3883058 feat: add sitemap.xml with correct production URL (#462) +2cc553e feat: warn when trim range excludes audio track (#460) +9b5f71f fix: remove duplicate Escape handler in ExportOverlay (#461) +8428e4f Update README.md (#459) +3ed9bcb feat: add output format selector (MP4, WebM, MKV) in export settings (#452) +67b2499 feat: add feature request issue template (#43) (#451) +9ff7511 Update TrimControl.tsx (#458) +1abccb3 feat: add footer with copyright and github link (#62) (#453) +fdd3a37 feat: add bug report issue template (#42) (#450) +f2b8710 fix: configure Vercel 404 routing and add not-found page +54ecca4 feat: extract video metadata (width, height, duration) +4f60833 feat: add unit tests for getPresetById in presets.ts +3c4e9e4 docs: improve CONTRIBUTING.md and add Code of Conduct for #40 (#449) +b1145a8 chore: add id-token: write to claude-manager jobs + fix onCancel type error +e959309 fix: improve FFmpeg CDN error handling with typed FFmpegLoadError +937e611 feat: add file size validation with 2GB hard limit and 500MB warning +75830cb feat: add cancel export button to export overlay +850b0bf docs: add CONTRIBUTING.md and update README +6a3bd25 feat: add aria-label and aria-disabled to export button +a1874b8 docs: add CODE_OF_CONDUCT.md +23d23f6 feat: add reset all settings button +5b2ac56 feat: close export overlay on Escape key +f46c0d6 fix: improve aspect ratio text formatting in PresetSelector +d44f86d fix: handle floating point precision in trim validation diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index 31e20d93..19777d10 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -197,15 +197,30 @@ export default function VideoEditor() { {!file && ( +<<<<<<< HEAD

Upload a video to get started

Supports MP4, MOV, WebM and more

+======= +
+

Upload a video to get started

+ +
+>>>>>>> 25996a2 (fix: render image overlay in live preview) )} {file && (
- +
} title="Rotate" delay={100}> @@ -466,4 +481,4 @@ export default function VideoEditor() {
); -} \ No newline at end of file +} diff --git a/src/components/VideoPreview.tsx b/src/components/VideoPreview.tsx index 71095330..21a0a511 100644 --- a/src/components/VideoPreview.tsx +++ b/src/components/VideoPreview.tsx @@ -1,104 +1,27 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-noninteractive-element-interactions */ "use client"; -import { useEffect, useRef, useState, useCallback, RefObject } from "react"; +import { useEffect, useRef, RefObject } from "react"; import { EditRecipe } from "@/lib/types"; import { getPresetById } from "@/lib/presets"; -import { cn } from "@/lib/utils"; -import { Camera } from "lucide-react"; - interface Props { file: File | null; - recipe?: EditRecipe; videoRef: RefObject; + recipe: EditRecipe; } -export default function VideoPreview({ file, recipe, videoRef }: Props) { - const lastId = useRef(0); +export default function VideoPreview({ file, videoRef ,recipe }: Props) { const urlRef = useRef(null); - const [isLoading, setIsLoading] = useState(true); - const [showOverlay, setShowOverlay] = useState(false); - const onLoadedRef = useRef<(() => void) | null>(null); - - /** Capture the current video frame and download it as a PNG. */ - const handleGrabFrame = useCallback(() => { - const video = videoRef.current; - if (!video || video.readyState < 2) return; - - const canvas = document.createElement("canvas"); - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - ctx.drawImage(video, 0, 0, canvas.width, canvas.height); - - canvas.toBlob((blob) => { - if (!blob) return; - - const totalSec = Math.floor(video.currentTime); - const mins = String(Math.floor(totalSec / 60)).padStart(2, "0"); - const secs = String(totalSec % 60).padStart(2, "0"); - const filename = `frame-${mins}m${secs}s.png`; - - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); - }, "image/png"); - }, [videoRef]); useEffect(() => { if (!file) return; if (urlRef.current) URL.revokeObjectURL(urlRef.current); - setIsLoading(true); - const id = ++lastId.current; const url = URL.createObjectURL(file); - - // cleanup previous object URL safely - if (urlRef.current) { - URL.revokeObjectURL(urlRef.current); - } urlRef.current = url; - - const video = videoRef.current; - if (!video) return; - - video.src = url; - video.load(); - - // define handler once per effect run - const handleLoaded = () => { - if (lastId.current !== id) return; - video.play().catch(() => {}); - }; - - onLoadedRef.current = handleLoaded; - - video.addEventListener("loadeddata", handleLoaded); + if (videoRef.current) videoRef.current.src = url; return () => { - // cleanup event listener safely - if (onLoadedRef.current) { - video.removeEventListener("loadeddata", onLoadedRef.current); - onLoadedRef.current = null; - } - - // stop playback safely - if (video) { - video.pause(); - video.removeAttribute("src"); - video.load(); - } - - // revoke only if still current - if (urlRef.current === url) { - URL.revokeObjectURL(urlRef.current); - urlRef.current = null; - } + if (urlRef.current) URL.revokeObjectURL(urlRef.current); }; }, [file, videoRef]); @@ -112,170 +35,43 @@ export default function VideoPreview({ file, recipe, videoRef }: Props) { if (!videoRef.current || !recipe) return; videoRef.current.playbackRate = recipe.speed; }, [recipe, videoRef]); - - /** - * Compute the overlay geometry for the selected preset + framing mode. - * The preview container always uses a 16:9 aspect-video box. - * We express widths/heights as percentage strings for CSS. - */ - const overlay = (() => { - if (!recipe || !showOverlay) return null; - - const preset = recipe.preset === "custom" - ? { width: recipe.customWidth, height: recipe.customHeight } - : getPresetById(recipe.preset); - - if (!preset) return null; - - // Preview container is 16:9 - const containerW = 16; - const containerH = 9; - const containerRatio = containerW / containerH; // 1.777… - const outputRatio = preset.width / preset.height; - - if (recipe.framing === "fit") { - // Letterbox: the output video fits entirely inside 16:9, padded with bars. - if (outputRatio > containerRatio) { - // Wider output → pillarbox bars on top & bottom - const contentH = (containerRatio / outputRatio) * 100; - const barH = (100 - contentH) / 2; - return { mode: "fit", barTop: `${barH}%`, barBottom: `${barH}%`, barLeft: "0", barRight: "0" }; - } else { - // Taller output → letterbox bars on left & right - const contentW = (outputRatio / containerRatio) * 100; - const barW = (100 - contentW) / 2; - return { mode: "fit", barTop: "0", barBottom: "0", barLeft: `${barW}%`, barRight: `${barW}%` }; - } - } else { - // Fill / crop: the output fills the entire 16:9 preview — show a box representing what survives the crop. - if (outputRatio < containerRatio) { - // Output is taller → crops top & bottom - const visibleH = (outputRatio / containerRatio) * 100; - const cropH = (100 - visibleH) / 2; - return { mode: "fill", barTop: `${cropH}%`, barBottom: `${cropH}%`, barLeft: "0", barRight: "0" }; - } else { - // Output is wider → crops left & right - const visibleW = (containerRatio / outputRatio) * 100; - const cropW = (100 - visibleW) / 2; - return { mode: "fill", barTop: "0", barBottom: "0", barLeft: `${cropW}%`, barRight: `${cropW}%` }; - } - } - })(); - - if (!file) return null; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.code === "Space") { - const target = e.target as HTMLElement; - if ( - target.tagName === "INPUT" || - target.tagName === "TEXTAREA" || - target.isContentEditable - ) { - return; - } - - const video = videoRef.current; - if (video) { - e.preventDefault(); // Prevent default page scroll - if (video.paused) { - video.play().catch(() => {}); - } else { - video.pause(); - } - } - } - }; - + const preset = + recipe.preset !== "custom" + ? getPresetById(recipe.preset) + : null; + + const previewWidth = + recipe.preset === "custom" + ? recipe.customWidth || 1920 + : preset?.width || 1920; + + const previewHeight = + recipe.preset === "custom" + ? recipe.customHeight || 1080 + : preset?.height || 1080; + + const aspectRatio = `${previewWidth}/${previewHeight}`; return (
- {isLoading && ( -
- )} - {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + - - {/* Letterbox / Crop overlay */} - {overlay && ( -