mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-05-17 06:09:29 +00:00
fix(hls): carry DRM keys forward across EXT-X-KEY rotation
When the active EXT-X-KEY changes but no segments precede the new key (e.g. rotation at the first segment), no separate decrypt batch is flushed for the previous DRM and its content keys are lost. The merged file still contains samples encrypted under those keys, so the final mp4decrypt/shaka call decrypts them as garbage. Carry the previous DRM's content keys into the new DRM via setdefault so every key needed across the merged segments is present at decrypt time. Existing zero-KID fallback handling (PlayReady, Widevine) remains the disambiguator for tracks whose tenc default_KID is all-zero.
This commit is contained in:
@@ -359,9 +359,8 @@ class PlayReady:
|
|||||||
key_hex = key if isinstance(key, str) else key.hex()
|
key_hex = key if isinstance(key, str) else key.hex()
|
||||||
key_args.extend(["--key", f"{kid_hex}:{key_hex}"])
|
key_args.extend(["--key", f"{kid_hex}:{key_hex}"])
|
||||||
|
|
||||||
# Some services use a blank/zero default KID in the tenc box,
|
# Fallback for tracks whose tenc default_KID is all-zero and whose real
|
||||||
# but the real KID for the license server. Add zero-KID fallback entries so
|
# KID is signalled out-of-band: emit a zero-KID entry per content key.
|
||||||
# mp4decrypt can match when the file's default KID is all zeros.
|
|
||||||
zero_kid = "00" * 16
|
zero_kid = "00" * 16
|
||||||
existing_kids = {kid.hex if hasattr(kid, "hex") else str(kid).replace("-", "") for kid in self.content_keys}
|
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:
|
if zero_kid not in existing_kids:
|
||||||
|
|||||||
@@ -296,9 +296,8 @@ class Widevine:
|
|||||||
key_hex = key if isinstance(key, str) else key.hex()
|
key_hex = key if isinstance(key, str) else key.hex()
|
||||||
key_args.extend(["--key", f"{kid_hex}:{key_hex}"])
|
key_args.extend(["--key", f"{kid_hex}:{key_hex}"])
|
||||||
|
|
||||||
# Some services use a blank/zero default KID in the tenc box,
|
# Fallback for tracks whose tenc default_KID is all-zero and whose real
|
||||||
# but the real KID for the license server. Add zero-KID fallback entries so
|
# KID is signalled out-of-band: emit a zero-KID entry per content key.
|
||||||
# mp4decrypt can match when the file's default KID is all zeros.
|
|
||||||
zero_kid = "00" * 16
|
zero_kid = "00" * 16
|
||||||
existing_kids = {kid.hex if hasattr(kid, "hex") else str(kid).replace("-", "") for kid in self.content_keys}
|
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:
|
if zero_kid not in existing_kids:
|
||||||
|
|||||||
@@ -858,6 +858,14 @@ class HLS:
|
|||||||
DOWNLOAD_CANCELLED.set() # skip pending track downloads
|
DOWNLOAD_CANCELLED.set() # skip pending track downloads
|
||||||
progress(downloaded="[red]FAILED")
|
progress(downloaded="[red]FAILED")
|
||||||
raise
|
raise
|
||||||
|
if (
|
||||||
|
encryption_data
|
||||||
|
and isinstance(drm, (Widevine, PlayReady))
|
||||||
|
and isinstance(encryption_data[1], type(drm))
|
||||||
|
and getattr(encryption_data[1], "content_keys", None)
|
||||||
|
):
|
||||||
|
for prev_kid, prev_key in encryption_data[1].content_keys.items():
|
||||||
|
drm.content_keys.setdefault(prev_kid, prev_key)
|
||||||
encryption_data = (key, drm)
|
encryption_data = (key, drm)
|
||||||
|
|
||||||
if DOWNLOAD_LICENCE_ONLY.is_set():
|
if DOWNLOAD_LICENCE_ONLY.is_set():
|
||||||
|
|||||||
Reference in New Issue
Block a user