forked from DRMTalks/devine
fix(HLS): Use filtered out segment key info
Also simplifies calculation of wanted segment range when decrypting. Instead of storing the starting segment index number with the encryption_data variable, we just grab the first segment that isn't already merged. Fixes #77
This commit is contained in:
parent
499fc67ea0
commit
6e8efc3f63
|
@ -239,19 +239,22 @@ class HLS:
|
||||||
else:
|
else:
|
||||||
session_drm = None
|
session_drm = None
|
||||||
|
|
||||||
segments = [
|
unwanted_segments = [
|
||||||
segment for segment in master.segments
|
segment for segment in master.segments
|
||||||
if not callable(track.OnSegmentFilter) or not track.OnSegmentFilter(segment)
|
if callable(track.OnSegmentFilter) and track.OnSegmentFilter(segment)
|
||||||
]
|
]
|
||||||
|
|
||||||
total_segments = len(segments)
|
total_segments = len(master.segments) - len(unwanted_segments)
|
||||||
progress(total=total_segments)
|
progress(total=total_segments)
|
||||||
|
|
||||||
downloader_ = downloader
|
downloader_ = downloader
|
||||||
|
|
||||||
urls: list[dict[str, Any]] = []
|
urls: list[dict[str, Any]] = []
|
||||||
range_offset = 0
|
range_offset = 0
|
||||||
for segment in segments:
|
for segment in master.segments:
|
||||||
|
if segment in unwanted_segments:
|
||||||
|
continue
|
||||||
|
|
||||||
if segment.byterange:
|
if segment.byterange:
|
||||||
if downloader_.__name__ == "aria2c":
|
if downloader_.__name__ == "aria2c":
|
||||||
# aria2(c) is shit and doesn't support the Range header, fallback to the requests downloader
|
# aria2(c) is shit and doesn't support the Range header, fallback to the requests downloader
|
||||||
|
@ -260,6 +263,7 @@ class HLS:
|
||||||
range_offset = byte_range.split("-")[0]
|
range_offset = byte_range.split("-")[0]
|
||||||
else:
|
else:
|
||||||
byte_range = None
|
byte_range = None
|
||||||
|
|
||||||
urls.append({
|
urls.append({
|
||||||
"url": urljoin(segment.base_uri, segment.uri),
|
"url": urljoin(segment.base_uri, segment.uri),
|
||||||
"headers": {
|
"headers": {
|
||||||
|
@ -272,7 +276,7 @@ class HLS:
|
||||||
for status_update in downloader_(
|
for status_update in downloader_(
|
||||||
urls=urls,
|
urls=urls,
|
||||||
output_dir=segment_save_dir,
|
output_dir=segment_save_dir,
|
||||||
filename="{i:0%d}{ext}" % len(str(len(segments))),
|
filename="{i:0%d}{ext}" % len(str(len(urls))),
|
||||||
headers=session.headers,
|
headers=session.headers,
|
||||||
cookies=session.cookies,
|
cookies=session.cookies,
|
||||||
proxy=proxy,
|
proxy=proxy,
|
||||||
|
@ -289,19 +293,21 @@ class HLS:
|
||||||
|
|
||||||
progress(total=total_segments, completed=0, downloaded="Merging")
|
progress(total=total_segments, completed=0, downloaded="Merging")
|
||||||
|
|
||||||
|
name_len = len(str(total_segments))
|
||||||
discon_i = 0
|
discon_i = 0
|
||||||
range_offset = 0
|
range_offset = 0
|
||||||
map_data: Optional[tuple[m3u8.model.InitializationSection, bytes]] = None
|
map_data: Optional[tuple[m3u8.model.InitializationSection, bytes]] = None
|
||||||
if session_drm:
|
if session_drm:
|
||||||
encryption_data: Optional[tuple[int, Optional[m3u8.Key], DRM_T]] = (0, None, session_drm)
|
encryption_data: Optional[tuple[Optional[m3u8.Key], DRM_T]] = (None, session_drm)
|
||||||
else:
|
else:
|
||||||
encryption_data: Optional[tuple[int, Optional[m3u8.Key], DRM_T]] = None
|
encryption_data: Optional[tuple[Optional[m3u8.Key], DRM_T]] = None
|
||||||
|
|
||||||
for i, segment in enumerate(segments):
|
i = -1
|
||||||
is_last_segment = (i + 1) == total_segments
|
for real_i, segment in enumerate(master.segments):
|
||||||
name_len = len(str(total_segments))
|
if segment not in unwanted_segments:
|
||||||
segment_file_ext = get_extension(segment.uri)
|
i += 1
|
||||||
segment_file_path = segment_save_dir / f"{str(i).zfill(name_len)}{segment_file_ext}"
|
|
||||||
|
is_last_segment = (real_i + 1) == len(master.segments)
|
||||||
|
|
||||||
def merge(to: Path, via: list[Path], delete: bool = False, include_map_data: bool = False):
|
def merge(to: Path, via: list[Path], delete: bool = False, include_map_data: bool = False):
|
||||||
"""
|
"""
|
||||||
|
@ -339,13 +345,17 @@ class HLS:
|
||||||
|
|
||||||
Returns the decrypted path.
|
Returns the decrypted path.
|
||||||
"""
|
"""
|
||||||
drm = encryption_data[2]
|
drm = encryption_data[1]
|
||||||
first_segment_i = encryption_data[0]
|
first_segment_i = next(
|
||||||
|
int(file.stem)
|
||||||
|
for file in sorted(segment_save_dir.iterdir())
|
||||||
|
if file.stem.isdigit()
|
||||||
|
)
|
||||||
last_segment_i = max(0, i - int(not include_this_segment))
|
last_segment_i = max(0, i - int(not include_this_segment))
|
||||||
range_len = (last_segment_i - first_segment_i) + 1
|
range_len = (last_segment_i - first_segment_i) + 1
|
||||||
|
|
||||||
segment_range = f"{str(first_segment_i).zfill(name_len)}-{str(last_segment_i).zfill(name_len)}"
|
segment_range = f"{str(first_segment_i).zfill(name_len)}-{str(last_segment_i).zfill(name_len)}"
|
||||||
merged_path = segment_save_dir / f"{segment_range}{get_extension(segments[last_segment_i].uri)}"
|
merged_path = segment_save_dir / f"{segment_range}{get_extension(master.segments[last_segment_i].uri)}"
|
||||||
decrypted_path = segment_save_dir / f"{merged_path.stem}_decrypted{merged_path.suffix}"
|
decrypted_path = segment_save_dir / f"{merged_path.stem}_decrypted{merged_path.suffix}"
|
||||||
|
|
||||||
files = [
|
files = [
|
||||||
|
@ -405,57 +415,60 @@ class HLS:
|
||||||
include_map_data=include_map_data
|
include_map_data=include_map_data
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(track, Subtitle):
|
if segment not in unwanted_segments:
|
||||||
segment_data = try_ensure_utf8(segment_file_path.read_bytes())
|
if isinstance(track, Subtitle):
|
||||||
if track.codec not in (Subtitle.Codec.fVTT, Subtitle.Codec.fTTML):
|
segment_file_ext = get_extension(segment.uri)
|
||||||
segment_data = segment_data.decode("utf8"). \
|
segment_file_path = segment_save_dir / f"{str(i).zfill(name_len)}{segment_file_ext}"
|
||||||
replace("‎", html.unescape("‎")). \
|
segment_data = try_ensure_utf8(segment_file_path.read_bytes())
|
||||||
replace("‏", html.unescape("‏")). \
|
if track.codec not in (Subtitle.Codec.fVTT, Subtitle.Codec.fTTML):
|
||||||
encode("utf8")
|
segment_data = segment_data.decode("utf8"). \
|
||||||
segment_file_path.write_bytes(segment_data)
|
replace("‎", html.unescape("‎")). \
|
||||||
|
replace("‏", html.unescape("‏")). \
|
||||||
|
encode("utf8")
|
||||||
|
segment_file_path.write_bytes(segment_data)
|
||||||
|
|
||||||
if segment.discontinuity and i != 0:
|
if segment.discontinuity and i != 0:
|
||||||
if encryption_data:
|
if encryption_data:
|
||||||
decrypt(include_this_segment=False)
|
decrypt(include_this_segment=False)
|
||||||
merge_discontinuity(
|
merge_discontinuity(
|
||||||
include_this_segment=False,
|
include_this_segment=False,
|
||||||
include_map_data=not encryption_data or not encryption_data[2]
|
include_map_data=not encryption_data or not encryption_data[1]
|
||||||
)
|
|
||||||
|
|
||||||
discon_i += 1
|
|
||||||
range_offset = 0 # TODO: Should this be reset or not?
|
|
||||||
map_data = None
|
|
||||||
if encryption_data:
|
|
||||||
encryption_data = (i, encryption_data[1], encryption_data[2])
|
|
||||||
|
|
||||||
if segment.init_section and (not map_data or segment.init_section != map_data[0]):
|
|
||||||
if segment.init_section.byterange:
|
|
||||||
init_byte_range = HLS.calculate_byte_range(
|
|
||||||
segment.init_section.byterange,
|
|
||||||
range_offset
|
|
||||||
)
|
)
|
||||||
range_offset = init_byte_range.split("-")[0]
|
|
||||||
init_range_header = {
|
|
||||||
"Range": f"bytes={init_byte_range}"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
init_range_header = {}
|
|
||||||
|
|
||||||
res = session.get(
|
discon_i += 1
|
||||||
url=urljoin(segment.init_section.base_uri, segment.init_section.uri),
|
range_offset = 0 # TODO: Should this be reset or not?
|
||||||
headers=init_range_header
|
map_data = None
|
||||||
)
|
if encryption_data:
|
||||||
res.raise_for_status()
|
encryption_data = (encryption_data[0], encryption_data[1])
|
||||||
map_data = (segment.init_section, res.content)
|
|
||||||
|
if segment.init_section and (not map_data or segment.init_section != map_data[0]):
|
||||||
|
if segment.init_section.byterange:
|
||||||
|
init_byte_range = HLS.calculate_byte_range(
|
||||||
|
segment.init_section.byterange,
|
||||||
|
range_offset
|
||||||
|
)
|
||||||
|
range_offset = init_byte_range.split("-")[0]
|
||||||
|
init_range_header = {
|
||||||
|
"Range": f"bytes={init_byte_range}"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
init_range_header = {}
|
||||||
|
|
||||||
|
res = session.get(
|
||||||
|
url=urljoin(segment.init_section.base_uri, segment.init_section.uri),
|
||||||
|
headers=init_range_header
|
||||||
|
)
|
||||||
|
res.raise_for_status()
|
||||||
|
map_data = (segment.init_section, res.content)
|
||||||
|
|
||||||
if segment.keys:
|
if segment.keys:
|
||||||
key = HLS.get_supported_key(segment.keys)
|
key = HLS.get_supported_key(segment.keys)
|
||||||
if encryption_data and encryption_data[1] != key and i != 0:
|
if encryption_data and encryption_data[0] != key and i != 0 and segment not in unwanted_segments:
|
||||||
decrypt(include_this_segment=False)
|
decrypt(include_this_segment=False)
|
||||||
|
|
||||||
if key is None:
|
if key is None:
|
||||||
encryption_data = None
|
encryption_data = None
|
||||||
elif not encryption_data or encryption_data[1] != key:
|
elif not encryption_data or encryption_data[0] != key:
|
||||||
drm = HLS.get_drm(key, proxy)
|
drm = HLS.get_drm(key, proxy)
|
||||||
if isinstance(drm, Widevine):
|
if isinstance(drm, Widevine):
|
||||||
try:
|
try:
|
||||||
|
@ -470,7 +483,7 @@ 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
|
||||||
encryption_data = (i, key, drm)
|
encryption_data = (key, drm)
|
||||||
|
|
||||||
# TODO: This wont work as we already downloaded
|
# TODO: This wont work as we already downloaded
|
||||||
if DOWNLOAD_LICENCE_ONLY.is_set():
|
if DOWNLOAD_LICENCE_ONLY.is_set():
|
||||||
|
@ -482,7 +495,7 @@ class HLS:
|
||||||
decrypt(include_this_segment=True)
|
decrypt(include_this_segment=True)
|
||||||
merge_discontinuity(
|
merge_discontinuity(
|
||||||
include_this_segment=True,
|
include_this_segment=True,
|
||||||
include_map_data=not encryption_data or not encryption_data[2]
|
include_map_data=not encryption_data or not encryption_data[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
progress(advance=1)
|
progress(advance=1)
|
||||||
|
|
Loading…
Reference in New Issue