feat: 批量处理重做——视频旁已有字幕即跳过、产物对齐 CN 命名并清理过程文件
- 创建批量任务时一次性定位视频:视频所在目录存在文件名含视频名的字幕文件 (.srt/.ass/.ssa/.vtt)直接记 SKIPPED,不触发流水线;运行时只消费已定位 的明细,不再重新扫描文件夹。 - 视频完成后把最终产物放到视频旁,命名对齐媒体库约定:中文字幕存为 <视频名>.CN.srt、双目字幕存为 <视频名>.CN_dual_eye.ass;其余扩展名产物 保留原文件名。 - 收尾删除 run 记录与过程工作空间;工作空间改到应用私有目录 storage/batch/<job_id>/<bv_id>/,与用户媒体库隔离,防止媒体库把切片数据 当视频入库。 - 批量 API 产物清单/下载改为解析视频旁字幕文件,旧版 batch.done.json 语义 别名保持兼容;删除任务时清理私有工作空间。 - 前端说明与创建提示同步;测试按新语义重写并补覆盖(278 passed,100% 行覆盖率)。
This commit is contained in:
@@ -276,31 +276,38 @@ http://127.0.0.1:8000/docs API 文档
|
||||
(线程池 `on_progress` 回调,每任务完成触发);
|
||||
- whisper:分块转写打印"分块 X/Y 完成 offset=... 耗时 Zs (Nx 实时, 累计 ...s)";
|
||||
- frame-extract:ffmpeg `-progress` 输出解析 `frame=N`,打印"抽帧进度 X/Y 帧 (Z 帧/s)"。
|
||||
## 文件夹批量处理(2026-08)
|
||||
## 文件夹批量处理(2026-09 更新)
|
||||
|
||||
本地版核心能力:**不把视频上传到工作目录**,直接读取用户所选文件夹下的全部
|
||||
视频,逐个执行所选流水线。入口为批量处理页(`web/batch.html`,导航"批量处理"),
|
||||
后端为 `src/wov_app/batch.py` 的 `BatchWorker`(单线程轮询线程,处理
|
||||
`source=batch` 的运行,与主调度器互不抢占)与 `routers/batch.py`。
|
||||
- **路径选择(2026-08 起不用手敲路径)**:批量页点击"选择文件夹…"按钮弹出
|
||||
目录树选择器(懒加载),选完回填只读路径框。浏览器拿不到所选文件夹的绝对
|
||||
路径,因此由**本地后端**提供目录浏览:`GET /api/batch/roots`(Windows 盘符 /
|
||||
POSIX 根 + 家目录)、`GET /api/batch/dirs?path=`(列直接子目录,隐藏目录
|
||||
过滤;不存在/不可读返回空列表不报 500)。只暴露目录名,不返回文件内容。
|
||||
- **数据落盘**:每个视频的中间态(`runs/<run_id>/steps/...`)与最终产物都放在
|
||||
**视频所在目录的同名文件夹**(`movie.mp4` → `movie/`,去掉扩展名,
|
||||
`work_dir_for` 推导);最终产物在任务完成后从节点产物目录**复制**到同名
|
||||
文件夹根目录,并写 `batch.done.json` 完成标记(记录 workflow_id 与产物文件名)。
|
||||
- **已处理过的不再处理**:同名文件夹已有**同工作流**完成标记且产物文件齐全 →
|
||||
直接 SKIPPED;不同工作流的标记不互相误判(换流水线会重新处理)。
|
||||
- **任务参数**:`POST /api/batch/jobs {folder, workflow_id, recursive}` 创建批量
|
||||
任务(校验文件夹/已发布工作流/有版本/至少一个视频,失败 422);任务入
|
||||
`batch_jobs` 表,每个视频一行 `batch_videos`(PENDING/RUNNING/PAUSED/
|
||||
COMPLETED/FAILED/SKIPPED)。可用任意已发布流水线(demo / zh-direct /
|
||||
ocr-subtitle 等),前端下拉选择。
|
||||
- **执行复用**:每个视频创建一个 `source=batch` 的 run(`input_uri` 直接指向
|
||||
本地视频路径),用 per-video 的 `WorkflowScheduler` 实例(storage=同名文件夹)
|
||||
执行——完整复用 DAG 拓扑执行、产物表登记与**断点续跑**逻辑。
|
||||
- **路径选择**:批量页点击"选择文件夹…"按钮弹出目录树选择器(懒加载),选完
|
||||
回填只读路径框。浏览器拿不到所选文件夹的绝对路径,因此由**本地后端**提供目录
|
||||
浏览:`GET /api/batch/roots`(Windows 盘符 / POSIX 根 + 家目录)、
|
||||
`GET /api/batch/dirs?path=`(列直接子目录,隐藏目录过滤;不存在/不可读返回
|
||||
空列表不报 500)。只暴露目录名,不返回文件内容。
|
||||
- **创建任务时一次性定位(2026-09 起)**:`POST /api/batch/jobs {folder,
|
||||
workflow_id, recursive}` 只扫描一次文件夹并把每个视频登记为 `batch_videos`
|
||||
明细(PENDING/RUNNING/PAUSED/COMPLETED/FAILED/SKIPPED)。**视频所在目录
|
||||
(视频旁)若已存在文件名含视频名的字幕文件**(`.srt/.ass/.ssa/.vtt`,
|
||||
`list_sidecar_subtitles` 判定,如 `movie.CN.srt`、`movie.CN_dual_eye.ass`),
|
||||
说明该视频已有字幕,创建即记 **SKIPPED**——不为它触发任何流水线。运行时
|
||||
`BatchWorker` **只消费这批已定位的明细,不再重新扫描文件夹**(运行期间新增/
|
||||
删除的视频不会改变本次任务的范围)。校验失败(文件夹不存在/未发布工作流/
|
||||
无版本/一个视频都没有)返回 422。
|
||||
- **产物放在视频旁**:每个视频处理完成后,把工作流 `final_outputs` 对应的最终
|
||||
产物文件(字幕流水线即中文 `.srt` 与双目 `.ass`)**复制一份到视频所在目录**,
|
||||
与 .mp4 放在一起(`_place_products`)。文件名**对齐媒体库既有约定**:中文字幕
|
||||
存为 `<视频名>.CN.srt`、双目字幕存为 `<视频名>.CN_dual_eye.ass`(稳定无时间戳,
|
||||
`_sidecar_product_name` 映射,其余扩展名产物保留原文件名;同名目标直接覆盖)。
|
||||
文件名含视频主名,下次批量扫描会命中"已有字幕"规则直接跳过该视频。
|
||||
- **过程文件清理(2026-09 起)**:视频收尾完成后删除该视频的整个工作空间与
|
||||
run 记录(音频/分块/帧图/节点产物不留残),防止媒体库把切片数据当视频入库。
|
||||
工作空间位于**应用私有目录** `data/storage/batch/<job_id>/<bv_id>/`
|
||||
(不再放视频同名文件夹),与用户视频库天然隔离;暂停/失败的视频保留工作空间
|
||||
以便断点续跑。per-video 的 `WorkflowScheduler` 实例以该目录为 storage——
|
||||
完整复用 DAG 拓扑执行、产物表登记与**断点续跑**逻辑。
|
||||
- **暂停/继续**:`POST /api/batch/jobs/{id}/pause` 把任务置 PAUSED 并暂停当前
|
||||
run(写 `paused.flag`;whisper **分块间**检查、OCR 逐帧检查后中止,当前节点
|
||||
执行完才停);`resume` 恢复 QUEUED,引擎从断点继续——PAUSED 视频的 run 显式
|
||||
@@ -313,13 +320,15 @@ http://127.0.0.1:8000/docs API 文档
|
||||
execute_run 从产物表跳过已完成节点、只重跑失败节点——extract/ocr 等长耗时
|
||||
成果不浪费;配合 llm-filter/OCR 的节点级断点存档,失败节点自身也只重判未完成
|
||||
条目。前端对"部分失败"(COMPLETED 且 failed>0)用红色徽章醒目标示。
|
||||
- **产物下载**:`GET /api/batch/jobs/{id}/videos/{vid}/download?alias=result`
|
||||
从完成标记解析产物文件并返回(只读同名文件夹根目录)。
|
||||
- **孤儿清理保护**:`source=batch` 的运行**跳过**自动清理——产物不在主存储
|
||||
目录下,普通孤儿逻辑会误删记录并连带删除 `input_uri` 的父目录(用户的整个
|
||||
视频文件夹)。
|
||||
- **删除任务**:`DELETE /api/batch/jobs/{id}` 只清理数据库记录(含关联 run),
|
||||
磁盘上的同名文件夹与产物属于用户数据,保留不删。
|
||||
- **产物下载**:`GET /api/batch/jobs/{id}/videos/{vid}/download?alias=<文件名>`
|
||||
解析并返回视频旁的字幕文件;旧版 `batch.done.json` 完成标记里的语义别名
|
||||
(位于旧 work_dir)仍兼容可下载。详情/创建响应里每个视频的 `finals` 合并上述
|
||||
两处来源。
|
||||
- **孤儿清理保护**:`source=batch` 的运行**跳过**自动清理——其 run 位于私有
|
||||
`storage/batch/...` 下,普通孤儿逻辑会误判删除,且 `_remove_run` 还会删除
|
||||
`input_uri` 的父目录(用户的整个视频文件夹)。
|
||||
- **删除任务**:`DELETE /api/batch/jobs/{id}` 清理数据库记录(含关联 run)与
|
||||
应用私有工作空间残留;视频旁已放置的产物属于用户数据,保留不删。
|
||||
- **环境变量**:`WOV_BATCH_ENABLED`(默认 1)、`WOV_BATCH_INTERVAL_SECONDS`
|
||||
(默认 1.0)。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user