From 23466cae8b2d0d3664d6032e08fa99dd0875d840 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 25 Mar 2026 14:36:26 -0600 Subject: [PATCH] fix(drm): add track ID fallback for mp4decrypt CBCS zero-KID content Some CBCS-encrypted content has an all-zeros default_KID in the tenc box while the real KID is only in the PSSH boxes. mp4decrypt matches keys against the tenc KID, so it silently skips decryption when the provided KID doesn't match. This adds a track ID-based key fallback when a zero KID is detected, matching the existing shaka-packager zero-KID fallback behavior. --- unshackle/core/drm/playready.py | 19 +++++++++++++++++++ unshackle/core/drm/widevine.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/unshackle/core/drm/playready.py b/unshackle/core/drm/playready.py index f23e1b8..107adbc 100644 --- a/unshackle/core/drm/playready.py +++ b/unshackle/core/drm/playready.py @@ -356,6 +356,25 @@ class PlayReady: key_hex = key if isinstance(key, str) else key.hex() key_args.extend(["--key", f"{kid_hex}:{key_hex}"]) + # Also pass keys by track ID as fallback for CBCS content where the + # tenc box may have an all-zeros default_KID (common with HLS/FairPlay), + # causing mp4decrypt to fail matching KID-based keys silently. + if self.content_keys: + first_key = next(iter(self.content_keys.values())) + first_key_hex = first_key if isinstance(first_key, str) else first_key.hex() + try: + with open(path, "rb") as f: + header = f.read(200_000) + tenc_idx = header.find(b"tenc") + if tenc_idx >= 0: + tenc_data = header[tenc_idx + 4:] # after box type + version = tenc_data[0] + default_kid = tenc_data[8:24] if version >= 1 else tenc_data[6:22] + if default_kid == b"\x00" * 16: + key_args.extend(["--key", f"1:{first_key_hex}"]) + except OSError: + pass + cmd = [ str(binaries.Mp4decrypt), "--show-progress", diff --git a/unshackle/core/drm/widevine.py b/unshackle/core/drm/widevine.py index 6ca4fb5..c9cb9fd 100644 --- a/unshackle/core/drm/widevine.py +++ b/unshackle/core/drm/widevine.py @@ -276,6 +276,25 @@ class Widevine: key_hex = key if isinstance(key, str) else key.hex() key_args.extend(["--key", f"{kid_hex}:{key_hex}"]) + # Also pass keys by track ID as fallback for CBCS content where the + # tenc box may have an all-zeros default_KID (common with HLS/FairPlay), + # causing mp4decrypt to fail matching KID-based keys silently. + if self.content_keys: + first_key = next(iter(self.content_keys.values())) + first_key_hex = first_key if isinstance(first_key, str) else first_key.hex() + try: + with open(path, "rb") as f: + header = f.read(200_000) + tenc_idx = header.find(b"tenc") + if tenc_idx >= 0: + tenc_data = header[tenc_idx + 4:] # after box type + version = tenc_data[0] + default_kid = tenc_data[8:24] if version >= 1 else tenc_data[6:22] + if default_kid == b"\x00" * 16: + key_args.extend(["--key", f"1:{first_key_hex}"]) + except OSError: + pass + cmd = [ str(binaries.Mp4decrypt), "--show-progress",