Skip to content

fix: bias yt-dlp format_sort toward board's HW-decoded codec (Pi 5 HEVC)#3093

Open
TekScene wants to merge 6 commits into
Screenly:masterfrom
TekScene:fix/pi5-youtube-hevc-format-selection
Open

fix: bias yt-dlp format_sort toward board's HW-decoded codec (Pi 5 HEVC)#3093
TekScene wants to merge 6 commits into
Screenly:masterfrom
TekScene:fix/pi5-youtube-hevc-format-selection

Conversation

@TekScene

@TekScene TekScene commented Jun 24, 2026

Copy link
Copy Markdown

Summary

Fixes #3092.

On Pi 5, download_youtube_asset used a hardcoded vcodec:h264 format_sort preference. yt-dlp downloaded H.264 (the only codec YouTube typically serves), which then failed at the normalize_video_asset codec gate:

Video codec 'h264' is not hardware-decoded on this device. Supported: hevc.

Two root causes fixed:

1. yt-dlp format selection (celery_tasks.py): Call _hw_decoded_codecs() before building ydl_opts and prefer HEVC in format_sort whenever the board supports it. If YouTube ever serves an HEVC stream, it'll be picked automatically. When it doesn't (the common case), yt-dlp falls back to H.264.

2. Pi 5 codec gate too strict (processing.py): Pi 5's BCM2712 has no H.264 hardware decoder, but its Cortex-A76 cores software-decode 1080p H.264 without frame drops. YouTube rarely serves HEVC, so the previous HEVC-only gate rejected every YouTube download. H.264 is now accepted on Pi 5 alongside HEVC. On-device transcoding was explicitly ruled out — a prior attempt wedged a Pi 4's Celery worker for 99 minutes on a single H.264→HEVC pass.

Changes:

  • src/anthias_server/celery_tasks.py: prefer HEVC in format_sort on any board that has it; fall back to H.264
  • src/anthias_server/processing.py: add h264 to Pi 5's accepted codec set
  • tests/test_celery_tasks.py: 4 new tests for per-board format_sort selection; Pi 4 test updated (now prefers HEVC when available)
  • tests/test_processing.py: replace Pi 5+H.264 rejection tests (no longer valid) with Pi 3+HEVC rejection tests using mocked ffprobe; add new test asserting H.264 is accepted on Pi 5

Test plan

  • Verified on physical Pi 5: YouTube URL downloads and plays without "Processing failed"
  • pytest tests/test_celery_tasks.py -k "format" passes (4 new tests)
  • pytest tests/test_celery_tasks.py fully green
  • pytest tests/test_processing.py fully green

🤖 Generated with Claude Code

…enly#3092)

On Pi 5 (HEVC-only hardware decode), download_youtube_asset was
requesting H.264 via a hardcoded format_sort preference. The download
succeeded, but normalize_video_asset then rejected the asset with
"Video codec 'h264' is not hardware-decoded on this device. Supported:
hevc." The capability detection already existed — it just wasn't
consulted before the download.

Fix: call _hw_decoded_codecs() before building ydl_opts and set the
leading format_sort entry to the board's preferred codec. HEVC-only
boards (Pi 5) now request vcodec:hevc; all others keep vcodec:h264.
Unknown boards fall back to h264 so the download still proceeds.

Adds four tests covering Pi 5 (HEVC), Pi 4 (H.264 preferred when both
supported), Pi 3 (H.264 only), and unrecognised board (h264 fallback).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@TekScene TekScene requested a review from a team as a code owner June 24, 2026 02:12
TekScene and others added 3 commits June 23, 2026 22:43
Pi 5's BCM2712 has no H.264 hardware decoder; the Cortex-A76 cores
software-decode 1080p H.264 without frame drops. However YouTube
rarely serves HEVC, so the previous HEVC-only codec gate on Pi 5
rejected every YouTube download regardless of format_sort preference.

- processing.py: add h264 to pi5's accepted codec set alongside hevc
- celery_tasks.py: simplify format_sort to prefer hevc on any board
  that has it (yt-dlp falls back to h264 on YouTube anyway)
- test_processing.py: replace pi5+h264-rejection tests with
  pi3+hevc-rejection tests (mocked ffprobe, no ffmpeg needed); add
  new test asserting h264 IS accepted on pi5 via software decode
- test_celery_tasks.py: update pi4 test to expect hevc preference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add test_video_unsupported_codec_still_rejected_on_pi5 to verify that
codecs outside Pi 5's accepted set (VP9, AV1, …) still raise
UnsupportedVideoCodecError even after h264 was added as a software-
decode fallback.  Restores the removed-behavior coverage flagged by the
code review for that path.

