"""补写 generated_videos 记录 — 修复一键生成场景下 project_id 为空导致未写表的问题. 背景:#535 之前的一键生成场景,由于 edit_plan.project_id 为空, _finalize_render_success 中 `if generation_task_id and project_id:` 判断不通过, 导致渲染成功的视频没有写入 generated_videos 表,成片库看不到。 本脚本扫描所有 status=completed 的 generation_task, 如果没有对应的 generated_videos 记录,则从 plan.config.rendered_url 补写。 用法: PYTHONPATH="apps/worker:apps/api:packages" python scripts/backfill_generated_videos.py 安全说明: - 只读事务扫描,补写前检查是否已存在,不会重复写入 - 失败不中断,继续处理下一个 """ from __future__ import annotations import logging import sys from datetime import datetime, timezone from uuid import uuid4 # 配置日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", ) logger = logging.getLogger(__name__) def main() -> int: from sqlalchemy import exists from sqlalchemy.orm import Session from packages.adapters.sqlalchemy_impl.models import ( EditPlanModel, GeneratedVideoModel, GenerationTaskModel, ) from packages.adapters.sqlalchemy_impl.session import get_db_session db: Session = next(get_db_session()) try: # 1. 查询所有 status=completed 的 generation_task completed_tasks = ( db.query(GenerationTaskModel) .filter( GenerationTaskModel.status == "completed", ) .all() ) logger.info("找到 %d 个已完成的生成任务", len(completed_tasks)) fixed_count = 0 skipped_count = 0 failed_count = 0 for task in completed_tasks: task_id = task.id # 2. 检查是否已有 generated_videos 记录 exists_video = db.query(exists().where(GeneratedVideoModel.generation_task_id == task_id)).scalar() if exists_video: skipped_count += 1 continue # 3. 从 source_edit_plan_id 找到对应的 edit_plan,取 rendered_url if not task.source_edit_plan_id: logger.warning("任务 %s 无 source_edit_plan_id,跳过", task_id) skipped_count += 1 continue plan = ( db.query(EditPlanModel) .filter( EditPlanModel.id == task.source_edit_plan_id, ) .first() ) if not plan: logger.warning("任务 %s 对应的剪辑计划不存在,跳过", task_id) skipped_count += 1 continue # 从 config 中取 rendered_url config = plan.config or {} rendered_url = config.get("rendered_url", "") rendered_storage_key = config.get("rendered_storage_key", "") if not rendered_url: logger.warning("任务 %s 的剪辑计划无 rendered_url,跳过", task_id) skipped_count += 1 continue # 4. 补写 generated_videos 记录 try: video_id = uuid4().hex video = GeneratedVideoModel( id=video_id, project_id=task.project_id or "", generation_task_id=task_id, name=f"generated-{task_id[:8]}.mp4", file_url=rendered_url, storage_key=rendered_storage_key, file_size=0, duration=0.0, width=0, height=0, fps=0.0, status="completed", generation_params={"mode": "backfill", "plan_id": plan.id}, review_status="pending", created_at=task.completed_at or datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(video) db.commit() fixed_count += 1 logger.info("已补写视频记录: task_id=%s video_id=%s url=%s", task_id, video_id, rendered_url[:80]) except Exception as e: db.rollback() failed_count += 1 logger.error("补写视频记录失败: task_id=%s error=%s", task_id, e) logger.info( "补写完成:共 %d 个已完成任务,补写 %d 条,跳过 %d 条,失败 %d 条", len(completed_tasks), fixed_count, skipped_count, failed_count, ) return 0 if failed_count == 0 else 1 finally: db.close() if __name__ == "__main__": sys.exit(main())