import React, { useState } from 'react'; import { FileVideo, FileAudio, FileImage, File, Clock, ExternalLink, ChevronDown, ChevronUp, Monitor, Volume2, Palette, Calendar, Hash, Zap, HardDrive, Film, Eye } from 'lucide-react'; import { Material, MaterialSegment } from '../types/material'; import { useMaterialStore } from '../store/materialStore'; interface MaterialCardProps { material: Material; } // 格式化时间(秒转为 mm:ss 格式) const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; }; // 格式化文件大小 const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; // 格式化比特率 const formatBitrate = (bitrate: number): string => { if (bitrate >= 1000000) { return `${(bitrate / 1000000).toFixed(1)} Mbps`; } else if (bitrate >= 1000) { return `${(bitrate / 1000).toFixed(0)} Kbps`; } return `${bitrate} bps`; }; // 格式化分辨率 const formatResolution = (width: number, height: number): string => { return `${width}×${height}`; }; // 格式化日期 const formatDate = (dateString: string): string => { return new Date(dateString).toLocaleDateString('zh-CN', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }; /** * 素材卡片组件 * 显示素材信息和切分片段 */ export const MaterialCard: React.FC = ({ material }) => { const { getMaterialSegments } = useMaterialStore(); const [segments, setSegments] = useState([]); const [showSegments, setShowSegments] = useState(false); const [loadingSegments, setLoadingSegments] = useState(false); // 获取素材类型图标 const getTypeIcon = (type: string) => { switch (type) { case 'Video': return ; case 'Audio': return ; case 'Image': return ; default: return ; } }; // 获取状态颜色 const getStatusColor = (status: string) => { switch (status) { case 'Completed': return 'text-green-600 bg-green-50'; case 'Processing': return 'text-blue-600 bg-blue-50'; case 'Failed': return 'text-red-600 bg-red-50'; case 'Pending': return 'text-yellow-600 bg-yellow-50'; default: return 'text-gray-600 bg-gray-50'; } }; // 加载切分片段 const loadSegments = async () => { if (segments.length > 0) { setShowSegments(!showSegments); return; } setLoadingSegments(true); try { const materialSegments = await getMaterialSegments(material.id); setSegments(materialSegments); setShowSegments(true); } catch (error) { console.error('加载切分片段失败:', error); } finally { setLoadingSegments(false); } }; // 打开文件所在文件夹 const openFileLocation = async (filePath: string) => { try { const { revealItemInDir } = await import('@tauri-apps/plugin-opener'); // 处理 Windows 路径格式,移除 \\?\ 前缀 let normalizedPath = filePath; if (normalizedPath.startsWith('\\\\?\\')) { normalizedPath = normalizedPath.substring(4); } console.log('打开文件位置:', normalizedPath); await revealItemInDir(normalizedPath); } catch (error) { console.error('打开文件位置失败:', error); // 如果 revealItemInDir 失败,尝试打开文件所在目录 try { const { openPath } = await import('@tauri-apps/plugin-opener'); // 获取文件所在目录 const pathParts = filePath.split(/[/\\]/); pathParts.pop(); // 移除文件名 const dirPath = pathParts.join('\\'); let normalizedDirPath = dirPath; if (normalizedDirPath.startsWith('\\\\?\\')) { normalizedDirPath = normalizedDirPath.substring(4); } console.log('尝试打开目录:', normalizedDirPath); await openPath(normalizedDirPath); } catch (fallbackError) { console.error('备用方法也失败:', fallbackError); alert('无法打开文件位置,请检查文件是否存在'); } } }; return (
{/* 素材基本信息 */}
{getTypeIcon(material.material_type)}

{material.name}

{material.processing_status}
{/* 素材详细信息 */}
{/* 基本信息 */}
{formatFileSize(material.file_size)}
{formatDate(material.created_at)}
{/* 元数据信息 */} {material.metadata !== 'None' && (
{/* 视频元数据 */} {material.metadata && typeof material.metadata === 'object' && 'Video' in material.metadata && (
视频信息
{formatTime(material.metadata.Video.duration)}
{formatResolution(material.metadata.Video.width, material.metadata.Video.height)}
{formatBitrate(material.metadata.Video.bitrate)}
{material.metadata.Video.fps} fps
{material.metadata.Video.codec}
{material.metadata.Video.has_audio && (
{material.metadata.Video.audio_codec || 'Audio'}
)}
)} {/* 音频元数据 */} {material.metadata && typeof material.metadata === 'object' && 'Audio' in material.metadata && (
音频信息
{formatTime(material.metadata.Audio.duration)}
{formatBitrate(material.metadata.Audio.bitrate)}
{material.metadata.Audio.sample_rate} Hz
{material.metadata.Audio.codec}
)} {/* 图片元数据 */} {material.metadata && typeof material.metadata === 'object' && 'Image' in material.metadata && (
图片信息
{formatResolution(material.metadata.Image.width, material.metadata.Image.height)}
{material.metadata.Image.format}
{material.metadata.Image.dpi && (
{material.metadata.Image.dpi} DPI
)}
)}
)} {/* 处理统计信息 */} {(material.scene_detection || material.segments.length > 0) && (
处理统计
{material.scene_detection && (
{material.scene_detection.total_scenes} 个场景
)} {material.segments.length > 0 && (
{material.segments.length} 个片段
)} {material.processed_at && (
已处理
)}
)}
{/* 切分片段控制 */} {material.material_type === 'Video' && material.processing_status === 'Completed' && (
{/* 切分片段列表 */} {showSegments && segments.length > 0 && (
切分片段 ({segments.length})
{segments.map((segment) => (
#{segment.segment_index + 1} {formatTime(segment.start_time)} - {formatTime(segment.end_time)} ({formatTime(segment.duration)})
))}
)} {showSegments && segments.length === 0 && (
暂无切分片段
)}
)}
); };