'use client'; import { Asset } from '@/lib/api'; interface Props { asset: Asset; fps: number; compact?: boolean; // true = show inline row; false = collapsible section } function formatBytes(bytes?: number | null): string { if (!bytes) return '—'; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; } function formatDate(iso: string): string { if (!iso) return '—'; try { return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', }).format(new Date(iso)); } catch { return '—'; } } function fmtDur(s?: number | null): string { if (!s || isNaN(s)) return '—'; const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = Math.floor(s % 60); if (h > 0) return `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}`; return `${m}:${String(sec).padStart(2,'0')}`; } function fmtRes(width?: number | null, height?: number | null): string { if (!width || !height) return '—'; return `${width} × ${height}`; } // Bitrate in bits/s → human-readable string function fmtBitrate(bps?: number | null): string { if (!bps || bps <= 0) return '—'; if (bps >= 1_000_000) return `${(bps / 1_000_000).toFixed(1)} Mbps`; if (bps >= 1_000) return `${(bps / 1_000).toFixed(0)} Kbps`; return `${bps} bps`; } // Codec display name function fmtCodec(codec?: string | null): string { if (!codec) return '—'; const c = codec.toUpperCase(); if (c === 'H264' || c === 'AVC') return 'H.264 / AVC'; if (c === 'H265' || c === 'HEVC') return 'H.265 / HEVC'; if (c === 'VP8') return 'VP8'; if (c === 'VP9') return 'VP9'; if (c === 'AV1') return 'AV1'; if (c.startsWith('AUDIO/')) return codec.replace('AUDIO/', '').toUpperCase(); return codec; } // Mime type → human label function fmtMime(mime?: string): string { if (!mime) return '—'; if (mime === 'video/mp4' || mime === 'video/mp4; codecs=avc1') return 'MP4'; if (mime === 'video/webm') return 'WebM'; if (mime === 'application/x-mpegURL') return 'HLS / M3U8'; if (mime === 'video/quicktime') return 'MOV / QuickTime'; if (mime === 'video/x-msvideo') return 'AVI'; if (mime === 'video/x-matroska') return 'MKV'; return mime; } // VideoInfoPanel — collapsible info strip for review page export function VideoInfoPanel({ asset, fps }: Props) { const hasVideoRes = asset.videoWidth != null || asset.videoHeight != null; const hasFileSize = asset.fileSize != null && asset.fileSize > 0; const rows: { label: string; value: string }[] = [ { label: 'Duration', value: fmtDur(asset.duration) }, { label: 'Bitrate', value: fmtBitrate(asset.bitrate) }, { label: 'Resolution', value: fmtRes(asset.videoWidth, asset.videoHeight) }, { label: 'Frame Rate', value: fps ? `${fps} fps` : asset.fps ? `${asset.fps} fps` : '—' }, { label: 'Codec', value: fmtCodec(asset.codec) }, { label: 'Format', value: fmtMime(asset.mimeType) }, { label: 'File Size', value: hasFileSize ? formatBytes(asset.fileSize) : '—' }, { label: 'Uploaded', value: asset.createdAt ? formatDate(asset.createdAt) : '—' }, ]; // Only show rows with real data const visible = rows.filter(r => r.value !== '—'); if (visible.length === 0) return null; return (