Skip to content

fix(playback): preserve and expose video color range - #447

Merged
Quick104 merged 4 commits into
mainfrom
codex/jellycompat-color-range
Jul 22, 2026
Merged

fix(playback): preserve and expose video color range#447
Quick104 merged 4 commits into
mainfrom
codex/jellycompat-color-range

Conversation

@Quick104

@Quick104 Quick104 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • capture canonical ffprobe color_range values on scanned video tracks
  • persist the field through media JSONB and catalog seed export/import
  • expose Jellyfin-compatible MediaStream.ColorRange from item detail and PlaybackInfo responses
  • lazily reprobe legacy video rows once, using an internal unknown sentinel when ffprobe provides no range
  • expose the additive value as source.color_range in playback v3 for native clients
  • show the source color range in the web playback diagnostics panel

Root cause

Silo decoded adjacent ffprobe color fields but omitted color_range from its probe shape, stored model, and Jellycompat MediaStream DTO. Direct-play bytes remained untouched, but clients could not receive the limited-range tv or full-range pc metadata that FFmpeg and Jellyfin expose.

FFmpeg semantics

FFmpeg names limited AVCOL_RANGE_MPEG as tv, full AVCOL_RANGE_JPEG as pc, and unspecified AVCOL_RANGE_UNSPECIFIED as unknown. ffprobe default JSON may omit an unspecified optional field, so Silo stores unknown internally to make one-time legacy repair converge. Jellyfin-compatible output omits that sentinel; playback v3 preserves the canonical source value so native clients can explicitly ignore unknown.

No client should use this metadata to override valid decoder or container signaling. The coordinated native changes use it only as a fallback when local range metadata is unspecified.

Impact

Newly probed files preserve their exact range. Existing video rows are repaired on first detail or playback access, so users do not need to touch or re-import files. This is additive within /api/v1 and requires no SQL migration because video tracks are stored as JSONB.

Part of #446.
Closes #446.

Coordinated clients

The browser decoder remains authoritative in the web app because browser media APIs do not expose a safe limited/full-range override; the web change makes the server-probed source value visible for diagnostics.

Validation

  • GOWORK=off go test ./internal/models ./internal/playback ./internal/scanner -count=1
  • focused Jellycompat ColorRange and VideoRangeType tests
  • cd web && pnpm run lint (zero errors; existing warnings only)
  • cd web && pnpm run format:check
  • cd web && pnpm run build
  • focused web playback-info tests (9 passed)
  • make verify-local-paths
  • autoreview clean with no accepted or actionable findings
  • synthetic ffprobe verification: full range reports pc; unspecified is omitted by default and reports unknown with -show_optional_fields always

The broad internal/jellycompat suite still has two unrelated macOS process-lock identity failures involving an empty current-process token. Both failures reproduce unchanged on origin/main; the focused changed path passes.

Risk and follow-up

The main server-side behavioral change is a one-time lazy ffprobe for legacy video rows whose tracks lack color_range. When ffprobe cannot determine the value, Silo stores unknown internally so subsequent requests do not repeatedly probe.

The original reporter should verify the same direct-play file after deploying this branch and confirm the Infuse and native client output.

AI use

Implemented with OpenAI Codex. The patch was reviewed with the repository autoreview workflow, focused validation was run across server and web, and review findings in the coordinated native clients were addressed before publication.

Summary by CodeRabbit

  • New Features

    • Added end-to-end handling of video color-range metadata (tv/pc/unknown) across scanning, export/import, API types, and playback planning.
    • The playback UI now shows the current “Color range” for the selected video track.
  • Bug Fixes

    • Detects missing color-range metadata for video tracks and triggers re-probing when required.
    • Normalizes whitespace/casing and consistently treats unknown/missing values.
  • Tests

    • Added unit and API/UI-focused coverage to verify correct preservation, JSON behavior, and displayed labels.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b7a46d9-7241-46ac-a874-57bb466191f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3362d and b822bb7.

📒 Files selected for processing (1)
  • internal/catalogseed/service_test.go

📝 Walkthrough

Walkthrough

Video color range metadata now flows from ffprobe through persisted video tracks, Jellyfin-compatible playback streams, protocol-v3 source descriptors, catalog records, and web playback information. Missing values become unknown during probing, while playback and client surfaces normalize their presentation.

Changes

Video color range metadata

Layer / File(s) Summary
Probe propagation and repair detection
internal/scanner/..., internal/models/...
ffprobe color_range values are captured and persisted on video tracks; missing values trigger probe repair, with tests covering canonical values and JSON serialization.
Jellyfin media stream output
internal/jellycompat/...
Media stream DTOs expose normalized color range values and omit the unknown sentinel from JSON.
Protocol-v3 source descriptors
internal/playback/...
Source descriptors serialize optional color range metadata and normalize accepted values to tv, pc, or unknown.
Catalog export and client display
internal/catalogseed/..., web/src/api/types.ts, web/src/player/...
Catalog and web types preserve color range metadata, while playback information formats limited, full, unknown, and missing values.

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

