62a974a836
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m19s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m45s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 6m1s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Failing after 3m43s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 7m39s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
146 lines
4.8 KiB
Python
Executable File
146 lines
4.8 KiB
Python
Executable File
"""补写 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())
|