From 773dafe5a8eece364f4d492cd8d7517dfa717e30 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Thu, 20 Aug 2026 10:50:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8A=E4=BC=A0=E6=97=B6=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B=20HEVC=20=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E5=B9=B6=E8=BD=AC=E7=A0=81=E4=B8=BA=201080p=20H.264?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决 4K HEVC 素材在浏览器 WebCodecs 硬件解码时输出黑帧的问题。 改动范围:仅 ingest.py(+88 行) - metadata 提取后检测 codec 是否为 hevc/h265/hvh1 - HEVC 素材自动转码:H.264 CRF18 + 1080p + yuv420p + faststart - 转码产物上传 OSS,storage_key 指向 _h264.mp4 - 转码后重新提取 metadata(新 codec/size/分辨率) - 非 HEVC 素材(H.264 等)不做任何处理 - 转码失败降级使用原始文件,不阻塞上传流程 - 超时 300s,临时文件 finally 清理 --- apps/worker/worker_app/tasks/ingest.py | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/apps/worker/worker_app/tasks/ingest.py b/apps/worker/worker_app/tasks/ingest.py index 6449b8d0f..3f1f50e6c 100755 --- a/apps/worker/worker_app/tasks/ingest.py +++ b/apps/worker/worker_app/tasks/ingest.py @@ -233,6 +233,94 @@ def ingest_asset(job_id: str) -> dict: job_id, thumb_err, ) + + # ── HEVC 自动转码为 1080p H.264 ────────────────────────────── + # 浏览器 WebCodecs 硬件解码 HEVC 输出黑帧,上传时自动转码 + # 失败时降级使用原始文件,不阻塞上传流程 + if ( + media_type == "video" + and extract_success + and local_file + and local_file.exists() + ): + codec = (metadata.get("codec") or "").lower() + if codec in ("hevc", "h265", "hvh1"): + logger.info( + "检测到 HEVC 编码 (codec=%s),启动转码: job_id=%s", + codec, job_id, + ) + _tc_tmp = None + try: + _tc_tmp = Path(tempfile.mktemp(suffix="_h264.mp4")) + _cmd = [ + "ffmpeg", "-y", + "-i", str(local_file), + "-c:v", "libx264", + "-preset", "fast", + "-crf", "18", + "-vf", + "scale='if(gt(ih,1080),-2,iw)':'if(gt(ih,1080),1080,ih)'", + "-pix_fmt", "yuv420p", + "-c:a", "aac", + "-b:a", "128k", + "-movflags", "+faststart", + str(_tc_tmp), + ] + _proc = subprocess.run( + _cmd, capture_output=True, text=True, timeout=300, + ) + if ( + _proc.returncode == 0 + and _tc_tmp.exists() + and _tc_tmp.stat().st_size > 0 + ): + from video_processing.oss_helpers import upload_to_oss + + _p = Path(job.storage_key) + _new_key = str( + _p.parent / (_p.stem + "_h264" + _p.suffix) + ) + _url = upload_to_oss(_tc_tmp, _new_key) + if _url: + job.storage_key = _new_key + metadata, extract_success = ( + extract_media_metadata( + str(_tc_tmp), media_type, + ) + ) + logger.info( + "HEVC→H.264 转码完成: job_id=%s key=%s", + job_id, _new_key[:80], + ) + else: + logger.warning( + "转码文件上传 OSS 失败,使用原始文件: job_id=%s", + job_id, + ) + else: + _tail = ( + _proc.stderr[-300:] if _proc.stderr else "" + ) + logger.warning( + "FFmpeg 转码失败 rc=%s,降级原始文件: job_id=%s", + _proc.returncode, job_id, + ) + except subprocess.TimeoutExpired: + logger.warning( + "FFmpeg 转码超时 (300s),降级原始文件: job_id=%s", + job_id, + ) + except Exception as _e: + logger.warning( + "HEVC 转码异常(降级原始文件): job_id=%s err=%s", + job_id, _e, + ) + finally: + if _tc_tmp and _tc_tmp.exists(): + try: + _tc_tmp.unlink() + except OSError: + pass finally: if local_file and local_file.exists(): try: