You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
3.8 KiB
137 lines
3.8 KiB
/** |
|
* 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<Blob> { |
|
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<Blob> { |
|
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<File> { |
|
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() |
|
}); |
|
}
|
|
|