feat: initial DnD and ffmpeg install

This commit is contained in:
Hyteq
2025-06-23 10:33:03 +03:00
parent 56efb55a0f
commit 56480772c3
6 changed files with 263 additions and 4 deletions

View 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;
};

View File

@ -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);
}