Commit graph

103 commits

Author SHA1 Message Date
s8n
4750a2c4cc docs(34): import johnny-harris assange-guilty-plea-20220510 to stock Jellyfin Education
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
2026-05-12 15:53:42 +01:00
s8n
c8a1305da4 docs(33): import 2 YT videos to stock Jellyfin Education
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Single-video imports per playbook §1d (collectionType=movies):
- Johnny Harris — Why the US is deporting so many people (20251031)
- The Guardian — NSA whistleblower Edward Snowden (20130709)

Snowden run exposed Jellyfin's single-file channel folder caveat:
MovieResolver parses folder name as item title when only one media file
exists. Worked around with PUT /Items/<id> Name + LockData=true.
Documented in the run log for future hardening into playbook §1d.
2026-05-12 15:48:24 +01:00
s8n
c391447a9f doc 32: wipe jellyfin-dev container + config (200MB)
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
2026-05-11 17:28:43 +01:00
s8n
4ab8c277da docs(playbooks): point at beta-flix for procedural docs
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Procedural playbooks (READMEs, helpers) moved to git.s8n.ru/s8n/beta-flix
and rewritten for stock Jellyfin 10.11.8. Per-iteration runs/ and
CHANGELOG.md stay here as history. Replace the three top-level READMEs
with pointer stubs.
2026-05-11 16:00:12 +01:00
s8n
690ea117c3 docs(32): import lex-fridman-podcast s01 (5 eps) to stock Jellyfin Podcasts
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
2026-05-11 15:45:23 +01:00
s8n
6e336d1798 docs(31): import benn-jordan s01 (4 eps) to stock Jellyfin Educational
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
2026-05-11 15:28:42 +01:00
s8n
93b9c9d533 docs(30): stock Jellyfin 10.11.8 rebuild on tv.s8n.ru
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Brand-new container, brand-new volumes, ZERO ARRFLIX customisation.
Sister instance to prod (10.10.3, untouched) and dev (10.11.8 + scyfin).

P1+P2 from the rebuild plan: Movies + TV Shows libraries added via API,
library scan complete (4 movies / 12 series / 230 eps). Auto-scrape
matched 10/12 series + 4/4 movies to canonical TMDB IDs without manual
intervention; 3 unmatched are TMDB-absent indie content.

No theme, no shim, no CustomCss, no plugins, no user import, no home-
section seed — owner explicitly asked for a stock baseline.
2026-05-11 04:15:18 +01:00
s8n
9f3483a87c docs(29): jellyfin 10.10.3 -> 10.11.8 + scyfin theme migration on dev
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Documents:
- Staged migration path (10.10.3 -> 10.10.7 -> 10.11.8). Direct skip is
  unsupported per jellyfin#15027/#15244/#15293/#15504.
- Snapshots at /home/user/snapshots/.
- Schema diffs (library.db consolidated into jellyfin.db, ffmpeg 7.1.3).
- Theme swap: Cineplex v1.0.6 -> scyfin OLED (the only top-tier 10.11.x
  theme with active 10.11 branch + zero open compat bugs).
- The 10.10.3 home-section bug (CustomPrefs write but no HomeSection row)
  is fixed in 10.11.8 when POST body includes a HomeSections array.
  Verified end-to-end with test user.
- Outstanding work before promoting dev to prod.
- Rollback procedure (EF Core migrations are forward-only).
2026-05-11 03:42:37 +01:00
s8n
0122de7041 bin: add fix-home-db.sh — direct SQLite seed for HomeSection table
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
The Jellyfin 10.10.3 REST endpoint won't INSERT HomeSection rows for the
'Jellyfin Web' client (only updates existing rows), so users whose web-
client DisplayPreferences was created without explicit home-section state
fall back to factory defaults that include Next Up. Applying CustomPrefs
via API doesn't help — the web client reads HomeSection rows.

Direct DB seed: for every DisplayPreferences row with zero HomeSection
rows, insert [Resume, LatestMedia, None*8]. Also replace Type=7 (NextUp)
with Type=0 (None) across all DPs. Idempotent.

Applied 2026-05-11 across prod (13 users) and dev (1 user). Verified
yummyhunny home now shows Continue Watching above Latest Media.
2026-05-11 03:20:24 +01:00
s8n
4f0d34fc93 fix(home-layout): write to client='Jellyfin Web' not just 'emby'
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
The Jellyfin 10.10.3 web client reads DisplayPreferences with the client
name 'Jellyfin Web'. The legacy 'emby' value is read by older SDKs only —
so writing only to 'emby' (the original script) updates the DB but has no
visible effect on the web UI. The empty 'Jellyfin Web' doc falls back to
factory defaults that include Next Up.

Now patches all four per-client docs ('Jellyfin Web', 'emby', 'emby-mobile',
'emby-web') and ensures both Continue Watching (resume) and Latest Media
are present so blank users don't fall back to defaults.

Applied 2026-05-11 across prod (13 users x 4 clients) and dev (1x4).
2026-05-11 03:16:23 +01:00
s8n
e686cc07e0 bin: add set-home-layout.py — disable Next Up, ensure Continue Watching
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Applied 2026-05-11 across both prod (13 users) and dev (1 user).
Replaces 'nextup' homesection slot with 'none' on every user; seeds 'resume'
at slot 0 for users whose CustomPrefs was empty (would have fallen back to
the Jellyfin factory default that includes Next Up).

Idempotent — re-running is a no-op once converged.
2026-05-11 03:01:42 +01:00
s8n
a6ce8451fa docs: mark OpenSubtitles creds as set (verified Caveman5 / CredentialsInvalid=false)
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
- ROADMAP H1 dropped (was: signup at .com); renumbered H2→H1 (GPU), H3 new (AV1 sweep)
- Removed OpenSubtitles row from Blocked table
- ADMIN-GUIDE snapshot: creds set, 20 dl/day free tier
- docs/03 §8: PENDING → SET (verified 2026-05-11)
- docs/13 Finding 04: R → G (resolved)
- docs/27 H1/H2 renumbered (GPU + backup)
2026-05-11 00:31:03 +01:00
s8n
7eb5f346fd subs: add source-priority tier ladder, accept original-release bitmap as tier 2
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Original-release bitmap subs (PGS, VobSub, dvd_subtitle) are first-class,
not stop-gaps. They're the canonical studio render — bitmap encoding is
just a format choice, not a quality compromise. OCR'd or AI-rebuilt
sidecars introduce transcription error that the source doesn't have.

STYLE.md changes:
- New "Source priority" section with 4 tiers: original text > original
  bitmap > trusted text rips > WhisperX rebuild.
- "What lands on disk" loosened: at least one English stream (embedded
  OR sidecar), keep embedded codec as-is, sidecar still .srt.
- New "OCR bitmap -> text" section documenting pgsrip recipe as an
  optional UX-nicety augmentation, not a correctness fix.
- "Why these rules" now explains why original > pretty (esp. for older
  shows like Futurama S1-3 / early Archer where the master is the only
  authoritative source and upscale artifacts already dominate).

STOPGAP-SUBS.md: header note clarifying bitmap-from-disc is NOT a
stop-gap; lists Lilo & Stitch (2002) and Archer (2009) S02 as examples
of correct-as-shipped library entries.
2026-05-10 21:22:32 +01:00
s8n
5b80cfd095 playbooks/import-media: v1.1 — fix two Jellyfin endpoint bugs + nullstone alias
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
Step 4 rewritten: /Library/Refresh is a silent no-op on this build,
must POST to /ScheduledTasks/Running/<scan-task-id> directly. Old
endpoint moved to known-broken table.

Step 5 rewritten: /Items/Counts is scope-cached and stays stale
even after items are indexed. Use /Shows/<id>/Episodes?Season=<NN>
as authoritative verify with provider + image-tag checks.

Both bugs surfaced in archer-s02-2009 run. LibraryMonitor inotify
auto-fire also confirmed broken (failed on lilo-stitch-2002 and
archer-s02-2009 runs).

Replaced user@192.168.0.100 (LAN IP, RFC1918 — flagged by
gitleaks lan-ip-rfc1918) with user@nullstone throughout. SSH config
already aliases nullstone -> 192.168.0.100. Aligns with CLAUDE.md
two-file doc rule: IPs belong in SYSTEM.md, not operational docs.
2026-05-10 06:49:17 +01:00
s8n
fcac178882 chore(ci): add gitleaks secret-scan workflow
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
2026-05-10 06:29:45 +01:00
s8n
22f87d9075 Add AGPL-3.0 LICENSE 2026-05-10 06:21:06 +01:00
s8n
c10a3987a7 subs: AD 49/58 -> 58/58 (closed 9 gaps via OS REST) 2026-05-10 06:14:14 +01:00
s8n
54997e54a1 subs/coverage refresh + fix DEFAULT_OUT path post-rename
audit-coverage.py DEFAULT_OUT still pointed at processes/subtitles/
COVERAGE.md after the processes/->playbooks/ rename. Updated to
playbooks/subtitles/COVERAGE.md.

Refreshed COVERAGE.md to current state (230 episodes total, was 197):
  + Archer (10) — OK-EMBED
  + Maul Shadow Lord (10 new 2160p) — OK-EMBED
  + Maul [Before Upscale] (10 split) — OK-EMBED
  + Lilo & Stitch movie — sidecar OK
  + 9 American Dad gaps closed by owner since last audit
  + Big Lez / Donny & Clarence / Mike Nolan now 100%

Strict-sidecar shows: Sassy, Big Lez, Donny & Clarence, Mike Nolan,
American Dad (84% partial — 9 gaps remain). Other shows OK-EMBED
which is playable but not STYLE.md-compliant — fetch optional.
2026-05-10 06:03:26 +01:00
s8n
3079f5009b playbooks/import-media: log Archer S02 (2009) run
13 eps imported, all matched on TVDB/IMDb. Run notes flag two
new playbook bugs found: /Library/Refresh is a silent no-op (must
trigger /ScheduledTasks/Running/<id> directly) and /Items/Counts
is scope-cached (use /Shows/<id>/Episodes?Season=N for verify).
Pending follow-ups recorded for v1.1 playbook revision.
2026-05-10 05:06:05 +01:00
s8n
cb9d5db1ce doc 32: nullstone storage upgrade plan
R&M S02-S08 import (~105GB) blocked on disk pressure (post-import
117GB->12GB free). Plan upgrade first.

