From f4dda3ae31315813fed988d076caa0cd147c1708 Mon Sep 17 00:00:00 2001 From: Devangee Jain Date: Sun, 24 May 2026 21:54:59 +0530 Subject: [PATCH] feat: add video compression feature using ffmpeg.wasm --- next.config.ts | 15 ++- package.json | 12 +- src/app/layout.tsx | 2 +- src/components/ErrorBoundaryWrapper.tsx | 4 + src/components/ExportSettings.tsx | 28 +++++ src/components/VideoCompressor.tsx | 159 ++++++++++++++++++++++++ src/components/VideoEditor.tsx | 8 +- 7 files changed, 217 insertions(+), 11 deletions(-) create mode 100644 src/components/ErrorBoundaryWrapper.tsx create mode 100644 src/components/VideoCompressor.tsx diff --git a/next.config.ts b/next.config.ts index fb2f90ee..790b2c95 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,13 +1,22 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - output: "export", - // Required for ffmpeg.wasm to load WASM files correctly - // Without this, Next.js might try to process .wasm files and break them + // output: "export", // ⚠️ Must disable this - static export doesn't support headers() webpack: (config) => { config.resolve.fallback = { fs: false }; return config; }, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' }, + { key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' }, + ], + }, + ]; + }, }; export default nextConfig; \ No newline at end of file diff --git a/package.json b/package.json index b503f760..ed5da9bf 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "vitest" }, "dependencies": { - "@ffmpeg/ffmpeg": "^0.12.10", + "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", "clsx": "^2.1.1", "focus-trap-react": "^12.0.1", @@ -25,6 +25,9 @@ "wasm-feature-detect": "^1.8.0" }, "devDependencies": { + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.4.3", "@types/bun": "^1.3.14", "@types/node": "^25", "@types/react": "^19", @@ -32,13 +35,10 @@ "eslint": "^9", "eslint-config-next": "^15.1.8", "eslint-plugin-jsx-a11y": "^6.10.2", + "jsdom": "^22.1.0", "postcss": "^8", "tailwindcss": "^3.4.17", "typescript": "^5", - "vitest": "^4.1.6", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "jsdom": "^22.1.0", - "@testing-library/jest-dom": "^6.0.0" + "vitest": "^4.1.6" } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f17960b2..fa03832b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import { Bebas_Neue, Syne, DM_Sans } from "next/font/google"; -import ErrorBoundary from "@/components/ErrorBoundary"; +import ErrorBoundary from "@/components/ErrorBoundaryWrapper"; import "./globals.css"; import { ThemeProvider } from "@/components/ThemeProvider"; import { ThemeToggle } from "@/components/ThemeToggle"; diff --git a/src/components/ErrorBoundaryWrapper.tsx b/src/components/ErrorBoundaryWrapper.tsx new file mode 100644 index 00000000..2e4e8dd6 --- /dev/null +++ b/src/components/ErrorBoundaryWrapper.tsx @@ -0,0 +1,4 @@ +"use client"; + +import ErrorBoundary from "@/components/ErrorBoundary"; +export default ErrorBoundary; \ No newline at end of file diff --git a/src/components/ExportSettings.tsx b/src/components/ExportSettings.tsx index 49f21bc9..f7b711fa 100644 --- a/src/components/ExportSettings.tsx +++ b/src/components/ExportSettings.tsx @@ -5,7 +5,10 @@ import { cn } from "@/lib/utils"; import { SlidersHorizontal, Info as InfoIcon, + Zap, } from "lucide-react"; +import React, { useState } from "react"; +import VideoCompressor from "@/components/VideoCompressor"; import { estimateExportSize, @@ -13,6 +16,7 @@ import { } from "@/lib/exportEstimate"; interface Props { + videoFile: File | null; recipe: EditRecipe; duration: number; onChange: ( @@ -23,6 +27,7 @@ interface Props { export default function ExportSettings({ recipe, duration, + videoFile, onChange, }: Props) { const label = @@ -41,6 +46,7 @@ export default function ExportSettings({ duration ) ); + const [isCompressEnabled, setIsCompressEnabled] = useState(false); return ( <> @@ -141,6 +147,28 @@ export default function ExportSettings({ )} +
+
+ + setIsCompressEnabled(e.target.checked)} + className="accent-film-600 cursor-pointer" + /> +
+ + {isCompressEnabled && ( +
+ +
+ )} +
+ +