Files
kit-ui/components/media/FileInfo.tsx

211 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import * as React from 'react';
import { File, FileVideo, FileAudio, FileImage, Clock, HardDrive, Film, Music } from 'lucide-react';
import { Card } from '@/components/ui/card';
import type { ConversionFormat } from '@/types/media';
interface FileInfoProps {
file: File;
format: ConversionFormat;
}
interface FileMetadata {
name: string;
size: string;
type: string;
category: string;
duration?: string;
dimensions?: string;
}
export function FileInfo({ file, format }: FileInfoProps) {
const [metadata, setMetadata] = React.useState<FileMetadata | null>(null);
React.useEffect(() => {
extractMetadata(file, format);
}, [file, format]);
const extractMetadata = async (file: File, format: ConversionFormat) => {
const sizeInMB = (file.size / (1024 * 1024)).toFixed(2);
const baseMetadata: FileMetadata = {
name: file.name,
size: file.size < 1024 * 1024 ? `${(file.size / 1024).toFixed(2)} KB` : `${sizeInMB} MB`,
type: format.name,
category: format.category,
};
// Try to extract media-specific metadata
if (format.category === 'video' && file.type.startsWith('video/')) {
try {
const video = document.createElement('video');
video.preload = 'metadata';
const promise = new Promise<FileMetadata>((resolve) => {
video.onloadedmetadata = () => {
const duration = video.duration;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
const durationStr = `${minutes}:${seconds.toString().padStart(2, '0')}`;
resolve({
...baseMetadata,
duration: durationStr,
dimensions: `${video.videoWidth} × ${video.videoHeight}`,
});
URL.revokeObjectURL(video.src);
};
video.onerror = () => {
resolve(baseMetadata);
URL.revokeObjectURL(video.src);
};
});
video.src = URL.createObjectURL(file);
const result = await promise;
setMetadata(result);
return;
} catch (error) {
console.error('Failed to extract video metadata:', error);
}
} else if (format.category === 'audio' && file.type.startsWith('audio/')) {
try {
const audio = document.createElement('audio');
audio.preload = 'metadata';
const promise = new Promise<FileMetadata>((resolve) => {
audio.onloadedmetadata = () => {
const duration = audio.duration;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
const durationStr = `${minutes}:${seconds.toString().padStart(2, '0')}`;
resolve({
...baseMetadata,
duration: durationStr,
});
URL.revokeObjectURL(audio.src);
};
audio.onerror = () => {
resolve(baseMetadata);
URL.revokeObjectURL(audio.src);
};
});
audio.src = URL.createObjectURL(file);
const result = await promise;
setMetadata(result);
return;
} catch (error) {
console.error('Failed to extract audio metadata:', error);
}
} else if (format.category === 'image' && file.type.startsWith('image/')) {
try {
const img = new Image();
const promise = new Promise<FileMetadata>((resolve) => {
img.onload = () => {
resolve({
...baseMetadata,
dimensions: `${img.width} × ${img.height}`,
});
URL.revokeObjectURL(img.src);
};
img.onerror = () => {
resolve(baseMetadata);
URL.revokeObjectURL(img.src);
};
});
img.src = URL.createObjectURL(file);
const result = await promise;
setMetadata(result);
return;
} catch (error) {
console.error('Failed to extract image metadata:', error);
}
}
setMetadata(baseMetadata);
};
const getCategoryIcon = () => {
switch (format.category) {
case 'video':
return <FileVideo className="h-5 w-5 text-primary" />;
case 'audio':
return <FileAudio className="h-5 w-5 text-primary" />;
case 'image':
return <FileImage className="h-5 w-5 text-primary" />;
default:
return <File className="h-5 w-5 text-primary" />;
}
};
if (!metadata) {
return (
<Card className="p-4 animate-pulse">
<div className="flex items-start gap-3">
<div className="w-5 h-5 bg-secondary rounded"></div>
<div className="flex-1 space-y-2">
<div className="h-4 bg-secondary rounded w-3/4"></div>
<div className="h-3 bg-secondary rounded w-1/2"></div>
</div>
</div>
</Card>
);
}
return (
<Card className="p-4">
<div className="flex items-start gap-3">
<div className="mt-0.5">{getCategoryIcon()}</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-medium text-foreground truncate" title={metadata.name}>
{metadata.name}
</h3>
<div className="mt-2 grid grid-cols-2 gap-2 text-xs">
{/* File Size */}
<div className="flex items-center gap-2 text-muted-foreground">
<HardDrive className="h-3.5 w-3.5" />
<span>{metadata.size}</span>
</div>
{/* Type */}
<div className="flex items-center gap-2 text-muted-foreground">
<File className="h-3.5 w-3.5" />
<span>{metadata.type}</span>
</div>
{/* Duration (for video/audio) */}
{metadata.duration && (
<div className="flex items-center gap-2 text-muted-foreground">
<Clock className="h-3.5 w-3.5" />
<span>{metadata.duration}</span>
</div>
)}
{/* Dimensions */}
{metadata.dimensions && (
<div className="flex items-center gap-2 text-muted-foreground">
{format.category === 'video' ? (
<Film className="h-3.5 w-3.5" />
) : (
<FileImage className="h-3.5 w-3.5" />
)}
<span>{metadata.dimensions}</span>
</div>
)}
</div>
</div>
</div>
</Card>
);
}