Hardware probed: MSI X470 Gaming Plus Max, single Intel 512GB NVMe in
M2_1, M2_2 free, 6 SATA ports free.

Recommended path: 2TB NVMe in M2_2 (~£130) -> pvcreate -> vgextend
keystone-vg -> lvextend lv-home -> resize2fs. ~5 min downtime, no
reinstall. Alt: SATA SSD/HDD, USB external. Cheapest /GB = SATA HDD.

Doc covers procedure, alternatives, post-upgrade R&M bulk import
plan via playbooks/import-media/. Owner to pick + execute install.
2026-05-10 04:41:52 +01:00
s8n
a30edcfa2f import: archer s01 (2009)
10 episodes, 1080p AI x265 10-bit Joy release, HE-AAC 5.1 ENG +
3x embedded DVDsub (ENG/SPA/FRE) per episode. Per README.md:41
quality bar — AI-upscale acceptable when source doesn't support 4K.

Items/Counts: Series 11->12, Episodes 207->217. Series item
9d22c409d531...

Run log notes 3rd consecutive LibraryMonitor failure — playbook
v1.1 should make manual /Library/Refresh MANDATORY. Same TMDb
auto-match miss as Maul; flagged for owner UI Identify step or
[tmdbid-NNNN] folder token.
2026-05-10 03:51:04 +01:00
s8n
520f0fbee3 maul s01 2160p import + variant 1 detail-page skin
Saved variant 1 "Netflix-cinema" detail-page redesign to
web-overrides/skins/detail-variant-01-netflix-cinema.html for
future reference. Not applied to dev/prod.

Imported Star Wars: Maul - Shadow Lord S01 2160p WEB-DL HEVC SDR
(10 episodes, ~21 GB) per playbooks/import-media/ v1.0. First
"replace-with-comparison" run:

- Existing 1080p upscale renamed in-place to
  "Star Wars - Maul - Shadow Lord [Before Upscale] (2026)/"
  with tvshow.nfo <lockdata>true</lockdata> to suppress Jellyfin
  TMDb auto-merge with the new canonical.
- New 2160p staged on onyx, rsync'd to nullstone canonical path
  /home/user/media/tv/Star Wars - Maul - Shadow Lord (2026)/Season 01/
- Both series live as separate items; Items/Counts bumped
  Series 10->11, Episodes 197->207.

Run log at playbooks/import-media/runs/star-wars-maul-shadow-lord-
2026-2160p.md flags v1.1 follow-ups: document the
[Before Upscale] pattern, rsync resume idempotency, and
[tmdbid-NNNN] folder token for titles that fail auto-match.

ffprobe confirmed E01 = HEVC Main 8-bit 3840x2160 SDR @ 12 Mbps,
EAC3 5.1 ENG Atmos + ITA dub, 4x embedded subrip per episode.
Direct-play on capable clients.

TMDb match failed auto on both folders (recent Disney+ release);
operator to manually identify via UI.
2026-05-10 03:34:34 +01:00
s8n
508fc42a1e next-ep popup: design A (Cinematic Strip) shipped to dev + designs A/B/C archived
Adds web-overrides/popup-designs/ with the 4-up preview (preview.html) and
three standalone candidate designs (a-strip.html / b-terminal.html /
c-minimal.html) so we can revisit the alternates later without re-running
the design generator. Owner picked A.

Design A is wired into Jellyfin's stock .upNextDialog by overriding its
CSS to a full-bleed bottom 26 % strip with white 'Start Now' CTA and a
custom SVG countdown ring that mirrors .upNextDialog-countdownText. The
DOM stays intact so Jellyfin's own countdown timer and click handlers on
.btnStartNow / .btnHide keep working untouched.

Shim is bracketed by NEXT-EP-POPUP-BEGIN / NEXT-EP-POPUP-END markers
inside the existing ARRFLIX-SHIM block in web-overrides/index-dev.html.
Only deployed to dev (dev.arrflix.s8n.ru) for spot-check; promote to
prod once verified by editing prod's index.html the same way and
redeploying via the nsenter trick.

Adds bin/revert-next-ep-popup.sh — sed-deletes between markers, defaults
to dev with --prod flag for prod target. Saves a timestamped backup and
prints the redeploy command.
2026-05-10 02:44:40 +01:00
s8n
24a9497e7d playbooks/ rename + import-media v1.0 + lilo&stitch run
processes/ -> playbooks/ (git mv preserves history; updated cross-refs
in ROADMAP, README, subtitles playbook + scripts).

playbooks/import-media/README.md v1.0 — 7-step import workflow:
  stage on onyx -> rsync to nullstone -> chmod -> verify scan ->
  Items/Counts bump -> optional subtitle pass -> run-log
Cross-references docs/05/07/08, ADMIN-GUIDE, README. Mirrors the
existing subtitles playbook structure (CHANGELOG + runs/_template).

CHANGELOG v1.0 lists known gaps (bin/cleanup-import.sh and
bin/normalize.py still doc-only, ROADMAP M6).

First run logged: playbooks/import-media/runs/lilo-stitch-2002.md.
Lilo & Stitch (2002) imported to /home/user/media/movies/, item
c2f4aff133c1b9631500fadf293b0b2f, TMDb 11544, MovieCount 3 -> 4.
LibraryMonitor didn't auto-fire — needed manual /Library/Refresh;
playbook updated to make this an unconditional step.

Source: 1080p BluRay HEVC 10-bit / EAC3 5.1 / 2x PGS embedded subs.
Per quality bar (README.md:41) — passes.
2026-05-10 02:29:57 +01:00
s8n
c6ec208520 processes/subtitles: COVERAGE.md live audit + auto-refresh on fetch
Adds lib/audit-coverage.py: queries Jellyfin live for every series, every
episode, and every movie; classifies each by whether the English subtitle
comes from a sidecar, embedded stream, or doesn't exist; renders a
Markdown report with one-char-per-episode bars for visual scanning. Output
file is processes/subtitles/COVERAGE.md, regenerated on demand.

v2 sub-rest-fetch.py and v3 sub-a7d-fetch.py now invoke the audit at end
of a successful run, so the committed coverage file stays in sync with
library state without manual intervention. v3.5 yt-fetch path skips the
auto-call since it doesn't speak to Jellyfin directly; run audit manually
after copying YT sidecars to nullstone.

README.md surfaces the audit at the top so anyone landing in the recipe
folder sees current state before starting a run.
2026-05-10 02:19:32 +01:00
s8n
fba9a5bfeb processes/subtitles: STOPGAP-SUBS.md tracker for v3.5 → v4 cross-ref
Owner accepted Sassy the Sasquatch S01 v3.5 YouTube-auto-CC subs as
'85 % acceptable, fine, not great' but flagged them for v4 WhisperX
rebuild. Adds a single worklist file (STOPGAP-SUBS.md) so every show
that ships via the v3.5 path gets logged for the eventual v4 sweep
instead of being silently forgotten.

