From 3426fc145f7abc9cc526a196d1701a93bf551b2e Mon Sep 17 00:00:00 2001
From: rlaphoenix <17136956+rlaphoenix@users.noreply.github.com>
Date: Fri, 17 May 2024 01:15:37 +0100
Subject: [PATCH] fix(HLS): Decrypt AES-encrypted segments separately

We cannot merge all the encrypted AES-128-CBC (ClearKey) segments and then decrypt them in one go because each segment should be padded to a 16-byte boundary in CBC mode.

Since it uses PKCS#5 or #7 style (cant remember which) then the merged file has a 15 in 16 chance to fail the boundary check. And in the 1 in 16 odds that it passes the boundary check, it will not decrypt properly as each segment's padding will be treated as actual data, and not padding.
---
 devine/core/manifests/hls.py | 30 +++++++++++++++++++++---------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/devine/core/manifests/hls.py b/devine/core/manifests/hls.py
index 4af583d..946acc3 100644
--- a/devine/core/manifests/hls.py
+++ b/devine/core/manifests/hls.py
@@ -387,15 +387,27 @@ class HLS:
                 elif len(files) != range_len:
                     raise ValueError(f"Missing {range_len - len(files)} segment files for {segment_range}...")
 
-                merge(
-                    to=merged_path,
-                    via=files,
-                    delete=True,
-                    include_map_data=True
-                )
-
-                drm.decrypt(merged_path)
-                merged_path.rename(decrypted_path)
+                if isinstance(drm, Widevine):
+                    # with widevine we can merge all segments and decrypt once
+                    merge(
+                        to=merged_path,
+                        via=files,
+                        delete=True,
+                        include_map_data=True
+                    )
+                    drm.decrypt(merged_path)
+                    merged_path.rename(decrypted_path)
+                else:
+                    # with other drm we must decrypt separately and then merge them
+                    # for aes this is because each segment likely has 16-byte padding
+                    for file in files:
+                        drm.decrypt(file)
+                    merge(
+                        to=merged_path,
+                        via=files,
+                        delete=True,
+                        include_map_data=True
+                    )
 
                 events.emit(
                     events.Types.TRACK_DECRYPTED,