| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- '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 (
- <div className="px-3 py-2" style={{ borderTop: '1px solid rgba(255,255,255,0.06)' }}>
- {/* Info rows — 2-column grid */}
- <div className="grid grid-cols-2 gap-x-4 gap-y-1">
- {visible.map(({ label, value }) => (
- <div key={label} className="flex items-center justify-between gap-2">
- <span className="text-[11px] truncate shrink-0" style={{ color: 'var(--text-subtle)' }}>
- {label}
- </span>
- <span className="text-[11px] font-medium text-right shrink-0" style={{ color: 'var(--text)' }}>
- {value}
- </span>
- </div>
- ))}
- </div>
- </div>
- );
- }
|