Sassy run log gets a STOP-GAP banner at the top pointing to the new
worklist. README.md gets a stop-gap-exception note alongside the
STYLE.md hard-prereq paragraph. ROADMAP H5 now points at the worklist
file as the canonical source of which shows v4 needs to regenerate.
2026-05-10 01:18:27 +01:00
s8n
eb71cf6beb processes/subtitles: v3.5 YouTube auto-CC stop-gap + Sassy 5/5
Adds lib/sub-yt-fetch.sh (yt-dlp wrapper) and lib/yt-clean.py (collapses
YouTube's rolling-window auto-caption VTT into a flat SRT). For shows
distributed YouTube-first that have no community subs anywhere -- verified
via three parallel research agents covering OpenSubtitles REST, OS legacy,
Addic7ed, SubDL, SubSource, and Podnapisi for the 5 niche shows in the
library, plus a price-vs-coverage analysis of OpenSubtitles VIP.

Findings: OS VIP would not have helped on the niche shows (it is
download-cap relief, not coverage unlock; same catalog as free). All 4
Jarrad Wright shows in the library (Sassy, Big Lez Saga, Donny &
Clarence, Mike Nolan) live on the same channel and have only YouTube
auto-CC available. v3.5 ships those, explicitly violating STYLE.md
'best quality' as a tracked stop-gap.

Sassy the Sasquatch S01 5/5 episodes subbed with cleaned auto-CC. Mike
Nolan special-case noted: a 'COMPLETE SEASON | SUBTITLES' YT upload from
Oct 2025 carries hand-typed CCs and should be preferred over per-episode
auto-CC when subbing that show.

ROADMAP H5 added: v4 WhisperX large-v3 on the friend RTX 4080 node will
regenerate the v3.5 stop-gap with proper-noun-prompted transcription
(~4-6%% WER vs ~12%% YT auto-CC) and restore the STYLE.md quality bar.
H1 OpenSubtitles credentials marked done (was completed 2026-05-09).
2026-05-10 01:05:07 +01:00
s8n
d9d6bdba64 testing/ folder: theme-edit guides + error catalog + headless recipes
7 docs in /testing/ — institutional memory after 6+ regressions in
24-48h on the v6 theme. Read before any edit.

  README.md           — index + quickstart
  THEMING.md          — safe-edit checklist + layer/specificity tables
  ERROR-PATTERNS.md   — 12 cataloged patterns (Symptom/Cause/Diag/Fix/Prev)
  HEADLESS-PROBE.md   — 11 playwright recipes (md5 chain, darkPct,
                        ancestor bg sample, dropdown listItem probe)
  ROLLBACK.md         — 8 emergency revert recipes (overlay, branding,
                        encoding, full-from-repo, dev-clone-prod,
                        git-revert, pw-reset, bind-mount inode-swap)
  SMOKE-TEST.md       — manual + headless verify checklist
  DEPLOY.md           — dev → prod promotion workflow with backup +
                        chown root + restart inode-swap

Empty subdirs: snipUSER-Es/, recipes/, incidents/ (post-mortems land here).

Goal: stop reinventing the same fixes. Catalog every error class,
codify the recovery, build a skills folder for future ARRFLIX work.
2026-05-10 00:47:20 +01:00
s8n
755088e7fc selector v4 hairline ring + reconcile prod drift + skin alt v2
Stock Jellyfin paints .listItem.selected and .focused in cyan
(#00a4dc) on actionSheet/selectionList/dialogContainer dropdowns.
Clashes with Cineplex red on audio + subtitle pickers.

Pick: variant 04 "Hairline ring" — 1px #E50914 outline (offset -1px
inside row) + dark near-black bg + white text. Architectural,
quietly on-brand. Applied via body.arrflix-themed scope.

Saved alternative: web-overrides/skins/selector-variant-02-red-
underline.css — variant 02 "Red underline" (matches search-input
focus). Future skin/swap option per owner. Not currently active.

Reconciled prod drift: prod overlay had been externally modified
since last snapshot (md5 2da61583 -> c62898). Pulled prod's current
overlay as new repo baseline + snapshot. Then applied variant 04 on
top -> dev md5 0ab8b258 (= prod + v4). Prod still c62898 untouched.
2026-05-10 00:28:55 +01:00
s8n
7ce1539ea7 processes/subtitles: STYLE.md — USER-G style for picks + display
Codifies the bar every fetch must hit:
- one plain English .srt per episode, named <base>.eng.srt
- drop SDH / HearingImpaired / MachineTranslated / AiTranslated / Forced
- prefer fps-matched, then highest download count
- UI label collapsed to just "English" via web-overrides shim

README.md now points at STYLE.md as a hard prereq before any fetch run.
The picker logic in v1/v2/v3 helpers already encodes these rules; STYLE.md
is the canonical source of truth that they should be checked against if
anyone (or future-me) is tempted to relax them.
2026-05-10 00:13:51 +01:00
s8n
b3ead71b7e shim: shorten subtitle stream labels in detail dropdowns
Stock Jellyfin renders subtitle stream entries as
"<lang> - <CODEC> - (External|Internal|Embedded)[ - flag]". For ARRFLIX,
which has at most one subtitle format per language, the codec and source
suffix are noise. Add a small JS shim to web-overrides/index.html that
matches that exact shape and collapses the label to "<lang>" (with
"(Forced)" / "(SDH)" / "(Hearing Impaired)" if those flags are
present; "Default" is dropped since it's redundant when there's only one
stream of that language).

Audio labels like "5.1Ch Surround Sound - English - AAC - Stereo - Default"
have a different number of segments and don't match, so they pass through
untouched.

The shim runs after DOMContentLoaded and re-walks any nodes added later
via MutationObserver (covers actionsheet dropdowns that mount lazily).

Bracketed by /* SUB-LABEL-SHIM-BEGIN */ and /* SUB-LABEL-SHIM-END */
markers; bin/revert-sub-label-shim.sh deletes between the markers in one
sed pass and saves a timestamped backup. No container restart needed
(index.html is bind-mounted).
2026-05-10 00:05:16 +01:00
s8n
43f55643be processes/subtitles: v3 Addic7ed fetcher + AD 49/58 subbed
Adds lib/sub-a7d-fetch.py: free, no-daily-cap path via subliminal's
addic7ed provider (anonymous). Uses OpenSubtitles REST search-only (no
quota cost) to translate library S/E to the show's primary catalogue
numbering, then drives subliminal to download from Addic7ed and writes
sidecars direct to nullstone via SSH.

Picker quirks: subliminal series-name matcher is broken by '!' in the
title, so the script strips it before building the synthetic
Video.fromname() string. OS feature_details S/E happens to align with
Addic7ed's indexing for the test show (American Dad).

Recipe README now reflects three paths in cheapest-first order: v3
Addic7ed, v2 OS REST (20/day), v1 plugin. American Dad run log updated
to 49/58 (S01 7/7 v1, S02 16/16 mixed v2/v3, S03 16/19 v3, S04 10/16
v3). 9 misses identified, deferred to next OS REST quota window.
2026-05-09 23:31:10 +01:00
s8n
23520df2df processes/subtitles: v2 REST fetcher + AD S02E01-E12 subbed
Adds lib/sub-rest-fetch.py: direct OpenSubtitles REST, looks up subs by
per-episode IMDB id (e.g. tt0511631) instead of the plugin's
(parent_imdb_id, season, episode) combo path. This sidesteps shows where
library numbering diverges from OpenSubtitles' catalogued numbering --
American Dad uses Hulu S1=7 eps; OS uses Fox S1=23 eps; the plugin path
returns 0 hits past S01E07 even though every per-episode IMDB id is
correct.

Recipe README updated to surface the two paths (v1 plugin / v2 REST) and
recommend v2 by default. American Dad run log now shows 19/58 episodes
subbed (S01 7/7 via v1, S02E01-E12 via v2). S02E13-S04 (39 eps) deferred
to next 20/day quota windows.

Quirk fixed in v2: OpenSubtitles /download endpoint consistently returns
HTTP 503 to Python urllib.request despite identical headers/body via curl.
_curl() shim routes all OS API calls through curl. Each 503 still
consumes a download slot, so urllib path was unsafe to retry on.
2026-05-09 23:09:09 +01:00
s8n
fedf3388b8 processes: subtitle acquisition v1 + AD S01 run
Adds processes/ umbrella for repeatable acquisition workflows. First child
is subtitles/, with recipe README (executable by Claude Code), CHANGELOG,
per-show run logs, and a tested helper at lib/sub-fetch.sh.

Run on American Dad: S01 (7 eps) passed, S02-S04 (51 eps) broke. Library
uses Hulu/DSP season ordering; OpenSubtitles indexes by Fox airing order;
plugin queries by (parent_imdb_id, season, episode) so library S02E01
returns 0 hits. v2 design = direct OpenSubtitles REST with per-episode
imdb_id lookup; pending API-key registration.
2026-05-09 22:56:31 +01:00
s8n
1ed55152b7 fix: drop video z-index hack + heavy comments + doc 31 layer model
Image-12 incident: I'd set <video> z-index:9999, which covers the OSD
scrubber + buttons (Jellyfin's stock OSD controls live at z-index
1100-2000, above the 1000 of .videoPlayerContainer but BELOW our
9999). Drop the lift entirely. Stock z-index hierarchy already has
controls floating on top. The fix for black-screen was always
transparent ancestor backgrounds (L1+L2), never z-index.

Reorganized inject-middle-theme.py CSS string from one-line dense
concat into a triple-quoted multi-line block with header comments
explaining each section + the layer model + DO-NOT rules. Same
output bytes (verified md5 deterministic). Added long-form comment
header to JS too.

Doc 31 (new): "Theme layer model + edit guide" — comprehensive
checklist for any future CSS edit. Covers:
  - Stacking order layer 0..7 with stock Jellyfin z-indexes
  - The two body classes (.arrflix-themed, .arrflix-video-active)
  - Specificity tiers + cascade order (L1 vs L2)
  - CSS load order (inline < bundle < branding.xml)
  - Recurring bug list (6 incidents now, all same anti-pattern)
  - DO NOT DO foot-gun list
  - 4-step smoke verify procedure
  - CI gates still TODO

Snapshot bumped to md5 2da61583. Prod+dev byte-identical.
2026-05-09 22:41:51 +01:00
s8n
4f13db63f9 fix v7: html-bg pin + correct video class names
Two bugs in prior video-isolation fix (452ce68):

1. .htmlVideoPlayer wrong class — Jellyfin actually uses
   .videoPlayerContainer + <video class="htmlvideoplayer"> (lowercase).
   :has(.htmlVideoPlayer) never matched, so L1 :not(:has) was always
   true — body never excluded from #000 paint, L2 (lower specificity
   0,2,1) lost to L1 (0,3,1). Replaced :has() gate with :not(.arrflix
   -video-active) — JS body class is reliable signal.

