Files
silo-server/web/src/lib/videoRange.ts
T
0f9584ef42 fix(overlays): derive dynamic-range badges from Dolby Vision metadata (#365)
* fix(overlays): derive dynamic-range badges from Dolby Vision metadata

Dolby Vision files were labeled with a generic "HDR" badge everywhere
outside the Media Info dialog, because badge sites only consulted the
bare FileVersion.hdr boolean even though the payload carries
video_tracks[].dolby_vision / dv_profile / video_range_type /
color_transfer.

Web: add a shared helper (web/src/lib/videoRange.ts) that derives a
display label ("DV", "DV HDR10", "DV HLG", "HDR10+", "HDR10", "HLG")
from the probed video tracks, mirroring the server-side normalizeHDR
vocabulary, with the hdr boolean kept as the last-resort fallback for
stale pre-DV probe rows. Use it in QualityBadges, VersionDropdown,
VersionFlyout, and the player HUD (playback-info) in place of the
hardcoded "HDR" literal. Badge styling is unchanged.

Server: in internal/overlays/summary.go, break bestFile resolution
ties by richness of dynamic-range metadata (DV > explicit HDR10/HLG
via color_transfer > bare hdr boolean > SDR) so a first-scanned
generic-HDR file no longer masks a Dolby Vision sibling on card
overlays. Full ties still keep the earliest file, so the selection
stays deterministic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(overlays): detect Dolby Vision via dv_profile and DOVI range type

Probed rows set DolbyVision and DVProfile together, but catalog-seeded
or imported tracks can carry only dv_profile or a DOVI* video_range_type
with an empty dolby_vision string. Share one hasDolbyVision predicate
between rangeRank and normalizeHDR so those rows rank and label as DV,
matching the web videoRange helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(overlays): read video_range_type and hdr10_plus in hdrTypeFromTracks

Review follow-up: hdrTypeFromTracks only inspected color_transfer, so
server card overlays could never label HDR10+ and a track whose only
signal is video_range_type (e.g. catalog-seeded rows without probed
color metadata) fell through to the bare-boolean tier of rangeRank —
inconsistent with hasDolbyVision, which already reads the range type.
The server now shares the web helper's detection order exactly, making
the web/server vocabulary mirror claim true.

Also drops the unused video_range field from the web VideoRangeTrack
interface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
2026-07-16 10:53:30 -04:00

90 lines
3.0 KiB
TypeScript

// Derives a compact dynamic-range badge label ("DV", "DV HDR10", "HDR10+",
// "HDR10", "HLG", "HDR") from a file version's probed video tracks. Mirrors
// the server-side vocabulary in internal/overlays/summary.go (normalizeHDR)
// so card overlays, detail badges, version pickers, and the player HUD all
// agree. The bare `hdr` boolean is kept as the last-resort fallback for
// files probed before Dolby Vision / video-range metadata existed.
/** Minimal structural shape shared by VersionVideoTrack and PlayerVideoTrack. */
export interface VideoRangeTrack {
dolby_vision?: string;
dv_profile?: number;
hdr10_plus?: boolean;
video_range_type?: string;
color_transfer?: string;
}
/** Minimal structural shape shared by FileVersion and PlayerFileVersion. */
export interface VideoRangeSource {
hdr?: boolean;
video_tracks?: VideoRangeTrack[];
}
function trackHasDolbyVision(track: VideoRangeTrack): boolean {
if (track.dolby_vision?.trim()) return true;
if ((track.dv_profile ?? 0) > 0) return true;
return (track.video_range_type?.trim() ?? "").startsWith("DOVI");
}
// Distinguishes HDR variants from the scanner-derived video_range_type enum
// (HDR10, HDR10Plus, HLG, DOVIWith*) with color_transfer as a fallback,
// matching the server's hdrTypeFromTracks.
function trackHdrType(track: VideoRangeTrack): string {
const rangeType = track.video_range_type?.trim() ?? "";
if (track.hdr10_plus || rangeType.includes("HDR10Plus")) return "HDR10+";
if (rangeType === "HDR10" || rangeType.endsWith("WithHDR10")) return "HDR10";
if (rangeType === "HLG" || rangeType.endsWith("WithHLG")) return "HLG";
const transfer = track.color_transfer?.toLowerCase() ?? "";
if (transfer.includes("smpte2084")) return "HDR10";
if (transfer.includes("arib-std-b67")) return "HLG";
return "";
}
/**
* Display label for a file version's dynamic range: "DV", "DV HDR10",
* "DV HDR10+", "DV HLG", "HDR10+", "HDR10", "HLG", "HDR" (boolean-only
* fallback), or "" for SDR/unknown.
*/
export function videoRangeLabel(source: VideoRangeSource): string {
let hasDV = false;
let hdrType = "";
for (const track of source.video_tracks ?? []) {
if (trackHasDolbyVision(track)) hasDV = true;
if (!hdrType) hdrType = trackHdrType(track);
}
if (hasDV) return hdrType ? `DV ${hdrType}` : "DV";
if (hdrType) return hdrType;
if (source.hdr) return "HDR";
return "";
}
// Rollup preference when a badge summarizes several versions: any DV variant
// beats explicit HDR10+/HDR10/HLG, which beat the generic boolean "HDR".
const LABEL_RANK: Record<string, number> = {
"DV HDR10+": 8,
"DV HDR10": 7,
"DV HLG": 6,
DV: 5,
"HDR10+": 4,
HDR10: 3,
HLG: 2,
HDR: 1,
};
/** Best (most specific) dynamic-range label across a set of versions. */
export function bestVideoRangeLabel(sources: VideoRangeSource[]): string {
let best = "";
let bestRank = 0;
for (const source of sources) {
const label = videoRangeLabel(source);
const rank = LABEL_RANK[label] ?? 0;
if (rank > bestRank) {
best = label;
bestRank = rank;
}
}
return best;
}