Auto-transcode AV1/HEVC videos to H.264 before processing
Docker Build and Push / check-secrets (push) Successful in 5s
Docker Build and Push / build-and-push (cuda, 12.8) (push) Has been skipped
Docker Build and Push / build-and-push (directml, latest) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 11.8) (push) Has been skipped
Docker Build and Push / build-and-push (cpu, latest) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.6) (push) Has been skipped
Build Windows CPU / build (push) Has been cancelled
Build Windows CUDA 11.8 / build (push) Has been cancelled
Build Windows CUDA 12.6 / build (push) Has been cancelled
Build Windows CUDA 12.8 / build (push) Has been cancelled
Build Windows DirectML / build (push) Has been cancelled

Root cause: FramePrefetcher._read_loop exits after cap.read() returns False
(AV1 decode failure), putting only one (False, None) sentinel in the queue.
If the caller reads past that, queue.get() blocks forever → deadlock.

Fix: _ensure_h264() checks if OpenCV can read the first frame. If not,
runs FFmpeg to transcode to H.264 NVENC (falls back to libx264 if NVENC
fails). Uses cached converted file on repeated attempts. Processing then
runs on the H.264 version which OpenCV can decode normally.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
liuchen
2026-06-27 15:48:12 +08:00
co-authored by Claude Haiku 4.5
parent 8dc1276a7c
commit 0b2a3ec6e3
+53
View File
@@ -389,6 +389,56 @@ def _download(task_id: str, url: str, dest: Path) -> bool:
return False
def _ensure_h264(task_id: str, video_path: str) -> str:
"""如果视频无法被 OpenCV 解码(如 AV1/HEVC),先用 FFmpeg 转成 H.264。
返回可用的视频路径(原路径或转码后路径)。"""
cap = cv2.VideoCapture(video_path)
ret, _ = cap.read()
cap.release()
if ret:
return video_path # 可正常解码,无需转换
# 无法解码,转码
converted = str(Path(video_path).with_suffix('')) + '_h264.mp4'
if Path(converted).exists():
push_log(task_id, f"使用已有 H.264 转码版本: {Path(converted).name}")
return converted
push_log(task_id, "⚠ 视频格式(可能是 AV1/HEVC)OpenCV 无法解码,自动转换为 H.264…")
try:
from backend.tools.ffmpeg_cli import FFmpegCLI
import subprocess
cmd = [
FFmpegCLI.instance().ffmpeg_path,
'-y', '-i', video_path,
'-c:v', 'h264_nvenc', '-cq', '18', '-preset', 'p4',
'-c:a', 'copy',
'-loglevel', 'error',
converted,
]
push_log(task_id, "FFmpeg 转码中(H.264 NVENC)…")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
if result.returncode != 0 or not Path(converted).exists():
push_log(task_id, "NVENC 转码失败,回退 libx264…")
cmd_x264 = [
FFmpegCLI.instance().ffmpeg_path,
'-y', '-i', video_path,
'-c:v', 'libx264', '-crf', '18', '-preset', 'fast',
'-c:a', 'copy', '-loglevel', 'error', converted,
]
result = subprocess.run(cmd_x264, capture_output=True, text=True, timeout=3600)
if result.returncode == 0 and Path(converted).exists():
size_mb = Path(converted).stat().st_size / 1048576
push_log(task_id, f"✓ 转码完成 → {Path(converted).name} ({size_mb:.1f} MB)")
return converted
else:
push_log(task_id, f"转码失败: {result.stderr[:200]},尝试用原始文件继续")
return video_path
except Exception as e:
push_log(task_id, f"转码异常: {e},尝试用原始文件继续")
return video_path
def _process(task_id: str, video_path: str, inpaint_mode_str: str, subtitle_areas: list,
preview_mode: bool = False, detect_mode: str = "text"):
if not VSR_AVAILABLE:
@@ -401,6 +451,9 @@ def _process(task_id: str, video_path: str, inpaint_mode_str: str, subtitle_area
push_log(task_id, f"启动 — 算法:{modeLabels.get(inpaint_mode_str, inpaint_mode_str)}")
update_task(task_id, status="processing", progress=0, started_at=time.time())
# ── 预检:AV1/HEVC 等 OpenCV 无法解码的格式,先转换为 H.264 ──────────────
video_path = _ensure_h264(task_id, video_path)
class _WebRemover(SubtitleRemover):
_static_boxes = []