diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 21dff9b..4086c04 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -569,15 +569,15 @@ function extractVideoFrames(filePath: string, maxFrames: number, maxWidth: numbe }); child.stderr?.on('data', (data: Buffer) => { ffmpegStderr += data.toString(); - // 实时解析进度 frame=\d+ - const progressMatch = ffmpegStderr.match(/frame=\s*(\d+)/g); - if (progressMatch) { - const last = progressMatch[progressMatch.length - 1]; + // 实时推送进度到渲染进程(每秒一次,覆盖更新) + const match = ffmpegStderr.match(/frame=\s*(\d+)/g); + if (match) { + const last = match[match.length - 1]; const currentFrame = parseInt(last.replace(/\D/g, '')); if (currentFrame > lastProgressFrame && Date.now() - lastProgressTime > 1000) { lastProgressFrame = currentFrame; lastProgressTime = Date.now(); - sendLog('debug', `🎬 提取进度`, `${currentFrame} 帧`); + mainWindow?.webContents.send('video:progress', { current: currentFrame }); } } }); diff --git a/src/main/preload.ts b/src/main/preload.ts index 9004e2a..4e5091d 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -127,6 +127,12 @@ contextBridge.exposeInMainWorld('metonaDesktop', { }, video: { extractFrames: (filePath: string, options?: { maxFrames?: number; maxWidth?: number; maxSize?: number }) => - ipcRenderer.invoke('video:extractFrames', filePath, options) + ipcRenderer.invoke('video:extractFrames', filePath, options), + onProgress: (callback: (data: { current: number }) => void) => { + ipcRenderer.on('video:progress', (_: unknown, data: { current: number }) => callback(data)); + }, + removeProgressListener: () => { + ipcRenderer.removeAllListeners('video:progress'); + } } }); diff --git a/src/renderer/components/input-area.ts b/src/renderer/components/input-area.ts index e2cacf1..2f1ddf5 100644 --- a/src/renderer/components/input-area.ts +++ b/src/renderer/components/input-area.ts @@ -251,28 +251,43 @@ async function handleVideoPath(filePath: string): Promise { } const name = filePath.split(/[/\\]/).pop() || filePath; - appendSystemMessage(`🎬 正在提取视频帧 (1fps): ${name}...`); + + // 先显示提取中的预览卡片 + pendingVideoMeta = { fileName: name, duration: 0, width: 0, height: 0 }; + renderVideoPreview(); + + // 监听提取进度,实时更新卡片 + let progressEl: HTMLElement | null = null; + bridge.video.onProgress(({ current }) => { + if (!progressEl) progressEl = videoPreviewEl.querySelector('.video-meta'); + if (progressEl) progressEl.textContent = `提取中 · ${current}帧`; + }); try { const result = await bridge.video.extractFrames(filePath, { maxFrames: 600, maxWidth: 512 }); + bridge.video.removeProgressListener(); + if (!result.success) { + pendingVideoFrames = []; + pendingVideoMeta = null; + renderVideoPreview(); appendSystemMessage(`❌ 视频帧提取失败: ${result.error}`); return; } - const frames = result.frames!; - const info = result.videoInfo!; - - // 存入视频专用存储 - pendingVideoFrames = frames; - pendingVideoMeta = { fileName: name, duration: info.duration, width: info.width, height: info.height }; + pendingVideoFrames = result.frames!; + pendingVideoMeta = { fileName: name, ...result.videoInfo! }; logInfo(`视频帧提取完成: ${name}`, - `${frames.length} 帧 (1fps, ${info.duration.toFixed(0)}s, ${info.width}x${info.height})`); - appendSystemMessage(`✅ ${name}: 已提取 ${frames.length} 帧 (1fps 采样)`); + `${pendingVideoFrames.length} 帧 (1fps, ${pendingVideoMeta.duration.toFixed(0)}s)`); + appendSystemMessage(`✅ ${name}: 已提取 ${pendingVideoFrames.length} 帧 (1fps 采样)`); renderVideoPreview(); } catch (err) { + bridge.video.removeProgressListener(); + pendingVideoFrames = []; + pendingVideoMeta = null; + renderVideoPreview(); logError('视频帧提取异常', (err as Error).message); appendSystemMessage(`❌ 视频处理失败: ${(err as Error).message}`); } diff --git a/src/renderer/types.d.ts b/src/renderer/types.d.ts index 9400dd2..2b2061b 100644 --- a/src/renderer/types.d.ts +++ b/src/renderer/types.d.ts @@ -278,6 +278,8 @@ export interface MetonaDesktopAPI { videoInfo?: { duration: number; width: number; height: number }; error?: string; }>; + onProgress: (callback: (data: { current: number }) => void) => void; + removeProgressListener: () => void; }; }