diff options
| author | Hyteq <DerpsDer.es@gmail.com> | 2025-06-23 10:33:03 +0300 |
|---|---|---|
| committer | Hyteq <DerpsDer.es@gmail.com> | 2025-06-23 10:33:03 +0300 |
| commit | 56480772c3ce5b9a2da8fb57b66cb184a52cfc97 (patch) | |
| tree | adadac534ba88a681a632cfd2aa60b6c4f5dd8c5 /apps/web/src/lib | |
| parent | 56efb55a0fbcd22fefd6d8c5cf4300166b7f168a (diff) | |
| download | opencut-56480772c3ce5b9a2da8fb57b66cb184a52cfc97.tar.gz | |
feat: initial DnD and ffmpeg install
Diffstat (limited to 'apps/web/src/lib')
| -rw-r--r-- | apps/web/src/lib/ffmpeg-utils.ts | 197 | ||||
| -rw-r--r-- | apps/web/src/lib/media-processing.ts | 7 |
2 files changed, 201 insertions, 3 deletions
diff --git a/apps/web/src/lib/ffmpeg-utils.ts b/apps/web/src/lib/ffmpeg-utils.ts new file mode 100644 index 00000000..56085a21 --- /dev/null +++ b/apps/web/src/lib/ffmpeg-utils.ts @@ -0,0 +1,197 @@ +import { FFmpeg } from '@ffmpeg/ffmpeg'; +import { toBlobURL } from '@ffmpeg/util'; + +let ffmpeg: FFmpeg | null = null; + +export const initFFmpeg = async (): Promise<FFmpeg> => { + if (ffmpeg) return ffmpeg; + + ffmpeg = new FFmpeg(); + + // Use locally hosted files instead of CDN + const baseURL = '/ffmpeg'; + + await ffmpeg.load({ + coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), + wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), + }); + + return ffmpeg; +}; + +export const generateThumbnail = async ( + videoFile: File, + timeInSeconds: number = 1 +): Promise<string> => { + const ffmpeg = await initFFmpeg(); + + const inputName = 'input.mp4'; + const outputName = 'thumbnail.jpg'; + + // Write input file + 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 + ]); + + // Read output file + const data = await ffmpeg.readFile(outputName); + const blob = new Blob([data], { type: 'image/jpeg' }); + + // Cleanup + await ffmpeg.deleteFile(inputName); + await ffmpeg.deleteFile(outputName); + + return URL.createObjectURL(blob); +}; + +export const trimVideo = async ( + videoFile: File, + startTime: number, + endTime: number, + onProgress?: (progress: number) => void +): Promise<Blob> => { + const ffmpeg = await initFFmpeg(); + + const inputName = 'input.mp4'; + const outputName = 'output.mp4'; + + // Set up progress callback + if (onProgress) { + ffmpeg.on('progress', ({ progress }) => { + onProgress(progress * 100); + }); + } + + // Write input file + 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 + ]); + + // Read output file + const data = await ffmpeg.readFile(outputName); + 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<{ + duration: number; + width: number; + height: number; + fps: number; +}> => { + const ffmpeg = await initFFmpeg(); + + const inputName = 'input.mp4'; + + // Write input file + await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); + + // Get video info + await ffmpeg.exec(['-i', inputName, '-f', 'null', '-']); + + // Note: In a real implementation, you'd parse the FFmpeg output + // For now, we'll return default values and enhance this later + + // Cleanup + await ffmpeg.deleteFile(inputName); + + return { + duration: 10, // Placeholder - would parse from FFmpeg output + width: 1920, // Placeholder + height: 1080, // Placeholder + fps: 30 // Placeholder + }; +}; + +export const convertToWebM = async ( + videoFile: File, + onProgress?: (progress: number) => void +): Promise<Blob> => { + const ffmpeg = await initFFmpeg(); + + const inputName = 'input.mp4'; + const outputName = 'output.webm'; + + // Set up progress callback + if (onProgress) { + ffmpeg.on('progress', ({ progress }) => { + onProgress(progress * 100); + }); + } + + // Write input file + 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 + ]); + + // Read output file + const data = await ffmpeg.readFile(outputName); + 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' +): Promise<Blob> => { + const ffmpeg = await initFFmpeg(); + + const inputName = 'input.mp4'; + const outputName = `output.${format}`; + + // Write input file + 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 + ]); + + // 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 diff --git a/apps/web/src/lib/media-processing.ts b/apps/web/src/lib/media-processing.ts index c51dde55..d241f411 100644 --- a/apps/web/src/lib/media-processing.ts +++ b/apps/web/src/lib/media-processing.ts @@ -6,6 +6,7 @@ import { getImageAspectRatio,
type MediaItem,
} from "@/stores/media-store";
+// import { generateThumbnail, getVideoInfo } from "./ffmpeg-utils"; // Temporarily disabled
export interface ProcessedMediaItem extends Omit<MediaItem, "id"> {}
@@ -33,7 +34,7 @@ export async function processMediaFiles( // Get image aspect ratio
aspectRatio = await getImageAspectRatio(file);
} else if (fileType === "video") {
- // Generate thumbnail and get aspect ratio for videos
+ // Use basic thumbnail generation for now
const videoResult = await generateVideoThumbnail(file);
thumbnailUrl = videoResult.thumbnailUrl;
aspectRatio = videoResult.aspectRatio;
@@ -42,8 +43,8 @@ export async function processMediaFiles( aspectRatio = 1;
}
- // Get duration for videos and audio
- if (fileType === "video" || fileType === "audio") {
+ // Get duration for videos and audio (if not already set by FFmpeg)
+ if ((fileType === "video" || fileType === "audio") && !duration) {
duration = await getMediaDuration(file);
}
|
