From faaaf08bd5c625050c4e8943b907d2426ff17c6e Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 19 Mar 2026 18:43:43 -0600 Subject: [PATCH] fix(drm): add zero-KID fallback for mp4decrypt and clear HLS track.drm after download mp4decrypt silently copies files unchanged when the tenc box default KID is all zeros, since none of the real KID:KEY pairs match. Add zero-KID fallback entries to both Widevine and PlayReady mp4decrypt methods, matching what Shaka Packager already does. Also clear track.drm after HLS download when decryption was performed, preventing unnecessary double-decryption. DASH and URL descriptors already did this. --- unshackle/core/drm/playready.py | 13 +++++++++++++ unshackle/core/drm/widevine.py | 13 +++++++++++++ unshackle/core/manifests/hls.py | 6 ++++++ 3 files changed, 32 insertions(+) diff --git a/unshackle/core/drm/playready.py b/unshackle/core/drm/playready.py index f23e1b8..7907100 100644 --- a/unshackle/core/drm/playready.py +++ b/unshackle/core/drm/playready.py @@ -356,6 +356,19 @@ class PlayReady: key_hex = key if isinstance(key, str) else key.hex() key_args.extend(["--key", f"{kid_hex}:{key_hex}"]) + # Some services use a blank/zero default KID in the tenc box, + # but the real KID for the license server. Add zero-KID fallback entries so + # mp4decrypt can match when the file's default KID is all zeros. + zero_kid = "00" * 16 + existing_kids = { + kid.hex if hasattr(kid, "hex") else str(kid).replace("-", "") + for kid in self.content_keys + } + if zero_kid not in existing_kids: + for key in self.content_keys.values(): + key_hex = key if isinstance(key, str) else key.hex() + key_args.extend(["--key", f"{zero_kid}:{key_hex}"]) + cmd = [ str(binaries.Mp4decrypt), "--show-progress", diff --git a/unshackle/core/drm/widevine.py b/unshackle/core/drm/widevine.py index 64b14e7..191bc23 100644 --- a/unshackle/core/drm/widevine.py +++ b/unshackle/core/drm/widevine.py @@ -290,6 +290,19 @@ class Widevine: key_hex = key if isinstance(key, str) else key.hex() key_args.extend(["--key", f"{kid_hex}:{key_hex}"]) + # Some services use a blank/zero default KID in the tenc box, + # but the real KID for the license server. Add zero-KID fallback entries so + # mp4decrypt can match when the file's default KID is all zeros. + zero_kid = "00" * 16 + existing_kids = { + kid.hex if hasattr(kid, "hex") else str(kid).replace("-", "") + for kid in self.content_keys + } + if zero_kid not in existing_kids: + for key in self.content_keys.values(): + key_hex = key if isinstance(key, str) else key.hex() + key_args.extend(["--key", f"{zero_kid}:{key_hex}"]) + cmd = [ str(binaries.Mp4decrypt), "--show-progress", diff --git a/unshackle/core/manifests/hls.py b/unshackle/core/manifests/hls.py index 2904c79..43d2787 100644 --- a/unshackle/core/manifests/hls.py +++ b/unshackle/core/manifests/hls.py @@ -483,6 +483,8 @@ class HLS: final_save_path = HLS._finalize_n_m3u8dl_re_output(track=track, save_dir=save_dir, save_path=save_path) progress(downloaded="Downloaded") track.path = final_save_path + if session_drm: + track.drm = None events.emit(events.Types.TRACK_DOWNLOADED, track=track) return @@ -787,6 +789,10 @@ class HLS: progress(downloaded="Downloaded") track.path = save_path + + if session_drm: + track.drm = None + events.emit(events.Types.TRACK_DOWNLOADED, track=track) @staticmethod