refactor(plex): address /code-review on the R12 S1 split

Cleanup only, no behaviour change (ruff clean + pytest 193):
- imports hoisted above `router =` and grouped/sorted (isort) across the split
  files; ruff-format pass restores the 2-blank-line PEP8 spacing the generator
  collapsed to 1.
- the shared on-disk cache root moves to a neutral app/plex/cache.py, so
  subtitles.py no longer imports it from imgcache.py (drops the cross-import).
- the former plex.py module docstring's access-model note (read endpoints open
  to any authed user; admin endpoints test/sync) is restored in the package
  __init__.py.
This commit is contained in:
2026-08-11 21:46:43 +02:00
parent 99befa6e38
commit f484de1008
9 changed files with 640 additions and 156 deletions
+35 -8
View File
@@ -2,22 +2,33 @@
Text subtitles (embedded or external) are turned into WebVTT so the browser <track> can render them;
bitmap subtitles (Blu-ray/DVD) can't be (would need burn-in). Converted VTTs are cached on the shared
Plex on-disk cache root (``imgcache.CACHE_DIR``).
Plex on-disk cache root (``cache.CACHE_DIR``).
"""
import re
import subprocess
from app.plex.cache import CACHE_DIR
from app.plex.client import PlexError
from app.plex.imgcache import CACHE_DIR
# Image-based subtitle codecs (Blu-ray/DVD) — can't be turned into text WebVTT (would need burn-in).
BITMAP_SUBS = {"pgs", "hdmv_pgs_subtitle", "dvd_subtitle", "dvdsub", "vobsub", "dvbsub", "dvb_subtitle"}
BITMAP_SUBS = {
"pgs",
"hdmv_pgs_subtitle",
"dvd_subtitle",
"dvdsub",
"vobsub",
"dvbsub",
"dvb_subtitle",
}
def srt_to_vtt(text: str) -> str:
"""Minimal SubRip→WebVTT: add the header and switch the ms separator (`,`→`.`). SRT cue-index
lines are valid in WebVTT, so they're left as-is."""
body = re.sub(r"(\d\d:\d\d:\d\d),(\d\d\d)", r"\1.\2", text.replace("\r\n", "\n").lstrip(""))
body = re.sub(
r"(\d\d:\d\d:\d\d),(\d\d\d)", r"\1.\2", text.replace("\r\n", "\n").lstrip("")
)
return "WEBVTT\n\n" + body.strip() + "\n"
@@ -75,7 +86,9 @@ def shift_vtt(vtt: str, offset: float) -> str:
if en <= 0:
keep = False # whole cue is before the session start → drop the block
else:
lines[i] = f"{_vtt_s_to_ts(st)} --> {_vtt_s_to_ts(en)}{settings}" # _vtt_s_to_ts clamps st>=0
lines[i] = (
f"{_vtt_s_to_ts(st)} --> {_vtt_s_to_ts(en)}{settings}" # _vtt_s_to_ts clamps st>=0
)
break
if keep:
out_blocks.append("\n".join(lines))
@@ -85,11 +98,25 @@ def shift_vtt(vtt: str, offset: float) -> str:
def ffmpeg_to_vtt(src: str, sub_ord: int | None) -> str:
"""Convert one subtitle stream of a file to WebVTT via ffmpeg (for embedded subs, and non-SRT
external subs written to a temp file). `sub_ord` = ordinal among the file's subtitle streams."""
cmd = ["ffmpeg", "-nostdin", "-loglevel", "error", "-i", src,
"-map", f"0:s:{sub_ord if sub_ord is not None else 0}", "-f", "webvtt", "pipe:1"]
cmd = [
"ffmpeg",
"-nostdin",
"-loglevel",
"error",
"-i",
src,
"-map",
f"0:s:{sub_ord if sub_ord is not None else 0}",
"-f",
"webvtt",
"pipe:1",
]
r = subprocess.run(cmd, capture_output=True)
if r.returncode != 0 or not r.stdout:
raise PlexError((r.stderr or b"").decode("utf-8", "replace")[:200] or "ffmpeg subtitle convert failed")
raise PlexError(
(r.stderr or b"").decode("utf-8", "replace")[:200]
or "ffmpeg subtitle convert failed"
)
return r.stdout.decode("utf-8", "replace")