Adding GPU transcoding possibility
This commit is contained in:
@@ -544,7 +544,7 @@ def reuse_repo_transcript(media_path: Path, repo_json: Path) -> Path | None:
|
|||||||
|
|
||||||
# copy or synthesize TXT
|
# copy or synthesize TXT
|
||||||
if src_txt.exists():
|
if src_txt.exists():
|
||||||
shutil.copy2(src_txt, new_base.with_suffix(".txt"))
|
_safe_copy(src_txt, new_base.with_suffix(".txt"))
|
||||||
else:
|
else:
|
||||||
# fallback: concatenate segments
|
# fallback: concatenate segments
|
||||||
txt = " ".join(s.get("text", "") for s in data.get("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
|
# copy SRT/VTT if present; otherwise synthesize SRT from segments
|
||||||
if src_srt.exists():
|
if src_srt.exists():
|
||||||
shutil.copy2(src_srt, new_base.with_suffix(".srt"))
|
_safe_copy(src_srt, new_base.with_suffix(".srt"))
|
||||||
else:
|
else:
|
||||||
# synthesize SRT
|
# synthesize SRT
|
||||||
def fmt_ts(t):
|
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):
|
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")
|
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():
|
if src_vtt.exists():
|
||||||
shutil.copy2(src_vtt, new_base.with_suffix(".vtt"))
|
_safe_copy(src_vtt, new_base.with_suffix(".vtt"))
|
||||||
else:
|
else:
|
||||||
# synthesize VTT from segments
|
# synthesize VTT from segments
|
||||||
def fmt_ts_vtt(t):
|
def fmt_ts_vtt(t):
|
||||||
@@ -642,7 +642,7 @@ def ensure_sidecar_next_to_media(sidecar: Path, media_path: Path, lang: str = "e
|
|||||||
return
|
return
|
||||||
if sidecar.suffix.lower() == ".srt":
|
if sidecar.suffix.lower() == ".srt":
|
||||||
dst = media_path.with_suffix(f".{lang}.srt")
|
dst = media_path.with_suffix(f".{lang}.srt")
|
||||||
shutil.copy2(sidecar, dst)
|
_safe_copy(sidecar, dst)
|
||||||
elif sidecar.suffix.lower() == ".vtt":
|
elif sidecar.suffix.lower() == ".vtt":
|
||||||
tmp_srt = sidecar.with_suffix(".srt")
|
tmp_srt = sidecar.with_suffix(".srt")
|
||||||
subprocess.run(["ffmpeg", "-nostdin", "-y", "-threads", str(FFMPEG_THREADS), "-i", str(sidecar), str(tmp_srt)], check=True)
|
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:
|
try:
|
||||||
show_poster = media_path.parent / "poster.jpg"
|
show_poster = media_path.parent / "poster.jpg"
|
||||||
if not show_poster.exists():
|
if not show_poster.exists():
|
||||||
shutil.copy2(episode_jpg, show_poster)
|
_safe_copy(episode_jpg, show_poster)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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()
|
lang_code = (info.language or (WHISPER_LANGUAGE if WHISPER_LANGUAGE.lower() != 'auto' else 'en')).lower()
|
||||||
srt_src = base.with_suffix(".srt")
|
srt_src = base.with_suffix(".srt")
|
||||||
srt_dst = media_path.with_suffix(f".{lang_code}.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:
|
except Exception as e:
|
||||||
print(f"[post] could not copy srt -> {srt_dst}: {e}", flush=True)
|
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
|
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:
|
def _is_sidecar_name(name: str, base_stem: str, base_name: str) -> bool:
|
||||||
exact_suffixes = [".info.json", ".nfo", ".jpg", ".jpeg", ".png", ".webp", ".prov.json"]
|
exact_suffixes = [".info.json", ".nfo", ".jpg", ".jpeg", ".png", ".webp", ".prov.json"]
|
||||||
for suf in exact_suffixes:
|
for suf in exact_suffixes:
|
||||||
@@ -1585,11 +1596,12 @@ def _normalize_video_file(path: Path, info: dict[str, str]) -> Path:
|
|||||||
if tmp_path.exists():
|
if tmp_path.exists():
|
||||||
tmp_path.unlink()
|
tmp_path.unlink()
|
||||||
|
|
||||||
|
def build_cmd(v_encoder: str) -> list[str]:
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffmpeg", "-nostdin", "-y",
|
"ffmpeg", "-nostdin", "-y",
|
||||||
"-i", str(path),
|
"-i", str(path),
|
||||||
"-map", "0",
|
"-map", "0",
|
||||||
"-c:v", encoder,
|
"-c:v", v_encoder,
|
||||||
]
|
]
|
||||||
if VIDEO_NORMALIZE_PRESET:
|
if VIDEO_NORMALIZE_PRESET:
|
||||||
cmd.extend(["-preset", VIDEO_NORMALIZE_PRESET])
|
cmd.extend(["-preset", VIDEO_NORMALIZE_PRESET])
|
||||||
@@ -1609,17 +1621,43 @@ def _normalize_video_file(path: Path, info: dict[str, str]) -> Path:
|
|||||||
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)
|
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"
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
subprocess.check_call(cmd)
|
print(f"[normalize] video -> {final_path.name} codec={enc}", flush=True)
|
||||||
except subprocess.CalledProcessError as e:
|
subprocess.check_call(build_cmd(enc))
|
||||||
if tmp_path.exists():
|
|
||||||
tmp_path.unlink()
|
|
||||||
raise RuntimeError(f"ffmpeg video normalize failed: {e}")
|
|
||||||
|
|
||||||
rename_media_sidecars(path, final_path, skip={tmp_path})
|
rename_media_sidecars(path, final_path, skip={tmp_path})
|
||||||
return _finalize_normalized_output(path, final_path, 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:
|
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"):
|
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 it's already lang-suffixed, keep; also copy to .en.srt when only plain .srt exists
|
||||||
if s.name == f"{p.stem}.srt":
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user