2. html background-color stayed transparent on details/video pages
   despite 5 stylesheet rules saying #000 !important. Headless Chrome
   reported computed bg as rgba(0,0,0,0) — root canvas propagation
   bug or cascade quirk. Fix: JS shim sets inline style on
   <html> with !important. Inline styles win against any stylesheet.

Result: html paints #000 (so letterbox bars are black not white),
body transparent during playback (L2 wins on equal specificity via
source order), videoPlayerContainer + <video> transparent → frames
visible. Off-video: body opaque #000 (L1 fires).

Verified DOM probe: html bg rgb(0,0,0), body rgba(0,0,0,0) when
arrflix-video-active. Both flip when class removed.
2026-05-09 22:33:45 +01:00
s8n
452ce68d7a isolate video player against opaque-bg regressions (recurring INC class)
Two-layer defense for the recurring "black screen during playback"
bug class (5+ occurrences in 24h per doc 26/28/30):

L1 (prevention): scope every black-bg rule with
:not(:has(.htmlVideoPlayer)):not(:has(#videoOsdPage)) so the rules
self-disable while a player is in the DOM. Covers body,
#reactRoot, .skinBody, .backgroundContainer, .mainAnimatedPage,
.mainAnimatedPages, .pageContainer.

L2 (override): when JS-toggled body.arrflix-video-active is set,
high-specificity (0,3,2 + tag) transparent rule wins against any
ancestor opaque-bg rule (including future regressions someone adds
without scoping). Covers all known wrappers, the
videoPlayerContainer + videoPlayerContainer-onTop, #videoOsdPage,
.libraryPage, .htmlVideoPlayer.

L3 (z-index lift): force .htmlVideoPlayer + child <video> to
z-index:9999 + isolation:isolate during playback so it floats above
any opaque ancestor that still leaks through.

Verified in playwright: with arrflix-video-active + .htmlVideoPlayer
mounted, all 7 ancestors return rgba(0,0,0,0). Without — all 7
return rgb(0,0,0). Self-disabling works.

Lesson reinforced (doc 30 roadmap open): add darkPct assertion to
bin/headless-test-v2.py + xmllint CI gate. Five recurrences without
those gates says we keep relearning this. TODO next.
2026-05-09 22:18:31 +01:00
s8n
e9d209da73 polish: pure-black bg + search bar fix + variant E nav glow + My Media hide
Grey #101010 stripe at bottom of pages: jellyfin-web theme.css:44-50
sets .backgroundContainer + html to #101010, neither Cineplex nor
prior overlay overrode it. Added rule forcing #000 across html, body,
.backgroundContainer, .skinBody, .mainAnimatedPages, .pageContainer,
#reactRoot under body.arrflix-themed.

Search input: stock cyan focus ring (#00a4dc, theme.css:262-272)
swapped for borderless slab + 2px red bottom on focus + soft red
box-shadow halo. Cineplex/Netflix-faithful.

Movies/Series nav: variant E "cinematic glow" picked from preview.
Active state = red text + text-shadow halo + font-weight 700. JS
hash matcher toggles .arrflix-nav.active when location.hash matches
#/movies.html or #/tv.html (and short forms). hashchange listener +
existing setInterval keep state in sync.

My Media row: .homePage .homeSectionsContainer .verticalSection.section0
hidden — matches prod which had it hidden via different mechanism.

Snapshot at snapshots/2026-05-09-v6-stable/index.html bumped to
md5 2c8f5d5f7c99611fa93d15c34fbe35d1; same as prod and dev.
2026-05-09 21:47:00 +01:00
s8n
92e2426734 hide back arrow on themed pages
Stock Jellyfin shows .headerBackButton on library pages
(Movies/Series). With our nav links already in headerLeft, the back
arrow is redundant clutter and confuses the home button intent.

Add .headerBackButton to the existing display:none rule under
body.arrflix-themed. Verified visually on dev (md5 c99aca0f), then
shipped to prod with overlay swap (md5 364cc890 -> c99aca0f). Both
sides byte-identical.

Snapshot at snapshots/2026-05-09-v6-stable/index.html updated.
2026-05-09 19:43:27 +01:00
s8n
a943933363 doc 30 v6-stable success + snapshot save state
Owner pronounced "near perfect". Save current state as the rollback
target. Replace older 2026-05-08-pre-elegantfin snapshot.

Snapshot md5 364cc890c58f02d07cf50b43b31a48f0 — matches both prod
and dev deployed overlay.

Doc 30 lists every file/path-of-record + rollback procedure +
remaining roadmap items.

Tag this commit v6-stable-2026-05-09 after push.
2026-05-09 12:52:44 +01:00
s8n
9003b55c81 favicon hijack + tonemap fix shipped to prod
Favicon: prod's older lockFavicon() shim was clobbering our injected
A-logo <link> tags every head mutation. Tag our links with
data-arrflix-icon="A" + add a hijack IIFE that re-pins the A URL on
matching tags AND removes any other large data:image/png link tags.

Tonemap: encoding.xml flipped EnableTonemapping false to true on dev
+ prod (server-side, not in repo). Doc 21 documented this fix
2026-05-08; prod was still grey-washing HDR10 sources because
setparams was relabeling PQ pixels as bt709 without zscale + tonemap
conversion. API now reports EnableTonemapping=True. Next HDR10
transcode gets the proper zscale -> tonemap -> format ffmpeg chain.

Both verified on dev first then promoted. Prod overlay md5 c6c85076
to 364cc890. dev and prod overlay byte-identical.
2026-05-09 10:17:40 +01:00
s8n
83fbfbf35e readme: add 3 ARRFLIX screenshots (detail, playback, search)
assets/screenshots/01-search.png       — search + suggestions list
assets/screenshots/02-detail-mandalorian.png — Mandalorian detail w/ backdrop
assets/screenshots/03-playback-sassy.png      — Sassy the Sasquatch playback

Embedded in README.md above 'What you get' so the repo landing page on
git.s8n.ru reads as a finished product rather than text only.
2026-05-09 10:10:38 +01:00
s8n
0c7d0aef14 middle-theme v6 + branding.xml video escape
Add ARRFLIX wordmark center, Movies/Series nav left, search right,
favicon=A-mark, auth gate so login stays stock, hide on video page.

Side-effect of branding.xml escape (<video> → &lt;video&gt;): prod's
CustomCss block now actually loads, so the INC7 transparent-video
rule reaches the browser. /Branding/Css.css 0 B → 36 256 B; doc-28
black-screen issue closed at the delivery layer.

Markers: ARRFLIX-MIDDLE-THEME-BEGIN/END (style + script) and
ARRFLIX-FAVICON-BEGIN/END (link). Idempotent.

See docs/29 for design + deploy procedure + recovery quirk.
2026-05-09 04:01:49 +01:00
s8n
3d388e8de7 doc 28 INC7-final: CSS overlay covering <video> was actual cause
Agent 6 applied SW-pin fix and marked verified via element state
(currentTime advancing, videoWidth=1920, readyState=4). Headless pixel
histogram still showed darkPct=100% — element decoded fine but CSS
overlay covered it.

Real cause: branding.xml BLACK-PASS paints .libraryPage with
#000 !important. Jellyfin OSD page renders <div id=videoOsdPage
class=libraryPage>; class match -> opaque black div above <video>.

Fix: extend transparent-scope using :has(.htmlVideoPlayer) +
#videoOsdPage selector. Post-fix darkPct=9.8% (was 100%), MNS S1E4
video frame visually paints.

Removed INC6 clear-cache-only middleware (no longer needed, was
burning HTTP cache every visit).

bin/apply-26-incident-fixes.sh extended with INC7 patch (idempotent
re-apply if branding.xml ever drifts back).

Lesson: video-element state alone is insufficient verification.
Always sample pixel histogram + canvas drawImage on the painted
viewport.
2026-05-09 03:04:41 +01:00
s8n
a4ababcfbf doc 28: record commit hash + nullstone backup path for INC7 fix 2026-05-09 02:50:21 +01:00
s8n
3a7d96aacb doc 28 + INC7: fix prod black-screen via SW cache pin
Five sibling agents converged on root cause:
jellyfin-asset-immutable Traefik router (priority 90) was matching
/web/serviceworker.js (Jellyfin PWA's actual SW filename), pinning it
with Cache-Control: public, max-age=31536000, immutable. The
priority-100 jellyfin-html-nocache router only excluded the literal
path /web/sw.js, missing serviceworker.js.

Stale SWs from earlier ARRFLIX iterations intercepted /Videos/* and
/web/* fetch events, returning cached/empty bytes. Result:
MediaSource appendBuffer got bad data -> black <video>. INC6's
Clear-Site-Data: "cache" couldn't fix it (per MDN spec, "cache"
excludes SW registrations; "storage" would have worked).

Fix: added jellyfin-sw-nocache router at priority 250 in
/opt/docker/traefik/config/dynamic.yml on nullstone, forcing
cache-no-store@file on /web/serviceworker.js + /web/sw.js. Hot-reload
via Traefik file provider, no docker restart.

Verified at the wire (curl -I /web/serviceworker.js now returns
no-cache, no-store, must-revalidate; main.jellyfin.bundle.js still
immutable as intended) and via headless Chromium probe of MNS S1E4
(33s of currentTime advance, readyState 4, videoWidth 1920x1080,
no errors, both s8n admin and USER-F user).

bin/prod-vs-dev-compare.py also lands as a one-shot diff helper used
during the investigation.
2026-05-09 02:50:00 +01:00
s8n
af2d625e4f doc 27: status snapshot 2026-05-09 visual
Visual status board: symptoms killed (8/8), roadmap (done /
pending / high / medium / deferred / strategic), library codec
matrix, repo file tree, next-click. Point-in-time after doc-26
incident closed. For ongoing roadmap see ROADMAP.md.
2026-05-09 02:23:53 +01:00
s8n
648e5d5238 doc 26 INC6: Clear-Site-Data cache-only for shim deploy
INC5 fmp4-disable shim required browser hard-reload to fire. Owner's
MNS S1E4 re-test still black-screened because cached index.html ran
old shim + fmp4-HLS bug recurred. Add Traefik response header
'Clear-Site-Data: "cache"' on /web/index.html. Cache-only is safe
(no cookie/storage wipe -> auth + localStorage preserved). One fresh
visit refetches index.html with new shim. Remove middleware after
owner confirms working, otherwise every revisit re-flushes cache.
2026-05-09 02:10:52 +01:00
s8n
b5e0d95826 doc 26: add INC5 scrollbar grey-strip subsection
Symptom + root cause + fix + lesson for the scrollbar grey strip at
very bottom of page. Patch already in bin/apply-26-incident-fixes.sh
and live in branding.xml on prod from earlier commit; this is the
documentation subsection that was missing dedicated coverage.
2026-05-09 02:05:16 +01:00
s8n
557317d104 doc 26: case CLOSED — final state + 8 forbidden patterns
All 8 owner-reported symptoms resolved across 5 iterations (INC1-5):

INC1 — index.html drift revert + :has() transparent-scope + Cineplex
       Abspielen override + encoding.xml HLS 499 fix
INC2 — pin .backdropContainer position:fixed (persistent backdrop)
INC3 — extend transparent-scope through detail-page sub-sections
INC4 — .emby-scroller transparent (kill black band behind carousels) +
       EnableTonemapping=false + 20Mbps RemoteClientBitrateLimit cap +
       headless-test-v2.py (admin+USER-F+click-play+bg-sweep)
INC5 — AV1 source re-encode (MNS S1E2/E4/E5 to H.264/AAC) +
       enableHlsFmp4=false localStorage shim +
       ::-webkit-scrollbar styled to ARRFLIX palette

Verification: headless playwright on Chrome + Firefox UA confirms MNS
S1E4 plays 1920x1080 readyState=4 currentTime advancing. Owner
double-confirmed solved.

Doc 26 final state section + 18-item forbidden-pattern checklist added
for future operators.
2026-05-09 02:03:39 +01:00