Apollo v1.6.0
Subtitles, mostly — and one of the fixes came from a viewer noticing something on screen that should never have been there.
Subtitles rendered as they were typeset
Jellyfin converts ASS/SSA to WebVTT for the browser's <track> element and leaves the format's override tags in the text, so the instructions were printed as if they were dialogue:
{\fad(1020,1)\blur1.2\bord2\fnTimes New Roman\pos(688,214)}Fifth Epoch
One file in a real library carried them on 112 of its 1439 lines.
ASS and SSA now render through libass, on a canvas over the video: positions, fonts, colours, outlines, karaoke, animation. SubRip keeps the <track> element it was always fine with, and the picture formats stay burned in by the server.
Where libass is not drawing, the text path is cleaned up rather than left as it was. Override tags are stripped, italics and bold survive as the WebVTT tags that mean the same thing, and \an alignment is honoured — Jellyfin writes line:90% onto every cue it converts, so a sign typeset for the top of the frame was landing on top of the dialogue it was placed to avoid.
\pos is deliberately not honoured on that path. It is in the script's own coordinates and the converted file has dropped the header saying what those are; one library's files declare anything from 1280x720 to 3840x2160, so any assumed resolution is a guess.
It is optional. Settings → Keep the original typesetting, and the same switch in the player's subtitle menu. Turning it off means the WebAssembly is never fetched, not fetched and ignored.
Subtitle size, where you notice it
Size lived only in Settings, which is the wrong place — you find out subtitles are too small while watching, and leaving to fix it means leaving the thing you were judging it against. It is in the player's menu now, and both controls clamp through one function.
With libass drawing, the setting scales the typesetting itself — every style's font size and every \fs override, since the overrides outnumber the styles by roughly three to one. Scaling the canvas was rejected: \pos lives in that coordinate space, so every sign would drift off the thing it labels.
Seasons in running order
Opening One Piece listed its seasons as Specials, 17, 18, 19, 20, 21, 23, 1, 2 … 16, 22. All twenty-four were there and the order was nonsense, because the server returns whatever order it holds them in.
Six of twenty-two multi-season shows in one anime library arrived jumbled, against three of forty-six in a television one — so not an anime problem, just most visible there. Specials go last rather than first: they are season zero, and someone opening a show wants the beginning of it.
What Apollo now ships
libass is ISC, but its WebAssembly build links FreeType and fribidi, so the .wasm is copyleft-covered while Apollo itself is MIT. THIRD-PARTY-NOTICES.md records that, points at the sources, and says how to drop it — the renderer is one dependency and one file, and ASS falls back to the cleaned-up text path without it.
The entry bundle grows by 1.9 kB. The WebAssembly is a separate asset fetched only when an ASS track is actually shown.
Verified
1092 tests, 0 lint errors, clean build. The renderer was checked in a real browser against a hand-written ASS file — position, colour, size, alignment, outlines and time offset — and the fallback path was observed failing over as intended.
Known limitation: the overlay is laid out for the player's default Fit aspect. Under Fill or Stretch, absolutely-positioned signs will not track the cropped picture; dialogue is unaffected.
Full changelog: v1.5.0...v1.6.0