Repository navigation
Pin the H.264 profile so transcoded video actually renders - #305
Conversation
Reported twice as "audio plays, no video", which reads as a broken stream and is an unsupported profile. libx264 inherits the source's pixel format unless told otherwise, so a 10-bit rip came out of the HLS transcode as High 10 -- which Apple's hardware decoder refuses outright. The audio decodes, so the clock runs over a black rectangle. Measured by running the server's exact command over a yuv420p10le source: before, profile=High 10 and pix_fmt=yuv420p10le; after, profile=High and pix_fmt=yuv420p. MediaTranscoder already set -pix_fmt yuv420p, with a comment saying 10-bit sources otherwise produce a file nothing will play. The HLS path never learned it. The profile is pinned in both now. One test checks the arguments; the other runs ffmpeg for real, encodes a 10-bit source through the server's own command and probes the segment for High 10. The second is the one that matters -- an argument assertion alone would pass while the output stayed unplayable.
|
Correction to my own "for review" note — the stale-cache concern is already handled, no purge needed. I wrote that existing sessions keep their High 10 segments and "worth confirming whether this needs a one-off purge on deploy". It does not. So after deploying, stale segments clear themselves within an hour or two, and the next request for that episode re-encodes with the pinned profile. Nothing to delete by hand. The only residue is an episode somebody re-opens inside that window, which would replay the old High 10 segments once. If that matters, deleting |
|
Reviewed on a5 — clean. 2 tests pass. Based on current main, no migration, no destructive ops. Correct fix for the transcode side of the 10-bit problem (Midnight Gospel E03 audio-only). Pinning This connects to my original Midnight Gospel probe: those episodes are 10-bit HEVC Main 10, so every transcode of them hit this. Nothing blocking. |
#305 fixed the transcode side: libx264 inherited the source pixel format and emitted High 10, which Apple hardware refuses. a5's review noted the Midnight Gospel episodes are 10-bit HEVC Main 10, which prompted checking the other route. A 10-bit H.264 .mp4 reaches the same failure with no transcode at all. The stream is genuinely h264 in a genuinely .mp4, so every check passed and the file was handed over untouched as High 10, playing black. The probe had recorded bit_depth all along and nothing consulted it. Verified against a real encode: codec_name=h264, profile=High 10, bits_per_raw_sample=10. Before, isPlayableVideo returned true; now it is refused and routes to the transcode that produces 8-bit High. An unrecorded depth is treated as 8-bit, because that is overwhelmingly what it is -- assuming otherwise would transcode every file an older probe measured.
Reported twice as "audio plays, no video" — most recently Midnight Gospel episode 3, after episode 2 was fixed. Both times it looked like a broken stream. It is an unsupported H.264 profile.
Why
libx264inherits the source's pixel format unless told otherwise. A 10-bit rip — common for anime and for anything encoded from a good master — came out of the HLS transcode as High 10, which Apple's hardware decoder refuses outright. The audio track decodes fine, so the player runs the clock over a black rectangle.Measured, not reasoned
Running the server's exact command over a
yuv420p10lesource:-profile:v high -level:v 4.1 -pix_fmt yuv420p. Level 4.1 is the ceiling every iOS device since the 4S decodes and covers 1080p comfortably.The lesson already existed in this codebase
MediaTranscoder, which writes the permanent converted copies, has set-pix_fmt yuv420pall along — with a comment reading "10-bit sources otherwise produce a file nothing will play". The HLS path never learned it.That is the part worth noting: this was a known hazard, documented in a neighbouring file, and the newer code path reintroduced it. The profile is now pinned in both, and asserted by a test rather than left to a comment.
Testing
Two tests. One checks the argument list. The other runs ffmpeg for real — encodes a genuinely 10-bit source through the server's own
command()and probes the resulting segment forHigh 10.The second is the one that matters: an argument-list assertion alone would have passed while the output was still unplayable, which is exactly how this shipped. Both confirmed to fail against the shipped code; the real-encode test fails with "High 10 is refused by Apple hardware decoders".
325 pass across the streaming and media suites. It skips cleanly where ffmpeg cannot encode 10-bit.
For review
Existing HLS sessions are cached on disk and will not be re-encoded.
HlsSegmenter::start()reuses a session directory whenindex.m3u8already exists, and the session id is a hash of (item, updated_at, height, start) — which this change does not alter. So any episode already transcoded still has High 10 segments and will still play black until that cache is cleared. Worth confirming where those live and whether this needs a one-off purge on deploy; I have not done that here because it deletes files.Level 4.1 is a judgement call. It is the safe universal ceiling, but it caps 4K — a 2160p source will be constrained. Given the transcode already scales to
max_height(1080 by default) that should not bite, but if you ever raise that ceiling the level needs raising with it.