mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-03-10 16:39:01 +00:00
fix(dash): pass period_filter to n_m3u8dl_re via filtered MPD file
The period_filter in DASH.to_tracks() only affected track listing but had no effect on n_m3u8dl_re downloads, which re-parsed the raw MPD and downloaded all periods including ads/pre-rolls. This caused DRM decryption failures and corrupted video output. When periods are filtered during to_tracks(), write a filtered MPD (with rejected periods removed) to a temp file and pass it to n_m3u8dl_re via track.from_file. Closes #51
This commit is contained in:
@@ -7,7 +7,7 @@ import math
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from copy import copy
|
from copy import copy, deepcopy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Optional, Union
|
from typing import Any, Callable, Optional, Union
|
||||||
@@ -18,6 +18,7 @@ from zlib import crc32
|
|||||||
import requests
|
import requests
|
||||||
from curl_cffi.requests import Session as CurlSession
|
from curl_cffi.requests import Session as CurlSession
|
||||||
from langcodes import Language, tag_is_valid
|
from langcodes import Language, tag_is_valid
|
||||||
|
from lxml import etree
|
||||||
from lxml.etree import Element, ElementTree
|
from lxml.etree import Element, ElementTree
|
||||||
from pyplayready.system.pssh import PSSH as PR_PSSH
|
from pyplayready.system.pssh import PSSH as PR_PSSH
|
||||||
from pywidevine.cdm import Cdm as WidevineCdm
|
from pywidevine.cdm import Cdm as WidevineCdm
|
||||||
@@ -101,14 +102,22 @@ class DASH:
|
|||||||
"""
|
"""
|
||||||
tracks = Tracks()
|
tracks = Tracks()
|
||||||
|
|
||||||
|
filtered_period_ids: list[str] = []
|
||||||
|
|
||||||
for period in self.manifest.findall("Period"):
|
for period in self.manifest.findall("Period"):
|
||||||
if callable(period_filter) and period_filter(period):
|
if callable(period_filter) and period_filter(period):
|
||||||
|
if period_id := period.get("id"):
|
||||||
|
filtered_period_ids.append(period_id)
|
||||||
continue
|
continue
|
||||||
if next(iter(period.xpath("SegmentType/@value")), "content") != "content":
|
if next(iter(period.xpath("SegmentType/@value")), "content") != "content":
|
||||||
|
if period_id := period.get("id"):
|
||||||
|
filtered_period_ids.append(period_id)
|
||||||
continue
|
continue
|
||||||
if "urn:amazon:primevideo:cachingBreadth" in [
|
if "urn:amazon:primevideo:cachingBreadth" in [
|
||||||
x.get("schemeIdUri") for x in period.findall("SupplementalProperty")
|
x.get("schemeIdUri") for x in period.findall("SupplementalProperty")
|
||||||
]:
|
]:
|
||||||
|
if period_id := period.get("id"):
|
||||||
|
filtered_period_ids.append(period_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for adaptation_set in period.findall("AdaptationSet"):
|
for adaptation_set in period.findall("AdaptationSet"):
|
||||||
@@ -235,6 +244,7 @@ class DASH:
|
|||||||
"period": period,
|
"period": period,
|
||||||
"adaptation_set": adaptation_set,
|
"adaptation_set": adaptation_set,
|
||||||
"representation": rep,
|
"representation": rep,
|
||||||
|
"filtered_period_ids": filtered_period_ids,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
**track_args,
|
**track_args,
|
||||||
@@ -541,6 +551,26 @@ class DASH:
|
|||||||
skip_merge = False
|
skip_merge = False
|
||||||
if downloader.__name__ == "n_m3u8dl_re":
|
if downloader.__name__ == "n_m3u8dl_re":
|
||||||
skip_merge = True
|
skip_merge = True
|
||||||
|
|
||||||
|
# When periods were filtered out during to_tracks(), n_m3u8dl_re will re-parse
|
||||||
|
# the raw MPD and download ALL periods (including ads/pre-rolls). Write a filtered
|
||||||
|
# MPD with the rejected periods removed so n_m3u8dl_re downloads the correct content.
|
||||||
|
filtered_period_ids = track.data.get("dash", {}).get("filtered_period_ids", [])
|
||||||
|
if filtered_period_ids:
|
||||||
|
filtered_manifest = deepcopy(manifest)
|
||||||
|
for child in list(filtered_manifest):
|
||||||
|
if not hasattr(child.tag, "find"):
|
||||||
|
continue
|
||||||
|
if child.tag == "Period" and child.get("id") in filtered_period_ids:
|
||||||
|
filtered_manifest.remove(child)
|
||||||
|
|
||||||
|
filtered_mpd_path = save_dir / f".{track.id}_filtered.mpd"
|
||||||
|
filtered_mpd_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
etree.ElementTree(filtered_manifest).write(
|
||||||
|
str(filtered_mpd_path), xml_declaration=True, encoding="utf-8"
|
||||||
|
)
|
||||||
|
track.from_file = filtered_mpd_path
|
||||||
|
|
||||||
downloader_args.update(
|
downloader_args.update(
|
||||||
{
|
{
|
||||||
"filename": track.id,
|
"filename": track.id,
|
||||||
@@ -578,6 +608,11 @@ class DASH:
|
|||||||
status_update["downloaded"] = f"DASH {downloaded}"
|
status_update["downloaded"] = f"DASH {downloaded}"
|
||||||
progress(**status_update)
|
progress(**status_update)
|
||||||
|
|
||||||
|
# Clean up filtered MPD temp file before enumerating segments
|
||||||
|
filtered_mpd_path = save_dir / f".{track.id}_filtered.mpd"
|
||||||
|
if filtered_mpd_path.exists():
|
||||||
|
filtered_mpd_path.unlink()
|
||||||
|
|
||||||
# see https://github.com/devine-dl/devine/issues/71
|
# see https://github.com/devine-dl/devine/issues/71
|
||||||
for control_file in save_dir.glob("*.aria2__temp"):
|
for control_file in save_dir.glob("*.aria2__temp"):
|
||||||
control_file.unlink()
|
control_file.unlink()
|
||||||
|
|||||||
Reference in New Issue
Block a user