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

/**
* 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()
});
}