fix(plex): honor the admin "max concurrent transcodes" setting (config drift)

stream.py enforced a hardcoded _MAX_SESSIONS = 4 and never read the
DB-overridable plex_max_transcodes ConfigSpec, so the Configuration → Plex →
"Max concurrent transcodes" knob was dead (same env-vs-DB drift class as the
Downloads/Admin fixes). _enforce_cap(cap) now takes the cap from
sysconfig.get_int(db, "plex_max_transcodes") (>=1 so playback can't be capped to
zero). Bumped the config default 1→4 so the effective default matches the old
hardcoded cap (no behavior change for non-overriders; the knob now works).
This commit is contained in:
npeter83 2026-07-11 21:53:24 +02:00
parent e92751dbce
commit 3c7a8c7a14
2 changed files with 8 additions and 6 deletions

View file

@ -211,7 +211,7 @@ class Settings(BaseSettings):
plex_watch_reconcile_interval_min: int = 1440 plex_watch_reconcile_interval_min: int = 1440
# Max concurrent transcodes for the P3 fallback. Low by default — CPU-only transcode is # Max concurrent transcodes for the P3 fallback. Low by default — CPU-only transcode is
# expensive; direct-serve (browser-compatible files) has no such limit. # expensive; direct-serve (browser-compatible files) has no such limit.
plex_max_transcodes: int = 1 plex_max_transcodes: int = 4
# Where on-the-fly HLS remux segments are written (transient, reaped). Empty → a `.plex-hls` # Where on-the-fly HLS remux segments are written (transient, reaped). Empty → a `.plex-hls`
# dir under download_root. On localdev the download_root is a slow Windows bind-mount, so point # dir under download_root. On localdev the download_root is a slow Windows bind-mount, so point
# this at a fast container-local path (e.g. /var/tmp/plex-hls); prod's download_root is fast # this at a fast container-local path (e.g. /var/tmp/plex-hls); prod's download_root is fast

View file

@ -23,6 +23,7 @@ from pathlib import Path
from sqlalchemy.orm import Session as DbSession from sqlalchemy.orm import Session as DbSession
from app import sysconfig
from app.config import settings from app.config import settings
from app.models import PlexItem from app.models import PlexItem
from app.plex import paths from app.plex import paths
@ -31,7 +32,6 @@ log = logging.getLogger("siftlode.plex")
_HLS_ROOT = Path(settings.plex_hls_dir) if settings.plex_hls_dir else Path(settings.download_root) / ".plex-hls" _HLS_ROOT = Path(settings.plex_hls_dir) if settings.plex_hls_dir else Path(settings.download_root) / ".plex-hls"
_SEG_SECONDS = 6 _SEG_SECONDS = 6
_MAX_SESSIONS = 4 # concurrent remux sessions (safety valve for the CPU-only host)
_SESSION_IDLE_S = 600 # reap a session with no access for this long _SESSION_IDLE_S = 600 # reap a session with no access for this long
_lock = threading.Lock() _lock = threading.Lock()
@ -168,11 +168,11 @@ def _kill(s: HlsSession) -> None:
shutil.rmtree(s.dir, ignore_errors=True) shutil.rmtree(s.dir, ignore_errors=True)
def _enforce_cap() -> None: def _enforce_cap(cap: int) -> None:
# Caller holds _lock. Drop the least-recently-accessed sessions over the cap. # Caller holds _lock. Drop the least-recently-accessed sessions over the cap.
if len(_sessions) <= _MAX_SESSIONS: if len(_sessions) <= cap:
return return
for key, s in sorted(_sessions.items(), key=lambda kv: kv[1].last_access)[: len(_sessions) - _MAX_SESSIONS]: for key, s in sorted(_sessions.items(), key=lambda kv: kv[1].last_access)[: len(_sessions) - cap]:
_kill(s) _kill(s)
_sessions.pop(key, None) _sessions.pop(key, None)
@ -211,7 +211,9 @@ def start_session(
) )
s = HlsSession(key, directory, proc, start_s, "master.m3u8" if is_multi else "index.m3u8") s = HlsSession(key, directory, proc, start_s, "master.m3u8" if is_multi else "index.m3u8")
_sessions[key] = s _sessions[key] = s
_enforce_cap() # Admin-configurable concurrency cap (Configuration → Plex → Max concurrent transcodes);
# at least 1 so playback can't be capped to zero.
_enforce_cap(max(1, sysconfig.get_int(db, "plex_max_transcodes")))
# Learn the REAL start offset K from the first VIDEO segment (outside the lock — this blocks on # Learn the REAL start offset K from the first VIDEO segment (outside the lock — this blocks on
# ffmpeg producing it, and we must not hold up other sessions). The client reads `media_start_s`, so # ffmpeg producing it, and we must not hold up other sessions). The client reads `media_start_s`, so
# its absolute clock + subtitle shift key off the true keyframe, not the requested offset. On # its absolute clock + subtitle shift key off the true keyframe, not the requested offset. On