Skip to content

fix(playback): remove Dolby Vision 7 enhancement-layer units in the HDR10 strip - #2090

Merged
Quick104 merged 2 commits into
Silo-Server:mainfrom
blurbery:fix/playback-dv7-strip-enhancement-layer
Oct 8, 2026
Merged

Quick104 merged 2 commits into
Silo-Server:mainfrom
blurbery:fix/playback-dv7-strip-enhancement-layer

Conversation

@blurbery

@blurbery blurbery commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Closes #656
Related issue: N/A
Validation tasks: none

When Silo strips Dolby Vision profile 7 down to HDR10 for a client that only takes HDR10, the "HDR10" stream still carries the profile 7 enhancement layer. A single-track profile 7 source interleaves its enhancement layer in the same video stream as NAL unit type 63. Mapping only 0:V:0 can't separate those units, and dovi_rpu=strip=1 doesn't remove them (FFmpeg 7.1 removes none; since FFmpeg 8.0 it removes at most the last unit of each picture). So the server_dv7_to_hdr10 output contradicts its own enhancement_layer_discarded claim and hands HDR10-only clients bytes they never said they could decode. Since #1535 the same strip also serves Jellyfin-compatible clients.

Approach

  • The filter chain. DV7ToHDR10BitstreamFilter is now dovi_rpu=strip=1,filter_units=remove_types=63. Every executor uses that one constant: native HLS remux, progressive remux, jellycompat and the RPU probe.
  • Capability checks. The transformation registry probe and the remux path's filter check now require both dovi_rpu and filter_units in ffmpeg -bsfs.
  • Recipe version. server_dv7_to_hdr10 moves to recipe version 2. One new constant feeds the registry, the planner, jellycompat's pinned version and the fixture generator. A transcode node that still advertises recipe 1 can't claim the corrected output: it's no longer chosen for new plans or for jellycompat strips.
  • Older plans. A plan frozen at recipe 1 still gets the current chain from videoBitstreamFilterForPlanV3 rather than no filter at all. An old node rejects the unknown chain, and a current executor refuses the recipe 1 plan when it validates versions, so the plan fails and replans instead of copying Dolby Vision unstripped.
  • Fixtures and docs. The protocol fixtures are regenerated with make playback-fixtures. docs/architecture/playback-protocol-v3.md and docs/jellycompat-api.md now describe recipe 2.

The fork I run used filter_units=remove_types=63 as part of a larger set of Dolby Vision changes. This is the same filter change on its own, written fresh against main. The earlier #657 was closed during the remux redesign.

No client changes. Clients only validate the recipe versions of client-run transformations, and their vendored copies of these fixtures can pick up recipe 2 on their next sync. This is the same filter jellycompat already uses, so jellycompat gets the fix too.

Validation

Tests cover:

  • the remux and HLS copy arguments, pinned to the full chain;
  • both probes refusing an FFmpeg that has only one of the two filters;
  • the advertised recipe version;
  • jellycompat refusing a node on recipe 1;
  • videoBitstreamFilterForPlanV3 returning the current chain for a recipe 1 plan.

Existing tests that used the old literal or a recipe 1 registry for the local server now use the constants.

Run on 0da5a6404 (macOS, FFmpeg 9.0.2):

  • go test ./internal/api/handlers/ ./internal/jellycompat/... ./cmd/playbackfixtures/ -count=1: pass.
  • go test ./internal/playback/... -count=1: everything passes except the hardware-encoder probe tests (VideoToolbox, NVENC, QSV, VAAPI) that fail the same way on main on this Mac.
  • make verify-playback-fixtures: current.
  • make lint-changed: 0 issues.
  • make verify-local-paths: not run locally (the script crashes under macOS bash); CI runs it.
  • CI on 0da5a6404: all required checks pass (run): Go test, Go integration, Go lint, Go DB pins, Go DB external auth, Web build, checks and tests, repository checks (including verify-local-paths).

Benchmarks. Not applicable. filter_units drops units in the same copy pass, with no decode or encode.

Evidence

Evidence: https://evidence.siloserver.org/r/silo-server/pr-2090/

I have no Dolby Vision profile 7 media I can share, so there's no playback capture. The change is to the bytes the remux emits, not to anything a screen shows. The byte-level A/B on a real file is in #656, and the synthetic check below reproduces it.

#656 has the A/B on a real dual-layer profile 7 file (Jellyfin FFmpeg 7.1.4). The old chain left 51 type-63 units in two seconds of output; the new chain left 0. The output stayed HEVC Main 10, PQ/BT.2020, with no Dolby Vision configuration record.

