/** * File compression utilities for images and videos * Compresses files before upload to reduce size */ export interface CompressionOptions { maxWidth?: number; maxHeight?: number; quality?: number; // 0-1 for images maxSizeMB?: number; // Skip compression if file is smaller than this } const DEFAULT_OPTIONS: CompressionOptions = { maxWidth: 1200, maxHeight: 1080, quality: 0.85, maxSizeMB: 2 }; /** * Compress an image file * @param file - The image file to compress * @param options - Compression options * @returns Compressed file as Blob */ export async function compressImage( file: File, options: CompressionOptions = {} ): Promise { const opts = { ...DEFAULT_OPTIONS, ...options }; // Skip compression if file is already small enough if (opts.maxSizeMB && file.size <= opts.maxSizeMB * 1024 * 1024) { return file; } return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); let width = img.width; let height = img.height; // Calculate new dimensions if (opts.maxWidth && width > opts.maxWidth) { height = (height * opts.maxWidth) / width; width = opts.maxWidth; } if (opts.maxHeight && height > opts.maxHeight) { width = (width * opts.maxHeight) / height; height = opts.maxHeight; } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('Could not get canvas context')); return; } ctx.drawImage(img, 0, 0, width, height); canvas.toBlob( (blob) => { if (blob) { resolve(blob); } else { reject(new Error('Failed to compress image')); } }, file.type || 'image/jpeg', opts.quality ); }; img.onerror = () => reject(new Error('Failed to load image')); img.src = e.target?.result as string; }; reader.onerror = () => reject(new Error('Failed to read file')); reader.readAsDataURL(file); }); } /** * Compress a video file (re-encode to reduce size) * Note: This is a basic implementation. For better compression, consider using a library like ffmpeg.wasm * @param file - The video file to compress * @param options - Compression options * @returns Compressed file as Blob (or original if compression not supported) */ export async function compressVideo( file: File, options: CompressionOptions = {} ): Promise { const opts = { ...DEFAULT_OPTIONS, ...options }; // Skip compression if file is already small enough if (opts.maxSizeMB && file.size <= opts.maxSizeMB * 1024 * 1024) { return file; } // For now, return original file as browser video compression is complex // In the future, could integrate ffmpeg.wasm for client-side video compression // For now, we'll rely on server-side compression or accept larger files return file; } /** * Compress a file based on its type * @param file - The file to compress * @param options - Compression options * @returns Compressed file as File object */ export async function compressFile( file: File, options: CompressionOptions = {} ): Promise { let compressedBlob: Blob; if (file.type.startsWith('image/')) { compressedBlob = await compressImage(file, options); } else if (file.type.startsWith('video/')) { compressedBlob = await compressVideo(file, options); } else { // No compression for other file types return file; } // Create a new File object from the compressed blob return new File([compressedBlob], file.name, { type: file.type, lastModified: Date.now() }); }