diff --git a/tests/remote/unit/test_remote_service_helpers.py b/tests/remote/unit/test_remote_service_helpers.py index 8bc7b2a2..09225241 100644 --- a/tests/remote/unit/test_remote_service_helpers.py +++ b/tests/remote/unit/test_remote_service_helpers.py @@ -176,6 +176,14 @@ def test_match_track_by_attributes_keeps_range_and_bitrate_apart() -> None: assert match_track(remote, [sdr, hdr]) is hdr +def test_match_track_by_attributes_matches_probed_hdr10p_to_local_hdr10() -> None: + common = {"codec": "HEVC", "width": 3840, "height": 2160, "language": "en", "bitrate": 15000000} + sdr = deserialize_video({**common, "id": "a", "range": "SDR"}) + hdr10 = deserialize_video({**common, "id": "b", "range": "HDR10"}) + remote = deserialize_video({**common, "id": "z", "range": "HDR10P"}) + assert match_track(remote, [sdr, hdr10]) is hdr10 + + def test_match_track_no_candidates_returns_none() -> None: remote = deserialize_video({"id": "X", "codec": "AVC", "width": 1, "height": 1}) assert match_track(remote, []) is None diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 2b30b347..4edf14a3 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -2597,10 +2597,13 @@ class dl: if keep_videos and range_: # Special handling for HYBRID - don't filter, keep all HDR10 and DV tracks if Video.Range.HYBRID not in range_: + dv_has_hdr10p = any(x.hdr10plus for x in title.tracks.videos) title.tracks.select_video(lambda x: x.range in range_) missing_ranges = [r for r in range_ if not any(x.range == r for x in title.tracks.videos)] for color_range in missing_ranges: self.log.warning(f"Skipping {color_range.name} video tracks as none are available.") + if Video.Range.HDR10P in missing_ranges and dv_has_hdr10p: + self.log.warning("A DV track carries HDR10+: use -r DV or -r HYBRID.") if not title.tracks.videos: self.log.error(f"There's no {', '.join(r.name for r in range_)} Video Track...") sys.exit(1) diff --git a/unshackle/core/remote_service.py b/unshackle/core/remote_service.py index cc8c1e42..d134a323 100644 --- a/unshackle/core/remote_service.py +++ b/unshackle/core/remote_service.py @@ -549,7 +549,11 @@ def same_bitrate(local: Optional[int], remote: Optional[int]) -> bool: def match_track(remote_track: Track, local_tracks: list) -> Optional[Track]: - """Find the locally-parsed track that matches a remote track by ID or by attributes.""" + """Find the locally-parsed track that matches a remote track by ID or by attributes. + + HDR10 matches HDR10P, because the server's HDR10+ probe relabels a track that the local re-parse calls HDR10. + """ + hdr10_ranges = {Video.Range.HDR10, Video.Range.HDR10P} remote_id = str(remote_track.id) for lt in local_tracks: if str(lt.id) == remote_id: @@ -564,7 +568,7 @@ def match_track(remote_track: Track, local_tracks: list) -> Optional[Track]: if ( lt.width == remote_track.width and lt.height == remote_track.height - and lt.range == remote_track.range + and (lt.range == remote_track.range or {lt.range, remote_track.range} == hdr10_ranges) and same_bitrate(lt.bitrate, remote_track.bitrate) ): return lt