Adds a movie-scoped "Playback" filter to the Plex feed so a user can isolate items by how they play — the groundwork for collecting HW-transcodable (or no-transcode) versions. Four classes derived from the stored `playable` + a new `transcode_hw` facet: - Direct Play (direct), Direct Stream (remux) - Transcode · HW — playable=transcode AND the iGPU can hardware-transcode it - Transcode · SW — playable=transcode, software libx264 only The HW/SW split is precise, not codec-only: a 10-bit/HDR HEVC is HW-decodable yet breaks the VAAPI nv12 pipeline (the same case plex/stream special-cases), so it must count as SW. Backend: - New PlexItem columns (migration 0077): transcode_hw, video_bit_depth, media_probed_at. Migration backfills transcode_hw codec-only so the filter works immediately. - `plex.sync.transcode_hw_capable` is the single classifier; the HW-decodable codec set moves to sync as `HW_DECODABLE_CODECS` and plex.stream imports it, so the catalog facet and the runtime encoder choice can't disagree by codec. - Bit-depth backfill (`_probe_media_batch`, wired into sync): probes only HW-decodable transcode items' video Stream for bit-depth + HDR and refines a 10-bit/HDR HEVC to software. Bounded per run, watermarked by media_probed_at. A codec change resets the watermark (stale probe). - Filter + self-excluded facet on a shared `playback_class_expr` CASE (movie-leg only — a show is an aggregate with no single class). transcode_hw threaded onto the feed/detail/card JSON. Frontend: - Multi-select "Playback" Combobox in the sidebar (movie scope, facet-empty drop-out), active chips, 4-way corner-dot badge (green/blue/amber/red) + tooltip, and an info-page playback chip with a codec · bit-depth subtitle. New `bg-info` (blue) token, i18n HU/EN parity. - Fix a regression where the card's playback dot lost its hover tooltip: the full-card reveal overlay swallowed the cursor; the dot now sits above it (z-20). Gate: backend full lane + frontend typecheck/lint/knip/prettier/vitest all green. Browser-UAT'd on localdev (filter narrows to the SW set, facet counts, badge colour + tooltip, info chip).
55 lines
2.5 KiB
Python
55 lines
2.5 KiB
Python
"""plex_items playback-class facet (E7: filter the feed by Direct Play / Direct Stream / Transcode)
|
|
|
|
Adds the columns the playback-class filter needs:
|
|
* transcode_hw — for a `playable="transcode"` item, whether the prod iGPU can hardware-transcode it
|
|
(codec HW-decodable AND 8-bit SDR) vs software-only. NULL for non-transcode items.
|
|
* video_bit_depth — the source video bit depth (from the Plex media Stream), NULL until probed.
|
|
* media_probed_at — watermark for the bit-depth backfill (NULL = not yet probed).
|
|
|
|
Backfill for existing rows: `transcode_hw` is derived from the ALREADY-stored `codec_video` alone (the
|
|
same HW-decodable allow-list `plex.sync.HW_DECODABLE_CODECS` / `plex.stream` use), so the filter works
|
|
immediately after migration. Bit depth is unknown for existing rows (the cheap Plex listing doesn't
|
|
carry it), so a 10-bit/HDR HEVC is provisionally 'hw' here and later refined to 'sw' by the bit-depth
|
|
backfill (`plex.sync._probe_media_batch`) once its Stream is probed. `video_bit_depth`/`media_probed_at`
|
|
start NULL.
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0077_plex_playback_class"
|
|
down_revision: Union[str, None] = "0076_media_asset_kind"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column("plex_items", sa.Column("transcode_hw", sa.Boolean(), nullable=True))
|
|
op.add_column("plex_items", sa.Column("video_bit_depth", sa.Integer(), nullable=True))
|
|
op.add_column(
|
|
"plex_items", sa.Column("media_probed_at", sa.DateTime(timezone=True), nullable=True)
|
|
)
|
|
op.create_index("ix_plex_items_transcode_hw", "plex_items", ["transcode_hw"])
|
|
# Codec-only derivation for existing rows — keep this codec set identical to
|
|
# plex.sync.HW_DECODABLE_CODECS. Bit depth is unknown here, so provisionally 8-bit (→ hw for a
|
|
# decodable codec); the bit-depth backfill downgrades 10-bit/HDR HEVC to sw later.
|
|
op.execute(
|
|
"""
|
|
UPDATE plex_items
|
|
SET transcode_hw = CASE
|
|
WHEN playable = 'transcode'
|
|
THEN lower(codec_video) IN ('h264','hevc','h265','vp9','vc1','mpeg2video')
|
|
ELSE NULL
|
|
END
|
|
"""
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_plex_items_transcode_hw", table_name="plex_items")
|
|
op.drop_column("plex_items", "media_probed_at")
|
|
op.drop_column("plex_items", "video_bit_depth")
|
|
op.drop_column("plex_items", "transcode_hw")
|