-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/import subtitle from stream #221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0d93d76
629af64
0da82d9
09295a5
7796059
366924c
953ef09
4dc9eec
ed0c685
d7e5604
63d59d5
398ea66
7a00ab6
7d3d9a3
a85b241
7075f83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,18 @@ const StyledComponent = styled.div` | |
| # Test | ||
|
|
||
| - 使用 vitest 作为测试框架 | ||
| - **重要**:全局测试配置文件 `tests/setup.ts` 中 mock 了 `node:fs` 和 `node:fs/promises` 模块,这会影响需要真实文件系统操作的测试 | ||
| - 对于需要真实文件系统操作的测试(如文件清理、文件读写等),必须在测试文件开头使用 `vi.unmock('node:fs')` 和 `vi.unmock('node:fs/promises')` 来取消全局 mock | ||
| - 示例: | ||
|
|
||
| ```typescript | ||
| // 在测试文件开头 | ||
| vi.unmock('node:fs') | ||
| vi.unmock('node:fs/promises') | ||
|
|
||
| // 然后导入真实的 fs | ||
| import * as fs from 'fs' | ||
| ``` | ||
|
|
||
| # Package Management | ||
|
|
||
|
|
@@ -82,10 +94,32 @@ const StyledComponent = styled.div` | |
| - 包管理器工具请使用 pnpm | ||
| - logger 的使用例子: `logger.error('Error in MediaClock listener:', { error: error })`, 第二参数必须接收为 `{}` | ||
|
|
||
| ## Resource Management & Cleanup | ||
|
|
||
| - **临时文件清理规范**:所有生成临时文件的服务都应实现集中清理机制,参考 `FFmpegDownloadService.cleanupTempFiles()` 和 `SubtitleExtractorService.cleanupTempFiles()` 的实现模式 | ||
| - 临时文件清理的最佳实践: | ||
| 1. 在服务中实现 `cleanupTempFiles()` 方法,扫描并删除符合特定模式的临时文件 | ||
| 2. 在 `src/main/index.ts` 的 `app.on('will-quit')` 事件中调用清理方法 | ||
| 3. 通过 IPC 通道暴露清理接口,允许渲染进程手动触发清理(如 `SubtitleExtractor_CleanupTemp`) | ||
| 4. 使用正则表达式精确匹配临时文件模式,避免误删其他文件 | ||
| 5. 包含完整的错误处理和日志记录,跳过正在使用或无法删除的文件 | ||
| - 临时文件命名规范:使用 `<prefix>_<timestamp>_<random>.<ext>` 格式(如 `subtitle_1234567890_abc123.srt`),便于模式匹配和清理 | ||
|
|
||
|
Comment on lines
+97
to
+107
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 给出“临时文件”匹配的正则示例,降低实现偏差 当前只描述了命名约定,建议补充一个推荐正则模板,便于一致实现与审查。 示例补充: ^subtitle_\d{10}_[a-z0-9]{6}\.(srt|vtt|ass|ssa|sup)$配合前缀可扩展: ^(subtitle|ffmpeg|probe)_\d{10}_[a-z0-9]{6}\.[a-z0-9]+$并在文档中强调排除锁文件/进行中文件(如 *.part, *.lock)。 🤖 Prompt for AI Agents |
||
| ## File System Operations | ||
|
|
||
| - **禁止使用同步文件操作**:在主进程中必须使用异步文件 API(`fs.promises.*`),避免阻塞事件循环导致应用冻结 | ||
| - 文件操作最佳实践: | ||
| - ❌ 错误示例:`fs.readdirSync()`, `fs.unlinkSync()`, `fs.readFileSync()` | ||
| - ✅ 正确示例:`await fs.promises.readdir()`, `await fs.promises.unlink()`, `await fs.promises.readFile()` | ||
| - 批量文件操作使用 `Promise.all()` 并行执行,提升性能 | ||
| - 所有文件操作方法应声明为 `async` 并返回 `Promise` | ||
|
|
||
| ## Issues & Solutions | ||
|
|
||
| 1. DictionaryPopover 组件主题兼容性问题已修复:将硬编码的深色主题颜色(白色文字、深色背景)替换为 Ant Design CSS 变量(如 `var(--ant-color-text)`、`var(--ant-color-bg-elevated)`),实现浅色和深色主题的自动适配,包括文字颜色、背景色、边框、滚动条和交互状态的完整主题化。 | ||
|
|
||
| 2. SubtitleExtractorService 临时文件清理机制已实现:在应用退出时自动清理系统临时目录中的字幕临时文件,防止磁盘空间浪费;支持通过 IPC 通道手动触发清理。 | ||
|
|
||
| ## Task Master AI Instructions | ||
|
|
||
| **Import Task Master's development workflow commands and guidelines, treat as if import is in the main CLAUDE.md file.** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -182,6 +182,16 @@ if (!app.requestSingleInstanceLock()) { | |
| }) | ||
|
|
||
| app.on('will-quit', async () => { | ||
| // Cleanup temporary subtitle files | ||
| try { | ||
| const SubtitleExtractorService = (await import('./services/SubtitleExtractorService')).default | ||
| const subtitleExtractorService = new SubtitleExtractorService() | ||
| await subtitleExtractorService.cleanupTempFiles() | ||
| logger.info('Temporary subtitle files cleaned up') | ||
| } catch (error) { | ||
| logger.error('Error cleaning up temporary subtitle files:', { error }) | ||
| } | ||
|
Comment on lines
+185
to
+193
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 已 await 清理:符合先前建议;可选增加超时护栏 will-quit 中已 await 清理,OK。为防极端阻塞退出,可包一层超时(如 2s)或在 before-quit 执行并设置兜底。当前改动可先合。 🤖 Prompt for AI Agents |
||
|
|
||
| // Close database connections | ||
| try { | ||
| const { closeDatabase } = await import('./db/index') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ import { mediaServerService } from './services/MediaServerService' | |
| import NotificationService from './services/NotificationService' | ||
| import { pythonVenvService } from './services/PythonVenvService' | ||
| import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService' | ||
| import SubtitleExtractorService from './services/SubtitleExtractorService' | ||
| import { themeService } from './services/ThemeService' | ||
| import { uvBootstrapperService } from './services/UvBootstrapperService' | ||
| import { calculateDirectorySize, getResourcePath } from './utils' | ||
|
|
@@ -41,18 +42,18 @@ const fileManager = new FileStorage() | |
| const dictionaryService = new DictionaryService() | ||
| const ffmpegService = new FFmpegService() | ||
| const mediaParserService = new MediaParserService() | ||
| const subtitleExtractorService = new SubtitleExtractorService() | ||
|
|
||
| /** | ||
| * Register all IPC handlers used by the main process. | ||
| * Registers all ipcMain handlers used by the main process. | ||
| * | ||
| * Initializes updater and notification services and wires a comprehensive set of ipcMain.handle | ||
| * handlers exposing application control, system info, theming/language, spell-check, cache and | ||
| * file operations, dictionary lookups, FFmpeg operations, shortcut management, and database DAOs | ||
| * (Files, VideoLibrary, SubtitleLibrary) to renderer processes. | ||
| * Exposes application control, system information, theming/language, spell-check, cache and file | ||
| * operations, media tooling (FFmpeg, media parser, subtitle extraction), shortcuts, and database | ||
| * DAOs to renderer processes via ipcMain handlers. | ||
| * | ||
| * This function has side effects: it registers handlers on ipcMain, may attach an app 'before-quit' | ||
| * listener when requested, and mutates Electron state (e.g., app paths, sessions). Call from the | ||
| * Electron main process once (typically during app initialization). | ||
| * This function mutates Electron state (registers handlers on ipcMain, may add/remove an app | ||
| * 'before-quit' listener, and updates sessions/paths) and must be invoked once from the main | ||
| * process during application initialization. | ||
| */ | ||
| export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { | ||
| const appUpdater = new AppUpdater(mainWindow) | ||
|
|
@@ -681,14 +682,45 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { | |
| } | ||
| ) | ||
|
|
||
| // 字幕轨道相关 IPC 处理程序 / Subtitle stream-related IPC handlers | ||
| ipcMain.handle(IpcChannel.Media_GetSubtitleStreams, async (_, inputPath: string) => { | ||
| return await mediaParserService.getSubtitleStreams(inputPath) | ||
| }) | ||
|
mkdir700 marked this conversation as resolved.
|
||
|
|
||
| ipcMain.handle( | ||
| IpcChannel.Media_ExtractSubtitle, | ||
| async ( | ||
| _, | ||
| options: { | ||
| videoPath: string | ||
| streamIndex: number | ||
| outputFormat?: string | ||
| subtitleCodec?: string | ||
| } | ||
| ) => { | ||
| return await subtitleExtractorService.extractSubtitle({ | ||
| videoPath: options.videoPath, | ||
| streamIndex: options.streamIndex, | ||
| outputFormat: (options.outputFormat as 'srt' | 'ass' | 'vtt') || 'srt', | ||
| subtitleCodec: options.subtitleCodec | ||
| }) | ||
| } | ||
| ) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| ipcMain.handle(IpcChannel.SubtitleExtractor_CleanupTemp, async () => { | ||
| const count = await subtitleExtractorService.cleanupTempFiles() | ||
| logger.info('触发字幕临时文件清理', { count }) | ||
| return count | ||
| }) | ||
|
|
||
| // 文件系统相关 IPC 处理程序 / File system-related IPC handlers | ||
| ipcMain.handle(IpcChannel.Fs_CheckFileExists, async (_, filePath: string) => { | ||
| try { | ||
| const exists = fs.existsSync(filePath) | ||
| logger.debug('检查文件存在性', { filePath, exists }) | ||
| return exists | ||
| await fs.promises.access(filePath, fs.constants.F_OK) | ||
| logger.debug('检查文件存在性', { filePath, exists: true }) | ||
| return true | ||
| } catch (error) { | ||
| logger.error('检查文件存在性时出错', { filePath, error }) | ||
| logger.debug('检查文件存在性', { filePath, exists: false }) | ||
| return false | ||
| } | ||
|
Comment on lines
717
to
725
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Fs_CheckFileExists 可提前过滤空路径以减少无意义 I/O 微优化:空字符串/非字符串直接返回 false 并打点一次 debug。 ipcMain.handle(IpcChannel.Fs_CheckFileExists, async (_, filePath: string) => {
- try {
+ if (!filePath || typeof filePath !== 'string') {
+ logger.debug('检查文件存在性', { filePath, exists: false, reason: 'invalid-arg' })
+ return false
+ }
+ try {
await fs.promises.access(filePath, fs.constants.F_OK)
logger.debug('检查文件存在性', { filePath, exists: true })
return true |
||
| }) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.