feat: initial DnD and ffmpeg install
This commit is contained in:
197
apps/web/src/lib/ffmpeg-utils.ts
Normal file
197
apps/web/src/lib/ffmpeg-utils.ts
Normal file
@ -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;
|
||||
};
|
Reference in New Issue
Block a user