0cb67afd61
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 40s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m34s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m46s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m54s
AI Code Review / AI Code Review (pull_request) Failing after 2m10s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m16s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m46s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
核心改造:
- 有source_edit_plan_id时,Worker直接调用RenderAdapter.render_plan(plan_id)
从数据库加载edit_plan+clips渲染,不再_download_all_assets+_build_plan_and_clips
- 新增_sync_task_config_to_plan:将title_config/bgm_config/分辨率同步到plan.config,
配音下载后通过voiceover_audio_path传给RenderAdapter
- 无source_edit_plan_id时保留旧路径(标记DEPRECATED)
- RenderAdapter.render_plan新增voiceover_audio_path参数透传给_do_render
新增API:
- PUT /templates/{id}/editor/clips 批量替换clips(delete_all+create+mark_ready)
- EditorClipBatchItem/EditorClipBatchUpdateRequest/EditorClipBatchUpdateResponse schema
删除死代码:
- worker_app/tasks/edit_plan_generation.py(worker.render_edit_plan,452行)
- worker_app/tasks/compose_video.py(worker.compose_video,198行)
- celery_app.py imports清理、tasks/__init__.py清理
- test_edit_plan_worker_failure.py、test_cover_url_finalize.py(测试已删除模块)
- templates_editor/generation.py的send_task改为worker.generate_video
新增3个单测,全量13790 passed
141 lines
4.5 KiB
Python
Executable File
141 lines
4.5 KiB
Python
Executable File
"""查重辅助函数 — 从 generation.py 提取的 GeneratedVideo 记录 + 查重逻辑.
|
|
|
|
供 generate_video 共同复用,
|
|
创建 GeneratedVideo 记录后计算指纹并执行项目级 + 批次内查重。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def create_video_record_and_dedup(
|
|
*,
|
|
generation_task_id: str,
|
|
project_id: str,
|
|
user_id: str = "",
|
|
batch_id: str,
|
|
file_url: str,
|
|
file_size: int,
|
|
duration: float,
|
|
video_path: str,
|
|
mode: str,
|
|
session: Session,
|
|
width: int = 1280,
|
|
height: int = 720,
|
|
fps: float = 25.0,
|
|
name: str = "",
|
|
thumbnail_url: str = "",
|
|
) -> int:
|
|
"""创建 GeneratedVideo 记录,计算指纹并执行查重(历史 + 批次)。
|
|
|
|
Args:
|
|
generation_task_id: 生成任务 ID
|
|
project_id: 项目 ID
|
|
batch_id: 批次 ID(可为空字符串)
|
|
file_url: 视频文件 URL
|
|
file_size: 文件大小(字节)
|
|
duration: 视频时长(秒)
|
|
video_path: 视频本地路径(用于计算指纹)
|
|
mode: 剪辑模式名称
|
|
session: 数据库会话
|
|
width: 视频宽度
|
|
height: 视频高度
|
|
fps: 视频帧率
|
|
|
|
Returns:
|
|
创建的视频记录数量(1 表示成功,0 表示失败)
|
|
"""
|
|
from video_processing.dedup import VideoDeduplicator
|
|
|
|
from packages.adapters.sqlalchemy_impl.generated_video_repository import (
|
|
SQLAlchemyGeneratedVideoRepository,
|
|
)
|
|
from packages.domain import GeneratedVideo
|
|
|
|
try:
|
|
video_id = uuid4().hex
|
|
# 使用传入的名称,没有则 fallback 到默认命名
|
|
video_name = name.strip() if name else f"generated-{generation_task_id[:8]}.mp4"
|
|
generated_video = GeneratedVideo(
|
|
id=video_id,
|
|
project_id=project_id,
|
|
user_id=user_id,
|
|
generation_task_id=generation_task_id,
|
|
name=video_name,
|
|
file_url=file_url,
|
|
file_size=file_size,
|
|
duration=duration,
|
|
width=width,
|
|
height=height,
|
|
fps=fps,
|
|
status="completed",
|
|
generation_params={"mode": mode},
|
|
)
|
|
|
|
video_repo = SQLAlchemyGeneratedVideoRepository(session)
|
|
video_repo.create(generated_video)
|
|
|
|
# 生成封面缩略图
|
|
if thumbnail_url:
|
|
generated_video.thumbnail_url = thumbnail_url
|
|
video_repo.update_thumbnail(video_id, thumbnail_url)
|
|
logger.info("Thumbnail set for video %s: %s", video_id, thumbnail_url[:80] if thumbnail_url else "")
|
|
else:
|
|
logger.debug("No thumbnail_url provided for video %s, skipping", video_id)
|
|
|
|
# 计算视频指纹
|
|
deduplicator = VideoDeduplicator()
|
|
try:
|
|
fingerprint = deduplicator.compute_fingerprint(video_path)
|
|
except Exception as fp_err:
|
|
logger.warning("Fingerprint computation failed for %s: %s", video_id, fp_err)
|
|
session.commit()
|
|
return 1
|
|
|
|
generated_video.video_fingerprint = fingerprint.to_dict()
|
|
|
|
# (a) 历史成片查重
|
|
duplicate_result = deduplicator.check_duplicate(fingerprint, project_id, session)
|
|
|
|
# (b) 批次内查重(仅当有 batch_id 时)
|
|
if not duplicate_result and batch_id:
|
|
duplicate_result = deduplicator.check_batch_duplicate(fingerprint, batch_id, video_id, session)
|
|
|
|
if duplicate_result:
|
|
generated_video.is_duplicate = True
|
|
generated_video.duplicate_of = duplicate_result["duplicate_of"]
|
|
logger.info(
|
|
"Duplicate detected: %s -> %s (reason=%s, similarity=%.3f)",
|
|
video_id,
|
|
duplicate_result["duplicate_of"],
|
|
duplicate_result["reason"],
|
|
duplicate_result["similarity"],
|
|
)
|
|
else:
|
|
generated_video.is_duplicate = False
|
|
generated_video.duplicate_of = None
|
|
|
|
video_repo.update(generated_video)
|
|
session.commit()
|
|
logger.info(
|
|
"GeneratedVideo record created: %s (task=%s, dup=%s)",
|
|
video_id,
|
|
generation_task_id,
|
|
generated_video.is_duplicate,
|
|
)
|
|
return 1
|
|
except Exception as e:
|
|
logger.error(
|
|
"Failed to create video record / dedup for task %s: %s",
|
|
generation_task_id,
|
|
e,
|
|
)
|
|
session.rollback()
|
|
return 0
|