Sequence Diagram(s)

sequenceDiagram
  participant FFprobe
  participant Scanner
  participant MediaFile
  participant Playback
  participant WebPlayer
  FFprobe->>Scanner: Parse color_range
  Scanner->>MediaFile: Persist VideoTrack.ColorRange
  MediaFile->>Playback: Build source and stream metadata
  Playback->>WebPlayer: Return color_range
  WebPlayer->>WebPlayer: Format color range label
Loading

Possibly related PRs

  • Silo-Server/silo-server#448: Adds overlapping propagation of VideoTrack.ColorRange into catalog and Jellyfin-compatible playback metadata.

Suggested labels: v1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving and exposing video color range metadata.
Linked Issues check ✅ Passed The changes align with #446 by capturing, persisting, exposing, reprobe-handling, and testing video color range metadata.
Out of Scope Changes check ✅ Passed The modified files all support the color range metadata feature and its regressions; no unrelated changes are evident.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/jellycompat-color-range

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

@Quick104
Quick104 marked this pull request as ready for review July 21, 2026 15:07
@coderabbitai coderabbitai Bot added the v1 Silo v1 scope - auto-adds to the Silo v1 project label Jul 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/catalogseed/service_test.go (1)

10-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the unknown sentinel in the catalog conversion test.

This test covers tv and pc, but not the persisted unknown value. Add a third input/assertion so catalog export mapping is protected from accidentally dropping or normalizing the sentinel.

Suggested test extension
-		{ColorRange: "pc"},
+		{ColorRange: "pc"},
+		{ColorRange: "unknown"},
 	})
 
-	if len(got) != 2 {
-		t.Fatalf("records length = %d, want 2", len(got))
+	if len(got) != 3 {
+		t.Fatalf("records length = %d, want 3", len(got))
 	}
-	if got[0].ColorRange != "tv" || got[1].ColorRange != "pc" {
-		t.Fatalf("ColorRange values = [%q, %q], want [tv, pc]", got[0].ColorRange, got[1].ColorRange)
+	if got[0].ColorRange != "tv" || got[1].ColorRange != "pc" || got[2].ColorRange != "unknown" {
+		t.Fatalf("unexpected ColorRange values")
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/catalogseed/service_test.go` around lines 10 - 22, Extend
TestToVideoTrackRecordsPreservesColorRange with a third VideoTrack whose
ColorRange is "unknown", update the expected record count, and assert that the
corresponding converted record preserves "unknown" unchanged alongside the
existing "tv" and "pc" values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/catalogseed/service_test.go`:
- Around line 10-22: Extend TestToVideoTrackRecordsPreservesColorRange with a
third VideoTrack whose ColorRange is "unknown", update the expected record
count, and assert that the corresponding converted record preserves "unknown"
unchanged alongside the existing "tv" and "pc" values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c45d3dd9-5dd7-417d-932d-277e153a9b09

📥 Commits

Reviewing files that changed from the base of the PR and between 3457382 and 827843f.

📒 Files selected for processing (15)
  • internal/catalogseed/service.go
  • internal/catalogseed/service_test.go
  • internal/catalogseed/types.go
  • internal/jellycompat/deviceprofile_conditions_test.go
  • internal/jellycompat/dto.go
  • internal/jellycompat/handlers_playback.go
  • internal/models/media.go
  • internal/scanner/probe.go
  • internal/scanner/probe_repair.go
  • internal/scanner/probe_repair_audio_test.go
  • internal/scanner/probe_video_range_test.go
  • internal/scanner/scanner.go
  • internal/scanner/types.go
  • web/src/api/types.ts
  • web/src/player/types.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 827843fd21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +57 to +58
if videoTracksMissingColorRange(file.VideoTracks) {
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add scan-state color-range repair detection

When relying on a normal library rescan to backfill existing rows, this new full MediaFile check is bypassed: processFile builds updateReasons via scanStateUpdateReasons, which calls needsCriticalProbeRepairScanState on the lightweight scan state, and that state only exposes HasVideoTracks rather than whether each video_tracks element has color_range. For already-scanned, unchanged matched videos whose JSON lacks color_range, the scan skip path returns actionUnchanged, so the Jellyfin ColorRange field remains absent until an on-demand detail/playback repair happens; add a scan-state boolean for missing color range and include it in needsCriticalProbeRepairScanState so scheduled rescans converge too.

Useful? React with 👍 / 👎.

@Quick104 Quick104 changed the title fix(jellycompat): preserve video color range fix(playback): preserve and expose video color range Jul 21, 2026
@Quick104
Quick104 merged commit a0507c7 into main Jul 22, 2026
1 check passed
@Quick104
Quick104 deleted the codex/jellycompat-color-range branch July 22, 2026 17:38
@github-project-automation github-project-automation Bot moved this to Done in Silo v1 Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1 Silo v1 scope - auto-adds to the Silo v1 project

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix(jellycompat): preserve video color range metadata

1 participant