"""查重辅助函数 — 从 generation.py 提取的 GeneratedVideo 记录 + 查重逻辑. 供 render_edit_plan 和 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 reused (pre-generated) for video %s", video_id) else: thumbnail_storage_key = f"generated/projects/{project_id}/thumbnails/{video_id}.jpg" try: from video_processing.thumbnail_generator import generate_and_upload_thumbnail _thumbnail_url = generate_and_upload_thumbnail(video_path, thumbnail_storage_key) if _thumbnail_url: generated_video.thumbnail_url = _thumbnail_url video_repo.update_thumbnail(video_id, _thumbnail_url) logger.info("Thumbnail generated for video %s: %s", video_id, _thumbnail_url) except Exception as thumb_err: logger.warning("Thumbnail generation failed for %s: %s", video_id, thumb_err) # 计算视频指纹 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