To check this without private media, I injected type-63 units into 24 pictures of a synthetic HEVC Main 10 stream and counted the units each chain left, using FFmpeg 9.0.2:

type-63 units per picture   chain                                            type 63 left   pictures
1                           dovi_rpu=strip=1 (recipe 1)                      0 of 24        24
1                           dovi_rpu=strip=1,filter_units=remove_types=63    0 of 24        24
3                           dovi_rpu=strip=1 (recipe 1)                      48 of 72       24
3                           dovi_rpu=strip=1,filter_units=remove_types=63    0 of 72        24

The recipe 2 output decodes cleanly (hevc, Main 10, 24 frames).

GET /api/v1/playback/capability (protocol fixture capability_response.json), transformations entry for server_dv7_to_hdr10:

before: "recipe_version": "1"
after:  "recipe_version": "2"

Risks

  • Sessions in flight during the upgrade. A DV7 strip session started before the upgrade fails when it restarts its copy on an upgraded executor (seek or node move), because the stored recipe still names the old chain. It then replans onto recipe 2.

  • Mixed-version clusters. Two cases still serve the wrong bytes while API replicas and nodes run different versions:

    • An API replica still on the old binary that picks up a recipe 2 plan from the shared attempt store sends no strip filter at all.
    • The jellycompat remux-dv-v1 path segment isn't bumped, so an old replica can still serve recipe 1 bytes for a session a new replica negotiated.

    Fencing these needs either a CopyFMP4RecipeVersion bump, which drops every in-flight copy session at deploy, or a new jellycompat path segment. I left both out to keep this change focused and can add either if you want it here. Single-node deployments aren't affected.

Checklist

  • I read and can explain the complete diff.
  • This pull request addresses one concern.
  • The Evidence section shows every change a user can see, or says there is none.

AI Disclosure

  • Harness: Claude Code (Claude desktop app)
  • Tool(s): Claude Code
  • Model(s): claude-opus-5-5
  • Involvement: AI-assisted. I directed the task and designed the work.
  • Adversarial review: A separate Claude Code subagent (claude-opus-5-5) reviewed the first commit read-only. It checked every place that builds, validates or compares the filter string or the recipe version, both directions of a rolling upgrade, the Profile 8 and preserve paths, filter order, docs and fixtures. It found:
    • recipe 1 plans got no filter at all, which is now fixed and tested;
    • stale comments, an error message and test literals, which are now updated;
    • the two mixed-version gaps and the in-flight session behaviour, which are described under Risks.

@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d6dae5bf-5460-4044-95df-54bf893352ee
📥 Commits

Reviewing files that changed from the base of the PR and between ca186fe and 0da5a64.

📒 Files selected for processing (23)
  • cmd/playbackfixtures/main.go
  • docs/architecture/playback-protocol-v3.md
  • docs/design/schemas/playback-v3/v3/fixtures/valid/capability_response.json
  • docs/jellycompat-api.md
  • internal/api/handlers/playback_v3.go
  • internal/api/handlers/playback_v3_test.go
  • internal/api/handlers/playback_v3_union_test.go
  • internal/jellycompat/dv_strip.go
  • internal/jellycompat/dv_strip_test.go
  • internal/playback/plan_v3.go
  • internal/playback/plan_v3_dv_strip_fallback_test.go
  • internal/playback/protocol_v3.go
  • internal/playback/protocol_v3_test.go
  • internal/playback/recipecard_test.go
  • internal/playback/remux.go
  • internal/playback/remux_dv_test.go
  • internal/playback/remux_leading_picture_test.go
  • internal/playback/testdata/protocol_v3/capability_response.json
  • internal/playback/testdata/protocol_v3/conformance_matrix.json
  • internal/playback/transcode.go
  • internal/playback/transcode_args_test.go
  • internal/playback/transformations_v3.go
  • internal/playback/transformations_v3_test.go

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The server DV7-to-HDR10 remux recipe now removes Dolby Vision RPUs and Profile 7 type-63 enhancement-layer NAL units. Capability advertisement requires FFmpeg support for both filters. The recipe version advances to 2, and planning, execution filtering, fixtures, and documentation reflect the updated contract.

Changes

DV7-to-HDR10 remux

