refactor: 视频提取进度实时更新卡片,不再刷日志面板
- 主进程通过 video:progress IPC 推送当前帧数 - 前端监听进度事件,直接更新视频芯片的 meta 文本 - 日志面板只保留开始和完成两条记录
This commit is contained in:
+5
-5
@@ -569,15 +569,15 @@ function extractVideoFrames(filePath: string, maxFrames: number, maxWidth: numbe
|
|||||||
});
|
});
|
||||||
child.stderr?.on('data', (data: Buffer) => {
|
child.stderr?.on('data', (data: Buffer) => {
|
||||||
ffmpegStderr += data.toString();
|
ffmpegStderr += data.toString();
|
||||||
// 实时解析进度 frame=\d+
|
// 实时推送进度到渲染进程(每秒一次,覆盖更新)
|
||||||
const progressMatch = ffmpegStderr.match(/frame=\s*(\d+)/g);
|
const match = ffmpegStderr.match(/frame=\s*(\d+)/g);
|
||||||
if (progressMatch) {
|
if (match) {
|
||||||
const last = progressMatch[progressMatch.length - 1];
|
const last = match[match.length - 1];
|
||||||
const currentFrame = parseInt(last.replace(/\D/g, ''));
|
const currentFrame = parseInt(last.replace(/\D/g, ''));
|
||||||
if (currentFrame > lastProgressFrame && Date.now() - lastProgressTime > 1000) {
|
if (currentFrame > lastProgressFrame && Date.now() - lastProgressTime > 1000) {
|
||||||
lastProgressFrame = currentFrame;
|
lastProgressFrame = currentFrame;
|
||||||
lastProgressTime = Date.now();
|
lastProgressTime = Date.now();
|
||||||
sendLog('debug', `🎬 提取进度`, `${currentFrame} 帧`);
|
mainWindow?.webContents.send('video:progress', { current: currentFrame });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+7
-1
@@ -127,6 +127,12 @@ contextBridge.exposeInMainWorld('metonaDesktop', {
|
|||||||
},
|
},
|
||||||
video: {
|
video: {
|
||||||
extractFrames: (filePath: string, options?: { maxFrames?: number; maxWidth?: number; maxSize?: number }) =>
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -251,28 +251,43 @@ async function handleVideoPath(filePath: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = filePath.split(/[/\\]/).pop() || filePath;
|
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 {
|
try {
|
||||||
const result = await bridge.video.extractFrames(filePath, { maxFrames: 600, maxWidth: 512 });
|
const result = await bridge.video.extractFrames(filePath, { maxFrames: 600, maxWidth: 512 });
|
||||||
|
bridge.video.removeProgressListener();
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
pendingVideoFrames = [];
|
||||||
|
pendingVideoMeta = null;
|
||||||
|
renderVideoPreview();
|
||||||
appendSystemMessage(`❌ 视频帧提取失败: ${result.error}`);
|
appendSystemMessage(`❌ 视频帧提取失败: ${result.error}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frames = result.frames!;
|
pendingVideoFrames = result.frames!;
|
||||||
const info = result.videoInfo!;
|
pendingVideoMeta = { fileName: name, ...result.videoInfo! };
|
||||||
|
|
||||||
// 存入视频专用存储
|
|
||||||
pendingVideoFrames = frames;
|
|
||||||
pendingVideoMeta = { fileName: name, duration: info.duration, width: info.width, height: info.height };
|
|
||||||
|
|
||||||
logInfo(`视频帧提取完成: ${name}`,
|
logInfo(`视频帧提取完成: ${name}`,
|
||||||
`${frames.length} 帧 (1fps, ${info.duration.toFixed(0)}s, ${info.width}x${info.height})`);
|
`${pendingVideoFrames.length} 帧 (1fps, ${pendingVideoMeta.duration.toFixed(0)}s)`);
|
||||||
appendSystemMessage(`✅ ${name}: 已提取 ${frames.length} 帧 (1fps 采样)`);
|
appendSystemMessage(`✅ ${name}: 已提取 ${pendingVideoFrames.length} 帧 (1fps 采样)`);
|
||||||
|
|
||||||
renderVideoPreview();
|
renderVideoPreview();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
bridge.video.removeProgressListener();
|
||||||
|
pendingVideoFrames = [];
|
||||||
|
pendingVideoMeta = null;
|
||||||
|
renderVideoPreview();
|
||||||
logError('视频帧提取异常', (err as Error).message);
|
logError('视频帧提取异常', (err as Error).message);
|
||||||
appendSystemMessage(`❌ 视频处理失败: ${(err as Error).message}`);
|
appendSystemMessage(`❌ 视频处理失败: ${(err as Error).message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -278,6 +278,8 @@ export interface MetonaDesktopAPI {
|
|||||||
videoInfo?: { duration: number; width: number; height: number };
|
videoInfo?: { duration: number; width: number; height: number };
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
onProgress: (callback: (data: { current: number }) => void) => void;
|
||||||
|
removeProgressListener: () => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user