diff --git a/apps/desktop/src/components/MaterialSegmentView.tsx b/apps/desktop/src/components/MaterialSegmentView.tsx index e5532ee..920865c 100644 --- a/apps/desktop/src/components/MaterialSegmentView.tsx +++ b/apps/desktop/src/components/MaterialSegmentView.tsx @@ -106,6 +106,98 @@ const playVideoSegment = async (filePath: string, startTime: number, endTime: nu } }; +// 生成片段缩略图(使用首帧) +const generateSegmentThumbnail = async (segment: SegmentWithDetails): Promise => { + try { + // 使用片段的开始时间作为缩略图时间戳(首帧) + const timestamp = segment.segment.start_time; + + // 生成缩略图文件名 + const thumbnailFileName = `${segment.segment.id}_thumbnail.jpg`; + const thumbnailPath = `${segment.segment.file_path.replace(/\.[^/.]+$/, '')}_${thumbnailFileName}`; + + await invoke('generate_video_thumbnail', { + inputPath: segment.segment.file_path, + outputPath: thumbnailPath, + timestamp, + width: 160, + height: 120 + }); + + return thumbnailPath; + } catch (error) { + console.error('生成缩略图失败:', error); + return null; + } +}; + +// 缩略图显示组件 +interface ThumbnailDisplayProps { + segment: SegmentWithDetails; + thumbnailCache: Map; + setThumbnailCache: React.Dispatch>>; + generateSegmentThumbnail: (segment: SegmentWithDetails) => Promise; +} + +const ThumbnailDisplay: React.FC = ({ + segment, + thumbnailCache, + setThumbnailCache, + generateSegmentThumbnail +}) => { + const [loading, setLoading] = useState(false); + const [thumbnailUrl, setThumbnailUrl] = useState(null); + + useEffect(() => { + const loadThumbnail = async () => { + const segmentId = segment.segment.id; + + // 检查缓存 + if (thumbnailCache.has(segmentId)) { + setThumbnailUrl(thumbnailCache.get(segmentId) || null); + return; + } + + // 生成缩略图 + setLoading(true); + try { + const thumbnailPath = await generateSegmentThumbnail(segment); + if (thumbnailPath) { + // 转换为可访问的URL(这里需要根据实际情况调整) + const thumbnailUrl = `file://${thumbnailPath}`; + setThumbnailUrl(thumbnailUrl); + + // 更新缓存 + setThumbnailCache(prev => new Map(prev.set(segmentId, thumbnailUrl))); + } + } catch (error) { + console.error('加载缩略图失败:', error); + } finally { + setLoading(false); + } + }; + + loadThumbnail(); + }, [segment.segment.id, thumbnailCache, setThumbnailCache, generateSegmentThumbnail]); + + return ( +
+ {loading ? ( +
+ ) : thumbnailUrl ? ( + 视频缩略图 setThumbnailUrl(null)} + /> + ) : ( +
+ ); +}; + /** * 素材片段管理组件 - 多条件检索标签页风格 */ @@ -116,6 +208,7 @@ export const MaterialSegmentView: React.FC = ({ projec const [searchTerm, setSearchTerm] = useState(''); const [selectedClassification, setSelectedClassification] = useState('全部'); const [selectedModel, setSelectedModel] = useState('全部'); + const [thumbnailCache, setThumbnailCache] = useState>(new Map()); // 加载数据 const loadSegmentView = async () => { @@ -240,9 +333,12 @@ export const MaterialSegmentView: React.FC = ({ projec
{/* 缩略图 */} -
-
+ {/* 内容信息 */}