Adding GPU transcoding possibility

This commit is contained in:
2025-09-24 12:19:29 +02:00
parent 1dc7005d4b
commit 4ae6951c9c

View File

@@ -544,7 +544,7 @@ def reuse_repo_transcript(media_path: Path, repo_json: Path) -> Path | None:
# copy or synthesize TXT
if src_txt.exists():
shutil.copy2(src_txt, new_base.with_suffix(".txt"))
_safe_copy(src_txt, new_base.with_suffix(".txt"))
else:
# fallback: concatenate segments
txt = " ".join(s.get("text", "") for s in data.get("segments", []))
@@ -552,7 +552,7 @@ def reuse_repo_transcript(media_path: Path, repo_json: Path) -> Path | None:
# copy SRT/VTT if present; otherwise synthesize SRT from segments
if src_srt.exists():
shutil.copy2(src_srt, new_base.with_suffix(".srt"))
_safe_copy(src_srt, new_base.with_suffix(".srt"))
else:
# synthesize SRT
def fmt_ts(t):
@@ -562,7 +562,7 @@ def reuse_repo_transcript(media_path: Path, repo_json: Path) -> Path | None:
for i, s in enumerate(data.get("segments", []), 1):
srt.write(f"{i}\n{fmt_ts(s.get('start',0.0))} --> {fmt_ts(s.get('end',0.0))}\n{s.get('text','').strip()}\n\n")
if src_vtt.exists():
shutil.copy2(src_vtt, new_base.with_suffix(".vtt"))
_safe_copy(src_vtt, new_base.with_suffix(".vtt"))
else:
# synthesize VTT from segments
def fmt_ts_vtt(t):
@@ -642,7 +642,7 @@ def ensure_sidecar_next_to_media(sidecar: Path, media_path: Path, lang: str = "e
return
if sidecar.suffix.lower() == ".srt":
dst = media_path.with_suffix(f".{lang}.srt")
shutil.copy2(sidecar, dst)
_safe_copy(sidecar, dst)
elif sidecar.suffix.lower() == ".vtt":
tmp_srt = sidecar.with_suffix(".srt")
subprocess.run(["ffmpeg", "-nostdin", "-y", "-threads", str(FFMPEG_THREADS), "-i", str(sidecar), str(tmp_srt)], check=True)
@@ -708,7 +708,7 @@ def save_episode_artwork(image_url: str | None, media_path: Path, show_title: st
try:
show_poster = media_path.parent / "poster.jpg"
if not show_poster.exists():
shutil.copy2(episode_jpg, show_poster)
_safe_copy(episode_jpg, show_poster)
except Exception:
pass
@@ -1187,7 +1187,7 @@ def transcribe(media_path: Path):
lang_code = (info.language or (WHISPER_LANGUAGE if WHISPER_LANGUAGE.lower() != 'auto' else 'en')).lower()
srt_src = base.with_suffix(".srt")
srt_dst = media_path.with_suffix(f".{lang_code}.srt")
shutil.copy2(srt_src, srt_dst)
_safe_copy(srt_src, srt_dst)
except Exception as e:
print(f"[post] could not copy srt -> {srt_dst}: {e}", flush=True)
@@ -1500,6 +1500,17 @@ def _unique_backup_path(path: Path) -> Path:
counter += 1
def _safe_copy(src: Path, dst: Path) -> None:
try:
if src.resolve(strict=False) == dst.resolve(strict=False):
return
except Exception:
if os.path.abspath(src) == os.path.abspath(dst):
return
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
def _is_sidecar_name(name: str, base_stem: str, base_name: str) -> bool:
exact_suffixes = [".info.json", ".nfo", ".jpg", ".jpeg", ".png", ".webp", ".prov.json"]
for suf in exact_suffixes:
@@ -1585,41 +1596,68 @@ def _normalize_video_file(path: Path, info: dict[str, str]) -> Path:
if tmp_path.exists():
tmp_path.unlink()
cmd = [
"ffmpeg", "-nostdin", "-y",
"-i", str(path),
"-map", "0",
"-c:v", encoder,
]
if VIDEO_NORMALIZE_PRESET:
cmd.extend(["-preset", VIDEO_NORMALIZE_PRESET])
if VIDEO_NORMALIZE_TUNE:
cmd.extend(["-tune", VIDEO_NORMALIZE_TUNE])
if VIDEO_NORMALIZE_CRF:
cmd.extend(["-crf", VIDEO_NORMALIZE_CRF])
def build_cmd(v_encoder: str) -> list[str]:
cmd = [
"ffmpeg", "-nostdin", "-y",
"-i", str(path),
"-map", "0",
"-c:v", v_encoder,
]
if VIDEO_NORMALIZE_PRESET:
cmd.extend(["-preset", VIDEO_NORMALIZE_PRESET])
if VIDEO_NORMALIZE_TUNE:
cmd.extend(["-tune", VIDEO_NORMALIZE_TUNE])
if VIDEO_NORMALIZE_CRF:
cmd.extend(["-crf", VIDEO_NORMALIZE_CRF])
if info.get("audio"):
if VIDEO_NORMALIZE_AUDIO_CODEC == "copy":
cmd.extend(["-c:a", "copy"])
if info.get("audio"):
if VIDEO_NORMALIZE_AUDIO_CODEC == "copy":
cmd.extend(["-c:a", "copy"])
else:
cmd.extend(["-c:a", _resolve_audio_encoder(VIDEO_NORMALIZE_AUDIO_CODEC)])
if VIDEO_NORMALIZE_AUDIO_BITRATE:
cmd.extend(["-b:a", VIDEO_NORMALIZE_AUDIO_BITRATE])
else:
cmd.extend(["-c:a", _resolve_audio_encoder(VIDEO_NORMALIZE_AUDIO_CODEC)])
if VIDEO_NORMALIZE_AUDIO_BITRATE:
cmd.extend(["-b:a", VIDEO_NORMALIZE_AUDIO_BITRATE])
else:
cmd.append("-an")
cmd.append("-an")
cmd.extend(["-c:s", "copy", str(tmp_path)])
cmd.extend(["-c:s", "copy", str(tmp_path)])
return cmd
print(f"[normalize] video -> {final_path.name} codec={VIDEO_NORMALIZE_CODEC}", flush=True)
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
if tmp_path.exists():
tmp_path.unlink()
raise RuntimeError(f"ffmpeg video normalize failed: {e}")
def fallback_encoder() -> str:
base = VIDEO_NORMALIZE_CODEC.lower()
if base.startswith("hevc") or base.startswith("h265"):
return "libx265"
if base.startswith("h264") or base.startswith("avc"):
return "libx264"
if base.startswith("av1"):
return "libaom-av1"
return "libx265"
rename_media_sidecars(path, final_path, skip={tmp_path})
return _finalize_normalized_output(path, final_path, tmp_path)
attempted = []
encoders_to_try = [encoder]
lower_encoder = encoder.lower()
if any(token in lower_encoder for token in ("nvenc", "qsv", "cuda", "amf")):
cpu_encoder = fallback_encoder()
if cpu_encoder not in encoders_to_try:
encoders_to_try.append(cpu_encoder)
for idx, enc in enumerate(encoders_to_try):
try:
print(f"[normalize] video -> {final_path.name} codec={enc}", flush=True)
subprocess.check_call(build_cmd(enc))
rename_media_sidecars(path, final_path, skip={tmp_path})
return _finalize_normalized_output(path, final_path, tmp_path)
except subprocess.CalledProcessError as e:
attempted.append((enc, e))
if tmp_path.exists():
tmp_path.unlink()
if idx == len(encoders_to_try) - 1:
details = ", ".join(f"{enc}: {err}" for enc, err in attempted)
raise RuntimeError(f"ffmpeg video normalize failed ({details})")
else:
print(f"[normalize] encoder {enc} failed ({e}); retrying with CPU fallback", flush=True)
return path
def _normalize_audio_file(path: Path, info: dict[str, str]) -> Path:
@@ -2054,7 +2092,7 @@ def refresh_media(path_str: str):
for s in p.parent.glob(f"{p.stem}*.srt"):
# If it's already lang-suffixed, keep; also copy to .en.srt when only plain .srt exists
if s.name == f"{p.stem}.srt":
shutil.copy2(s, p.with_suffix(".en.srt"))
_safe_copy(s, p.with_suffix(".en.srt"))
except Exception:
pass