From e003a43894ece0586efa248e82f4bad1b7f867f2 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 19 Jul 2026 14:40:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(P0):=20=E7=B4=A0=E6=9D=90=E5=BA=93?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=B8=8A=E4=BC=A0=E6=97=B6=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=BC=A9=E7=95=A5=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ingest流程中添加视频缩略图生成(使用15%处抽帧) - 缩略图生成失败不影响主流程,仅打warning日志 - 修复素材库视频全部显示渐变占位图的问题 Issue: #577 --- apps/worker/worker_app/tasks/ingest.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/worker/worker_app/tasks/ingest.py b/apps/worker/worker_app/tasks/ingest.py index a3e001e01..e2a8d6f5b 100755 --- a/apps/worker/worker_app/tasks/ingest.py +++ b/apps/worker/worker_app/tasks/ingest.py @@ -280,6 +280,7 @@ def ingest_asset(job_id: str) -> dict: # 先从 OSS 下载文件到本地临时目录,再提取元数据 # (storage_key 是 OSS 内部路径,不能直接传给 ffprobe/Pillow) local_file = None + thumbnail_url = None try: suffix = Path(job.storage_key).suffix or ".bin" with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: @@ -291,6 +292,29 @@ def ingest_asset(job_id: str) -> dict: metadata, extract_success = {}, False else: metadata, extract_success = extract_media_metadata(str(local_file), media_type) + + # 视频类型:生成缩略图(文件还在的时候生成) + thumbnail_url = None + if media_type == "video" and extract_success: + try: + from video_processing.thumbnail_generator import generate_and_upload_thumbnail + + thumb_storage_key = f"assets/{job.project_id}/thumbnails/{job_id}.jpg" + thumbnail_url = generate_and_upload_thumbnail( + str(local_file), thumb_storage_key + ) + if thumbnail_url: + logger.info( + "素材缩略图生成成功: job_id=%s url=%s", + job_id, + thumbnail_url[:80], + ) + except Exception as thumb_err: + logger.warning( + "素材缩略图生成失败(不影响主流程): job_id=%s error=%s", + job_id, + thumb_err, + ) finally: if local_file and local_file.exists(): try: @@ -359,6 +383,7 @@ def ingest_asset(job_id: str) -> dict: codec=metadata.get("codec") or None, status=AssetStatus.READY, file_hash=job.file_hash, + thumbnail_url=thumbnail_url, ) asset_repo.create(asset) -- 2.54.0 From 9e89d22cb7cc07de2be13bb520890c871dd8e6a4 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 19 Jul 2026 14:53:19 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(P0):=20=E4=BF=AE=E5=A4=8D=E7=BC=A9?= =?UTF-8?q?=E7=95=A5=E5=9B=BE=E7=94=9F=E6=88=90=E5=A4=B1=E8=B4=A5=E6=97=B6?= =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6=E6=B3=84=E6=BC=8F=20+=20?= =?UTF-8?q?=E7=B4=A0=E6=9D=90=E5=BA=93=E8=A7=86=E9=A2=91=E7=BC=A9=E7=95=A5?= =?UTF-8?q?=E5=9B=BE=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ingest流程中添加视频缩略图生成(使用15%处抽帧) - 缩略图生成失败不影响主流程,仅打warning日志 - 修复extract_first_frame失败时临时文件未清理的bug - 修复素材库视频全部显示渐变占位图的问题 Issue: #577 --- .../video_processing/thumbnail_generator.py | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/apps/worker/video_processing/thumbnail_generator.py b/apps/worker/video_processing/thumbnail_generator.py index 175a1f9a1..5429587cd 100755 --- a/apps/worker/video_processing/thumbnail_generator.py +++ b/apps/worker/video_processing/thumbnail_generator.py @@ -38,53 +38,36 @@ def extract_first_frame( """ from video_processing.ffmpeg_utils import FFMPEG_BIN, probe_duration, run_ffmpeg + _is_temp_output = False if output_path is None: tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) tmp.close() output_path = tmp.name - - # 计算抽帧时间点:取视频时长 * seek_ratio,最少 min_seek_seconds 秒 - try: - duration = probe_duration(video_path) - seek_time = max(min_seek_seconds, duration * seek_ratio) - except Exception: - # probe 失败时 fallback 到第1秒 - seek_time = min_seek_seconds - - # 格式化为 HH:MM:SS.xx - seek_str = _format_seek_time(seek_time) - - # -ss 放在 -i 前面(input seeking,更快但精度稍低,缩略图够用) - # -vframes 1 只取一帧 - # -q:v 2 jpeg 高质量 - scale_filter = f"scale={width}:{height}:force_original_aspect_ratio=decrease" - cmd = [ - FFMPEG_BIN, - "-y", - "-ss", - seek_str, - "-i", - video_path, - "-vframes", - "1", - "-vf", - scale_filter, - "-q:v", - "2", - output_path, - ] + _is_temp_output = True try: - run_ffmpeg(cmd, capture_output=True, timeout=timeout) - except Exception: - # 失败时退回到第0帧兜底 - cmd2 = [ + # 计算抽帧时间点:取视频时长 * seek_ratio,最少 min_seek_seconds 秒 + try: + duration = probe_duration(video_path) + seek_time = max(min_seek_seconds, duration * seek_ratio) + except Exception: + # probe 失败时 fallback 到第1秒 + seek_time = min_seek_seconds + + # 格式化为 HH:MM:SS.xx + seek_str = _format_seek_time(seek_time) + + # -ss 放在 -i 前面(input seeking,更快但精度稍低,缩略图够用) + # -vframes 1 只取一帧 + # -q:v 2 jpeg 高质量 + scale_filter = f"scale={width}:{height}:force_original_aspect_ratio=decrease" + cmd = [ FFMPEG_BIN, "-y", + "-ss", + seek_str, "-i", video_path, - "-ss", - "00:00:00", "-vframes", "1", "-vf", @@ -93,12 +76,40 @@ def extract_first_frame( "2", output_path, ] - run_ffmpeg(cmd2, capture_output=True, timeout=timeout) - if not Path(output_path).exists() or Path(output_path).stat().st_size == 0: - raise RuntimeError(f"Thumbnail generation failed: {output_path}") + try: + run_ffmpeg(cmd, capture_output=True, timeout=timeout) + except Exception: + # 失败时退回到第0帧兜底 + cmd2 = [ + FFMPEG_BIN, + "-y", + "-i", + video_path, + "-ss", + "00:00:00", + "-vframes", + "1", + "-vf", + scale_filter, + "-q:v", + "2", + output_path, + ] + run_ffmpeg(cmd2, capture_output=True, timeout=timeout) - return output_path + if not Path(output_path).exists() or Path(output_path).stat().st_size == 0: + raise RuntimeError(f"Thumbnail generation failed: {output_path}") + + return output_path + except Exception: + # 失败时清理自己创建的临时文件 + if _is_temp_output and output_path: + try: + Path(output_path).unlink(missing_ok=True) + except Exception: + pass + raise def _format_seek_time(seconds: float) -> str: -- 2.54.0 From 95b9f01f70e68dce1ebf9490946fb587d4f6e449 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 19 Jul 2026 15:42:07 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20black=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/worker/worker_app/tasks/ingest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/worker/worker_app/tasks/ingest.py b/apps/worker/worker_app/tasks/ingest.py index e2a8d6f5b..b123db26a 100755 --- a/apps/worker/worker_app/tasks/ingest.py +++ b/apps/worker/worker_app/tasks/ingest.py @@ -300,9 +300,7 @@ def ingest_asset(job_id: str) -> dict: from video_processing.thumbnail_generator import generate_and_upload_thumbnail thumb_storage_key = f"assets/{job.project_id}/thumbnails/{job_id}.jpg" - thumbnail_url = generate_and_upload_thumbnail( - str(local_file), thumb_storage_key - ) + thumbnail_url = generate_and_upload_thumbnail(str(local_file), thumb_storage_key) if thumbnail_url: logger.info( "素材缩略图生成成功: job_id=%s url=%s", -- 2.54.0