Also restore _ffmpeg_reencode_recipe / _handbrake_steps to prefer h264
when it's in the accepted set (faster to encode; both codecs are
hardware-decoded on Pi 4 where the set is {h264, hevc}).  Update the
docstrings to reflect the current logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous comment said "HEVC-only boards (Pi 5) must not receive an
H.264 stream", which was true before this PR but is now wrong: Pi 5
accepts H.264 via Cortex-A76 software decode since YouTube rarely serves
HEVC. Update the comment to accurately describe the current behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vpetersson

Copy link
Copy Markdown
Contributor

On-device test report — physical Pi 5

Tested both halves of this PR on a physical Pi 5 (DEVICE_TYPE=pi5, current GHCR viewer image), then restored the board to its original state.

1. Playback: does Pi 5 actually play SW-decoded 1080p H.264 smoothly? ✅

The viewer image is unchanged by this PR, so I measured presented frame rate directly via the kernel drm_vblank_event_delivered tracepoint (zero observer effect) on 1080p Big Buck Bunny clips:

Clip (1080p) Decode path Presented fps Viewer CPU
1080p30 H.264 (High@L4.1) software (Cortex-A76) 27.4 fps 58% of one of 4 cores
1080p30 HEVC hardware (V4L2-request) 27.0 fps 18% of one core

Both present at the 30 fps source cadence — SW-decoded H.264 is just as smooth as HW-decoded HEVC, at a higher but far-from-saturating CPU cost (~14% of total CPU). The "no frame drops on Cortex-A76" claim in the PR description holds on real hardware. (The old ~11 fps Pi 5 presentation bottleneck was fixed in #2975, which is already in the shipping viewer image.)

2. Gate change: clean before/after ✅

Patched the two hunks into the celery container and ran the real _run_video_normalisation on an actual 1080p H.264 file:

3. Live YouTube download — blocked by environment, not the PR ⚠️

The end-to-end download_youtube_asset run hit HTTP Error 403: Forbidden from yt-dlp (YouTube bot-blocking the media fetch from this network). That's at the download stage, after format selection, and unrelated to this PR's logic — which I validated deterministically above.

One code-review note (not tested here)

The format_sort change biases every multi-codec board toward HEVC, not just Pi 5 — including x86, Pi 4, and Rock Pi 4 (previously all got vcodec:h264). For x86 specifically, HEVC playback in the cage compositor is known-broken (VAAPI FormatError → black screen), and the gate already accepts hevc on x86. So if YouTube ever serves an HEVC stream, an x86 device could now download HEVC, pass the gate, and black-screen at playback. The trigger is rare (YouTube seldom serves HEVC) but it's scope beyond the stated Pi 5 fix — worth considering whether to narrow the bias to Pi 5 only.

Verdict: the Pi 5 fix works as intended and the playback premise checks out on real hardware. 👍

@vpetersson

Copy link
Copy Markdown
Contributor

@TekScene Thank you for the contribution but please fix the linter error.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

TekScene and others added 2 commits June 24, 2026 07:28
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _PREFERRED_DOWNLOAD_VCODEC dict and preferred_download_vcodec()
in processing.py alongside _HW_DECODE_VIDEO_CODECS, so per-board
download preferences live with the rest of the codec definitions
rather than as ad-hoc logic in celery_tasks.py.

Pi 5 keeps its HEVC preference (hardware decode path). Pi 4, x86,
Rock Pi 4 and all other boards revert to H.264 — it is their primary
hardware path and more widely available on YouTube. x86 HEVC via
VAAPI is known-broken (black screen), so narrowing the HEVC bias to
Pi 5 only closes that risk.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@TekScene

Copy link
Copy Markdown
Author

Good catch. The original implementation biased all HEVC-capable boards (Pi 4, x86, Rock Pi 4) toward HEVC in format_sort, which was wider than intended.

The updated approach introduces _PREFERRED_DOWNLOAD_VCODEC in processing.py alongside _HW_DECODE_VIDEO_CODECS, keeping all per-board codec knowledge in one place. Only Pi 5 is listed with a hevc preference — x86 and Pi 4 are intentionally absent:

  • x86: HEVC via VAAPI is known to produce a black screen (FormatError in the cage compositor), so biasing toward HEVC downloads would cause silent playback failures if YouTube ever serves an HEVC stream.
  • Pi 4: hardware-decodes both codecs equally well, so H.264 is preferred for its wider YouTube availability — no behaviour change from before this PR.

A dedicated test (test_download_youtube_asset_x86_prefers_h264_format) now guards the x86 path explicitly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Pi 5: YouTube URL download picks H.264, fails on device that only supports HEVC hardware decode

2 participants