docs(plex): the transcode comments say the CPU-decode route

This commit is contained in:
2026-11-07 21:48:39 +01:00
parent 4ed07388e8
commit a6f1a26658
2 changed files with 20 additions and 17 deletions
+9 -8
View File
@@ -227,16 +227,17 @@ class PlexItem(Base, TimestampMixin, UpdatedAtMixin):
playable: Mapped[str | None] = mapped_column(String(12), index=True) playable: Mapped[str | None] = mapped_column(String(12), index=True)
probed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) probed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
# Transcode HW-capability facet (the playback-class filter). NULL for non-transcode items; for a # Transcode HW-capability facet (the playback-class filter). NULL for non-transcode items; for a
# `playable="transcode"` item: True = the prod iGPU can hardware-transcode it (codec is HW-decodable # `playable="transcode"` item: True = the prod iGPU ENCODES it (a HW-decodable 8-bit SDR source, a
# AND the source is 8-bit SDR), False = software libx264 only (AV1/XviD, or a 10-bit/HDR source that # true-HDR one through the OpenCL tonemap, or an 8-bit source in a codec the CPU decodes for it —
# breaks the VAAPI nv12 pipeline — see plex/stream.py). Derived by `plex.sync.transcode_hw_capable`: # VC-1, XviD, DivX), False = software libx264 (AV1, or a 10-bit SDR source — see plex/stream.py).
# set codec-only at sync (bit depth unknown from the cheap listing), then refined by the bit-depth # Derived by `plex.sync.transcode_hw_capable`: set codec-only at sync (bit depth unknown from the
# backfill (`_probe_media_batch`) which downgrades a 10-bit/HDR HEVC to False. The one filterable # cheap listing), then refined by the bit-depth backfill (`_probe_media_batch`), which downgrades a
# home of the invariant `_want_hw`'s source gate computes at play time — kept in sync via the shared # 10-bit SDR HEVC to False. The filterable twin of the rule `_select_encode` applies at play time;
# classifier so the catalog facet and the runtime encoder choice never disagree by codec. # the two share their codec sets, not their code, so a change to one tree is a change to both.
transcode_hw: Mapped[bool | None] = mapped_column(Boolean, index=True) transcode_hw: Mapped[bool | None] = mapped_column(Boolean, index=True)
# Video bit depth (8/10/12…) from the Plex media Stream, filled by the bit-depth backfill. NULL = # Video bit depth (8/10/12…) from the Plex media Stream, filled by the bit-depth backfill. NULL =
# not yet probed. Surfaced on the info page (e.g. "HEVC · 10-bit") and feeds `transcode_hw`. # not yet probed. Surfaced on the info page (e.g. "HEVC · 10-bit") and feeds `transcode_hw` (a
# 10-bit SDR source drops to software; an HDR one keeps the GPU through the tonemap).
video_bit_depth: Mapped[int | None] = mapped_column(Integer) video_bit_depth: Mapped[int | None] = mapped_column(Integer)
# Watermark for the bit-depth backfill: when this item's media Stream was last probed for # Watermark for the bit-depth backfill: when this item's media Stream was last probed for
# bit-depth/HDR. NULL = never (the backfill's work-list). Only HW-decodable transcode items are # bit-depth/HDR. NULL = never (the backfill's work-list). Only HW-decodable transcode items are
+11 -9
View File
@@ -4,9 +4,10 @@ Playback is from the LOCAL physical file. Browser-compatible files (playable="di
raw with HTTP range requests. Everything else that is h264 video (playable="remux") is remuxed on raw with HTTP range requests. Everything else that is h264 video (playable="remux") is remuxed on
the fly to HLS with **video stream-copy** (cheap, I/O-bound — no video re-encode) and audio to AAC the fly to HLS with **video stream-copy** (cheap, I/O-bound — no video re-encode) and audio to AAC
when needed. Non-h264 video (playable="transcode": HEVC/VP9/AV1/MPEG-2/XviD…) needs a full re-encode when needed. Non-h264 video (playable="transcode": HEVC/VP9/AV1/MPEG-2/XviD…) needs a full re-encode
to H.264: on the prod host that runs on the Intel iGPU via VAAPI (`h264_vaapi`, near-zero CPU), with to H.264: on the prod host that is encoded on the Intel iGPU via VAAPI (`h264_vaapi`) — decoded there
software libx264 as the automatic fallback where the render node is absent or the source codec isn't too when it can, else decoded on the CPU and uploaded (VC-1, XviD, DivX) — with software libx264 as
hardware-decodable. Same HLS session/seek machinery either way — only the video encoder differs. the fallback where the render node is absent, for a source neither GPU route takes (AV1, 10-bit SDR),
and when a GPU session produces nothing. Same HLS session/seek machinery either way.
Seek model (Jellyfin-style): one ffmpeg session per item, started at a given offset via `-ss` Seek model (Jellyfin-style): one ffmpeg session per item, started at a given offset via `-ss`
(fast keyframe seek). A seek beyond the generated region restarts the session at the new offset, (fast keyframe seek). A seek beyond the generated region restarts the session at the new offset,
@@ -501,16 +502,17 @@ def _want_hw(db: DbSession, item: PlexItem, transcode: bool, src: Path) -> bool:
return _select_encode(db, item, transcode, src)[0] return _select_encode(db, item, transcode, src)[0]
# CPU-nice increment for the SOFTWARE transcode child. The Plex ffmpeg is a Popen in the long-lived API # CPU-nice increment for a transcode child that works the CPU: a software encode, or a CPU decode
# process (which must NOT itself be niced), so — unlike the download worker's _deprioritize (run once in # feeding the GPU encoder. The Plex ffmpeg is a Popen in the long-lived API process (which must NOT
# its own main()) — we lower priority per-child via preexec_fn in the fork. The GPU path needs none of # itself be niced), so — unlike the download worker's _deprioritize (run once in its own main()) — we
# this. Reuses the download child's nice default so all co-located re-encoders share one handicap. # lower priority per-child via preexec_fn in the fork. The full-GPU path needs none of this. Reuses
# the download child's nice default so all co-located re-encoders share one handicap.
_SW_TRANSCODE_NICE = settings.download_child_nice _SW_TRANSCODE_NICE = settings.download_child_nice
def _sw_child_preexec() -> None: def _sw_child_preexec() -> None:
"""Runs in the forked child (POSIX) before exec: renice this software-transcode ffmpeg down so it """Runs in the forked child (POSIX) before exec: renice this CPU-bound transcode ffmpeg down so
can't starve the co-located API / Postgres / playback. Best-effort — a failed renice must not fail it can't starve the co-located API / Postgres / playback. Best-effort — a failed renice must not fail
playback.""" playback."""
try: try:
os.nice(_SW_TRANSCODE_NICE) os.nice(_SW_TRANSCODE_NICE)