diff options
Diffstat (limited to 'apps/web/src/lib/ffmpeg-utils.ts')
| -rw-r--r-- | apps/web/src/lib/ffmpeg-utils.ts | 209 |
1 files changed, 85 insertions, 124 deletions
diff --git a/apps/web/src/lib/ffmpeg-utils.ts b/apps/web/src/lib/ffmpeg-utils.ts index c34200f6..ae0c267b 100644 --- a/apps/web/src/lib/ffmpeg-utils.ts +++ b/apps/web/src/lib/ffmpeg-utils.ts @@ -1,5 +1,5 @@ -import { FFmpeg } from "@ffmpeg/ffmpeg"; -import { toBlobURL } from "@ffmpeg/util"; +import { FFmpeg } from '@ffmpeg/ffmpeg'; +import { toBlobURL } from '@ffmpeg/util'; let ffmpeg: FFmpeg | null = null; @@ -7,13 +7,13 @@ export const initFFmpeg = async (): Promise<FFmpeg> => { if (ffmpeg) return ffmpeg; ffmpeg = new FFmpeg(); - + // Use locally hosted files instead of CDN - const baseURL = "/ffmpeg"; - + const baseURL = '/ffmpeg'; + await ffmpeg.load({ - coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"), - wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"), + coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), + wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), }); return ffmpeg; @@ -21,42 +21,34 @@ export const initFFmpeg = async (): Promise<FFmpeg> => { export const generateThumbnail = async ( videoFile: File, - timeInSeconds = 1 + timeInSeconds: number = 1 ): Promise<string> => { const ffmpeg = await initFFmpeg(); - - const inputName = "input.mp4"; - const outputName = "thumbnail.jpg"; - + + const inputName = 'input.mp4'; + const outputName = 'thumbnail.jpg'; + // Write input file - await ffmpeg.writeFile( - inputName, - new Uint8Array(await videoFile.arrayBuffer()) - ); - + await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); + // Generate thumbnail at specific time await ffmpeg.exec([ - "-i", - inputName, - "-ss", - timeInSeconds.toString(), - "-vframes", - "1", - "-vf", - "scale=320:240", - "-q:v", - "2", - outputName, + '-i', inputName, + '-ss', timeInSeconds.toString(), + '-vframes', '1', + '-vf', 'scale=320:240', + '-q:v', '2', + outputName ]); - + // Read output file const data = await ffmpeg.readFile(outputName); - const blob = new Blob([data], { type: "image/jpeg" }); - + const blob = new Blob([data], { type: 'image/jpeg' }); + // Cleanup await ffmpeg.deleteFile(inputName); await ffmpeg.deleteFile(outputName); - + return URL.createObjectURL(blob); }; @@ -67,52 +59,43 @@ export const trimVideo = async ( onProgress?: (progress: number) => void ): Promise<Blob> => { const ffmpeg = await initFFmpeg(); - - const inputName = "input.mp4"; - const outputName = "output.mp4"; - + + const inputName = 'input.mp4'; + const outputName = 'output.mp4'; + // Set up progress callback if (onProgress) { - ffmpeg.on("progress", ({ progress }) => { + ffmpeg.on('progress', ({ progress }) => { onProgress(progress * 100); }); } - + // Write input file - await ffmpeg.writeFile( - inputName, - new Uint8Array(await videoFile.arrayBuffer()) - ); - + await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); + const duration = endTime - startTime; - + // Trim video await ffmpeg.exec([ - "-i", - inputName, - "-ss", - startTime.toString(), - "-t", - duration.toString(), - "-c", - "copy", // Use stream copy for faster processing - outputName, + '-i', inputName, + '-ss', startTime.toString(), + '-t', duration.toString(), + '-c', 'copy', // Use stream copy for faster processing + outputName ]); - + // Read output file const data = await ffmpeg.readFile(outputName); - const blob = new Blob([data], { type: "video/mp4" }); - + const blob = new Blob([data], { type: 'video/mp4' }); + // Cleanup await ffmpeg.deleteFile(inputName); await ffmpeg.deleteFile(outputName); - + return blob; }; -export const getVideoInfo = async ( - videoFile: File -): Promise<{ +export const getVideoInfo = async (videoFile: File): Promise<{ duration: number; width: number; height: number; @@ -120,32 +103,27 @@ export const getVideoInfo = async ( }> => { const ffmpeg = await initFFmpeg(); - const inputName = "input.mp4"; + const inputName = 'input.mp4'; // Write input file - await ffmpeg.writeFile( - inputName, - new Uint8Array(await videoFile.arrayBuffer()) - ); + await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); // Capture FFmpeg stderr output with a one-time listener pattern - let ffmpegOutput = ""; + let ffmpegOutput = ''; let listening = true; const listener = (data: string) => { if (listening) ffmpegOutput += data; }; - ffmpeg.on("log", ({ message }) => listener(message)); + ffmpeg.on('log', ({ message }) => listener(message)); // Run ffmpeg to get info (stderr will contain the info) try { - await ffmpeg.exec(["-i", inputName, "-f", "null", "-"]); + await ffmpeg.exec(['-i', inputName, '-f', 'null', '-']); } catch (error) { listening = false; await ffmpeg.deleteFile(inputName); - console.error("FFmpeg execution failed:", error); - throw new Error( - "Failed to extract video info. The file may be corrupted or in an unsupported format." - ); + console.error('FFmpeg execution failed:', error); + throw new Error('Failed to extract video info. The file may be corrupted or in an unsupported format.'); } // Disable listener after exec completes @@ -165,12 +143,8 @@ export const getVideoInfo = async ( duration = parseInt(h) * 3600 + parseInt(m) * 60 + parseFloat(s); } - const videoStreamMatch = ffmpegOutput.match( - /Video:.* (\d+)x(\d+)[^,]*, ([\d.]+) fps/ - ); - let width = 0, - height = 0, - fps = 0; + const videoStreamMatch = ffmpegOutput.match(/Video:.* (\d+)x(\d+)[^,]*, ([\d.]+) fps/); + let width = 0, height = 0, fps = 0; if (videoStreamMatch) { width = parseInt(videoStreamMatch[1]); height = parseInt(videoStreamMatch[2]); @@ -181,7 +155,7 @@ export const getVideoInfo = async ( duration, width, height, - fps, + fps }; }; @@ -190,81 +164,68 @@ export const convertToWebM = async ( onProgress?: (progress: number) => void ): Promise<Blob> => { const ffmpeg = await initFFmpeg(); - - const inputName = "input.mp4"; - const outputName = "output.webm"; - + + const inputName = 'input.mp4'; + const outputName = 'output.webm'; + // Set up progress callback if (onProgress) { - ffmpeg.on("progress", ({ progress }) => { + ffmpeg.on('progress', ({ progress }) => { onProgress(progress * 100); }); } - + // Write input file - await ffmpeg.writeFile( - inputName, - new Uint8Array(await videoFile.arrayBuffer()) - ); - + await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); + // Convert to WebM await ffmpeg.exec([ - "-i", - inputName, - "-c:v", - "libvpx-vp9", - "-crf", - "30", - "-b:v", - "0", - "-c:a", - "libopus", - outputName, + '-i', inputName, + '-c:v', 'libvpx-vp9', + '-crf', '30', + '-b:v', '0', + '-c:a', 'libopus', + outputName ]); - + // Read output file const data = await ffmpeg.readFile(outputName); - const blob = new Blob([data], { type: "video/webm" }); - + const blob = new Blob([data], { type: 'video/webm' }); + // Cleanup await ffmpeg.deleteFile(inputName); await ffmpeg.deleteFile(outputName); - + return blob; }; export const extractAudio = async ( videoFile: File, - format: "mp3" | "wav" = "mp3" + format: 'mp3' | 'wav' = 'mp3' ): Promise<Blob> => { const ffmpeg = await initFFmpeg(); - - const inputName = "input.mp4"; + + const inputName = 'input.mp4'; const outputName = `output.${format}`; - + // Write input file - await ffmpeg.writeFile( - inputName, - new Uint8Array(await videoFile.arrayBuffer()) - ); - + await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); + // Extract audio await ffmpeg.exec([ - "-i", - inputName, - "-vn", // Disable video - "-acodec", - format === "mp3" ? "libmp3lame" : "pcm_s16le", - outputName, + '-i', inputName, + '-vn', // Disable video + '-acodec', format === 'mp3' ? 'libmp3lame' : 'pcm_s16le', + outputName ]); - + // Read output file const data = await ffmpeg.readFile(outputName); const blob = new Blob([data], { type: `audio/${format}` }); - + // Cleanup await ffmpeg.deleteFile(inputName); await ffmpeg.deleteFile(outputName); - + return blob; -}; +};
\ No newline at end of file |
