"""修复僵尸批量任务脚本:把误标 COMPLETED 但仍有未结束视频的任务置回 QUEUED。 僵尸状态:`_run_job` 在视频收尾前就把任务置 COMPLETED,留下"N 个视频待处理 却已完成"的假完成;引擎只拾取 QUEUED,剩余视频永久无人处理。代码已修复 (置 COMPLETED 前校验无未结束明细 + 重启恢复把这类任务放回队列),本脚本用于 修复**历史遗留**数据(如 batch_351833b7d446:COMPLETED/done=0 但仍有 1 个 PENDING)。 修复方式:把 COMPLETED 且仍有非终态(PENDING/RUNNING/PAUSED)明细的任务置回 QUEUED 并清空误导的 progress,保留 SKIPPED/COMPLETED/FAILED 明细与 run 记录; 批量引擎重启后会重新拾起,从剩余视频续跑,全部处理完才置 COMPLETED。 安全约束:只改"COMPLETED 且存在未结束明细"的任务,正常完成的任务不动。 默认只打印预览,确认后加 --apply 实际写入。 """ from __future__ import annotations import sys from datetime import datetime, timezone from pathlib import Path from wov_app.db import Database # 未结束的明细状态:出现任一个就不能算任务完成。 UNFINISHED_STATUSES = ("PENDING", "RUNNING", "PAUSED") def _now_iso() -> str: """返回当前 UTC 时间的 ISO 格式字符串。""" return datetime.now(timezone.utc).isoformat() def find_zombie_jobs(db: Database) -> list[tuple[dict, list[dict]]]: """返回全部僵尸任务及其未结束明细(COMPLETED 但仍有未结束视频)。""" zombies: list[tuple[dict, list[dict]]] = [] for job in db.list_batch_jobs(limit=1000): if job["status"] != "COMPLETED": continue unfinished = [ v for v in db.list_batch_videos(str(job["id"])) if v["status"] in UNFINISHED_STATUSES ] if unfinished: zombies.append((job, unfinished)) return zombies def main(db_path: str, apply: bool = False) -> None: """诊断(默认)或修复(--apply)僵尸批量任务。""" db = Database(Path(db_path)) print(f"数据库: {db_path}\n") zombies = find_zombie_jobs(db) if not zombies: print(" [无] 没有 COMPLETED 但仍含未结束视频的批量任务") return for job, unfinished in zombies: job_id = str(job["id"]) videos = db.list_batch_videos(job_id) completed = sum(1 for v in videos if v["status"] == "COMPLETED") print(f" [待修] {job_id}: status={job['status']} → QUEUED, " f"COMPLETED={completed}, 未结束={len(unfinished)}" f"({', '.join(Path(v['video_path']).name for v in unfinished[:3])}…)") if apply: db.update_batch_job( job_id, status="QUEUED", progress=0, total=int(job["total"] or 0) or len(unfinished), done=completed, failed=int(job["failed"] or 0), current_video=None, error=None, updated_at=_now_iso(), ) print(f" ✓ 已置回 QUEUED(引擎将续跑剩余 {len(unfinished)} 个视频)") if not apply: print("\n以上为预览。确认无误后加 --apply 实际修复。") if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else "data/wov.db", "--apply" in sys.argv)