mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
perf(tracks): skip redundant DV/VUI bitstream passes
Expose DV via a plain elementary-stream demux instead of a dovi_tool RPU round-trip, and skip normalize_vui when the VUI already matches the target range. Both avoid a full bitstream pass; stale-VUI streams are still rewritten.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
"""
|
||||
DV fixup for HLS composite HEVC streams.
|
||||
DV exposure for HLS composite HEVC streams.
|
||||
|
||||
Some services deliver DV Profile 8.1 in a stream whose primary CODECS is plain
|
||||
hvc1, with DV advertised only via SUPPLEMENTAL-CODECS. The fMP4 carries DV RPU NALs but
|
||||
the container does not signal DV, so muxing produces an MKV that mediainfo and DV-capable
|
||||
TVs see as plain HDR10/HDR10+.
|
||||
hvc1, with DV advertised only via SUPPLEMENTAL-CODECS. The fMP4 carries valid DV RPU NALs
|
||||
but the container does not signal DV, so muxing the MP4 directly produces an MKV that
|
||||
mediainfo and DV-capable TVs see as plain HDR10/HDR10+.
|
||||
|
||||
A dovi_tool extract-rpu / inject-rpu round-trip rewrites the bitstream so it is recognised
|
||||
as DV after muxing. HDR10+ SEI NALs and HDR10 base layer signaling survive untouched.
|
||||
The RPU is already valid — only the container's DV signaling is lost. Demuxing the
|
||||
elementary HEVC stream (ffmpeg -c:v copy) exposes the in-stream RPU to mkvmerge, which then
|
||||
signals DV in the muxed MKV. No dovi_tool extract/inject round-trip is needed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -19,11 +20,9 @@ from typing import TYPE_CHECKING
|
||||
from rich.padding import Padding
|
||||
from rich.rule import Rule
|
||||
|
||||
from unshackle.core.binaries import FFMPEG, DoviTool
|
||||
from unshackle.core.config import config
|
||||
from unshackle.core.binaries import FFMPEG
|
||||
from unshackle.core.console import console
|
||||
from unshackle.core.utilities import log_event
|
||||
from unshackle.core.utils import dovi
|
||||
from unshackle.core.utils.subprocess import run_step
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -31,46 +30,37 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class DVFixup:
|
||||
"""Round-trip a DV-composite HEVC track through dovi_tool to restore DV signaling."""
|
||||
"""Demux a DV-composite HEVC track to its elementary stream so mkvmerge exposes DV."""
|
||||
|
||||
def __init__(self, video: "Video") -> None:
|
||||
self.log = logging.getLogger("dv-fixup")
|
||||
self.video = video
|
||||
|
||||
if not DoviTool:
|
||||
raise EnvironmentError("dovi_tool is required for DV-composite fixup but was not found.")
|
||||
if not FFMPEG:
|
||||
raise EnvironmentError("ffmpeg is required for DV-composite fixup but was not found.")
|
||||
if not video.path or not Path(video.path).exists():
|
||||
raise ValueError(f"Video track {video.id} was not downloaded before DV fixup.")
|
||||
|
||||
def run(self) -> Path:
|
||||
"""Execute the fixup. Returns the DV-signaled HEVC path, or the original
|
||||
source path on any failure so muxing can proceed with the as-downloaded file."""
|
||||
"""Demux the elementary HEVC so mkvmerge exposes its DV RPU. Returns the
|
||||
elementary-stream path, or the original source on any failure so muxing can
|
||||
proceed with the as-downloaded file."""
|
||||
source = Path(self.video.path)
|
||||
height = self.video.height or 0
|
||||
console.print(Padding(Rule(f"[rule.text]DV Composite Fixup ({height}p)"), (1, 2)))
|
||||
|
||||
fixed_hevc = source.with_name(f"{self.video.id}.dv.hevc")
|
||||
if fixed_hevc.exists() and fixed_hevc.stat().st_size > 0:
|
||||
self.log.info("✓ DV signaling already restored (reusing existing fixup)")
|
||||
self.log.info("✓ DV signaling already exposed (reusing existing demux)")
|
||||
return fixed_hevc
|
||||
|
||||
tmp = config.directories.temp
|
||||
tmp.mkdir(parents=True, exist_ok=True)
|
||||
suffix = f"{self.video.id}_{height or 'na'}"
|
||||
raw_hevc = tmp / f"dvfix_{suffix}.hevc"
|
||||
rpu = tmp / f"dvfix_{suffix}_rpu.bin"
|
||||
|
||||
try:
|
||||
run_step(
|
||||
[FFMPEG, "-nostdin", "-y", "-i", source, "-c:v", "copy", "-f", "hevc", raw_hevc],
|
||||
status="Demuxing HEVC bitstream...",
|
||||
output=raw_hevc,
|
||||
[FFMPEG, "-nostdin", "-y", "-i", source, "-c:v", "copy", "-f", "hevc", fixed_hevc],
|
||||
status="Demuxing HEVC bitstream to expose DV...",
|
||||
output=fixed_hevc,
|
||||
label="ffmpeg demux",
|
||||
)
|
||||
dovi.extract_rpu_with_fallback(raw_hevc, rpu)
|
||||
dovi.inject_rpu(raw_hevc, rpu, fixed_hevc, status="Re-injecting DV RPU with proper signaling...")
|
||||
except Exception as e:
|
||||
self.log.warning(f"DV fixup failed ({e}); muxing source as-is.")
|
||||
log_event(
|
||||
@@ -79,14 +69,10 @@ class DVFixup:
|
||||
message="DV fixup failed; falling back to source",
|
||||
context={"error": str(e), "source": str(source)},
|
||||
)
|
||||
for leftover in (raw_hevc, rpu, fixed_hevc):
|
||||
leftover.unlink(missing_ok=True)
|
||||
fixed_hevc.unlink(missing_ok=True)
|
||||
return source
|
||||
|
||||
for leftover in (raw_hevc, rpu):
|
||||
leftover.unlink(missing_ok=True)
|
||||
|
||||
self.log.info("✓ DV signaling restored")
|
||||
self.log.info("✓ DV signaling exposed")
|
||||
log_event(
|
||||
"dv_fixup",
|
||||
level="INFO",
|
||||
|
||||
@@ -433,6 +433,35 @@ class Video(Track):
|
||||
if not vui:
|
||||
return False
|
||||
|
||||
# Skip the rewrite when the bitstream VUI already matches the target — avoids a full
|
||||
# bitstream pass on services that already ship correct colour metadata
|
||||
expected = {
|
||||
Video.Range.HDR10: ("bt2020", "smpte2084", "bt2020nc"),
|
||||
Video.Range.HDR10P: ("bt2020", "smpte2084", "bt2020nc"),
|
||||
Video.Range.HLG: ("bt2020", "arib-std-b67", "bt2020nc"),
|
||||
}.get(self.range)
|
||||
if expected and binaries.FFProbe:
|
||||
probe = subprocess.run(
|
||||
[
|
||||
binaries.FFProbe,
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-show_entries",
|
||||
"stream=color_primaries,color_transfer,color_space",
|
||||
"-of",
|
||||
"default=noprint_wrappers=1",
|
||||
str(self.path),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
vals = dict(line.split("=", 1) for line in probe.stdout.splitlines() if "=" in line)
|
||||
current = (vals.get("color_primaries"), vals.get("color_transfer"), vals.get("color_space"))
|
||||
if current == expected:
|
||||
return False
|
||||
|
||||
if not binaries.FFMPEG:
|
||||
raise EnvironmentError('FFmpeg executable "ffmpeg" was not found but is required for this call.')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user