Layer / File(s) Summary
Apply and gate the Profile 7 stripping filter
internal/playback/remux.go, internal/playback/remux_dv_test.go, internal/playback/transcode.go, internal/playback/transcode_args_test.go, internal/playback/transformations_v3.go, internal/playback/transformations_v3_test.go, internal/playback/recipecard_test.go, internal/playback/remux_leading_picture_test.go
The validated filter chain strips Dolby Vision RPUs and type-63 enhancement-layer NAL units. The remux capability check and transformation registry require both dovi_rpu and filter_units. Tests cover filter application and availability.
Version and route the corrected transformation
internal/playback/protocol_v3.go, internal/playback/plan_v3.go, internal/api/handlers/playback_v3.go, internal/api/handlers/playback_v3_union_test.go, internal/api/handlers/playback_v3_test.go, internal/jellycompat/dv_strip.go, internal/jellycompat/dv_strip_test.go, internal/playback/plan_v3_dv_strip_fallback_test.go, internal/playback/protocol_v3_test.go, cmd/playbackfixtures/main.go, internal/playback/testdata/protocol_v3/*, docs/architecture/playback-protocol-v3.md, docs/design/schemas/playback-v3/v3/fixtures/valid/capability_response.json, docs/jellycompat-api.md
The shared recipe version changes to 2. Planning and fixtures use the version constant; API filter selection applies to server-executed transformations regardless of recipe version. Compatibility routing rejects nodes advertising stale recipe version 1. Fixtures and documentation describe the new version and filter requirements.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant FFmpeg
  participant TransformationRegistry
  participant PlanV3
  participant Executor
  FFmpeg-->>TransformationRegistry: Reports dovi_rpu and filter_units
  TransformationRegistry-->>PlanV3: Advertises server_dv7_to_hdr10 recipe 2
  PlanV3->>Executor: Selects server transformation recipe 2
  Executor->>FFmpeg: Applies RPU stripping and type-63 removal
Loading

Suggested reviewers: quick104

Merge Risk: ⚪ Minimal · up to 0da5a

No actionable issue was identified in the corrected DV7-to-HDR10 path. Merge after normal checks, with the documented mixed-version limitations understood.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning [#656] The PR implements the combined dovi_rpu=strip=1,filter_units=remove_types=63 chain, requires both FFmpeg filters, and changes the recipe to version 2. Tests cover these changes. The PR also s… Add mixed-version fencing for both execution paths. Prevent an old API binary from executing a recipe-2 plan, and prevent an old Jellycompat route from serving recipe-1 output for a newly negotiated session. Add automated coverage for these…
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 36 functions across 18 files. (5 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changed production code, tests, fixtures, and documentation all support [#656]. The filter chain, capability checks, recipe version, plan handling, and Jellycompat updates are directly related to …
Title check ✅ Passed The title clearly and concisely describes the main change: removing Dolby Vision 7 enhancement-layer units during HDR10 stripping.
Description check ✅ Passed The description directly explains the defect, implementation, recipe-version change, validation, risks, and scope of the changeset.
Full details: Linked Issues check

Explanation

[#656] The PR implements the combined dovi_rpu=strip=1,filter_units=remove_types=63 chain, requires both FFmpeg filters, and changes the recipe to version 2. Tests cover these changes. The PR also states that mixed-version deployments can still serve incorrect bytes: an old API replica can execute a recipe-2 plan without a filter, and an old Jellycompat route can serve recipe-1 bytes. This does not meet the issue requirement for clean server-generated DV7-to-HDR10 output across remux deliveries when the transformation is selected.

Resolution

Add mixed-version fencing for both execution paths. Prevent an old API binary from executing a recipe-2 plan, and prevent an old Jellycompat route from serving recipe-1 output for a newly negotiated session. Add automated coverage for these upgrade cases.

Full details: Docstring Coverage

Explanation

Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 36 functions across 18 files. (5 skipped: 5 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Quick104 Quick104 added priority: P1 Core-path bug; painful workaround — next after P0 impact: playback Can't play, stalls, or wrong media labels Oct 8, 2026 — with Cursor
@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown

Evidence is incomplete for these visible playback changes:

  • Native HLS/progressive playback (internal/playback/transcode.go, internal/playback/remux.go) now removes Profile 7 enhancement-layer units; add before-and-after captures or a short recording of the same test source on the same client, and name the surface and build/commit.
  • Jellycompat playback (internal/jellycompat/dv_strip.go) also uses the corrected strip; add equivalent before-and-after evidence for that surface, with its build/commit.

The synthetic NAL-unit counts and capability recipe_version excerpt document the implementation and protocol change, but do not show the user-visible playback result. Alternatively, publish the captures and add the Evidence: https://evidence.siloserver.org/... link.

Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness.

Posted via Macroscope — Visible change evidence

@Quick104
Quick104 merged commit 97f4f82 into Silo-Server:main Oct 8, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

impact: playback Can't play, stalls, or wrong media priority: P1 Core-path bug; painful workaround — next after P0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] DV7 HDR10 fallback retains enhancement-layer NAL units

2 participants