// Transcription limits and thresholds // These constants are shared across components to ensure consistency export const TRANSCRIPTION_LIMITS = { // Duration limits (in seconds) MIN_DURATION: 3, // Minimum recommended duration for accuracy MAX_DURATION: 180, // Maximum recommended duration (3 minutes) // File size limits (in MB) MAX_FILE_SIZE: 100, // Maximum recommended file size } as const; export const WARNING_MESSAGES = { TOO_SHORT: { title: "Media Too Short", message: "Your media file is less than 3 seconds long. This may result in reduced transcription accuracy due to insufficient context for the AI model. For best results, use media files at least 30 seconds long.", }, TOO_LONG: { title: "Media Too Long", message: "Your media file is longer than 3 minutes. This may exceed server resources and could result in processing failures or timeouts. Consider trimming your media to under 3 minutes for optimal results.", }, VERY_LARGE_FILE: { title: "File Too Large", message: "Your file exceeds the recommended 100MB limit. This may cause upload failures or processing timeouts. Please use a smaller file or compress your media before uploading.", }, } as const; type WarningType = keyof typeof WARNING_MESSAGES; interface MediaInfo { file: File; duration?: number; // Duration in seconds, may not be available immediately } /** * Get the duration of a media file * Returns a promise that resolves with the duration in seconds */ export const getMediaDuration = (file: File): Promise => { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); if (file.type.startsWith('video/')) { const video = document.createElement('video'); video.preload = 'metadata'; video.onloadedmetadata = () => { URL.revokeObjectURL(url); resolve(video.duration); }; video.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Failed to load video metadata')); }; video.src = url; } else if (file.type.startsWith('audio/')) { const audio = document.createElement('audio'); audio.preload = 'metadata'; audio.onloadedmetadata = () => { URL.revokeObjectURL(url); resolve(audio.duration); }; audio.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Failed to load audio metadata')); }; audio.src = url; } else { URL.revokeObjectURL(url); reject(new Error('Unsupported media type')); } }); }; /** * Check for transcription warnings based on file size and duration * Returns an array of warning types that should be shown to the user */ export const checkTranscriptionWarnings = (mediaInfo: MediaInfo): WarningType[] => { const warnings: WarningType[] = []; const { file, duration } = mediaInfo; // Check file size (convert to MB) const fileSizeMB = file.size / (1024 * 1024); if (fileSizeMB > TRANSCRIPTION_LIMITS.MAX_FILE_SIZE) { warnings.push('VERY_LARGE_FILE'); } // Check duration if available if (duration !== undefined && !isNaN(duration)) { if (duration < TRANSCRIPTION_LIMITS.MIN_DURATION) { warnings.push('TOO_SHORT'); } else if (duration > TRANSCRIPTION_LIMITS.MAX_DURATION) { warnings.push('TOO_LONG'); } } return warnings; }; /** * Format file size in a human-readable format */ export const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; }; /** * Format duration in a human-readable format */ export const formatDuration = (seconds: number): string => { if (isNaN(seconds) || seconds < 0) return 'Unknown'; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); if (minutes === 0) { return `${remainingSeconds}s`; } else { return `${minutes}m ${remainingSeconds}s`; } };