mirror of
https://github.com/streamlink/streamlink
synced 2024-11-01 01:19:33 +01:00
stream.dash: refactor AdaptationSet+Representation
- Make `AdaptationSet` and `Representation` inherit from the common `_RepresentationBaseType` class - Move and logically group class definitions - Define common attributes in `_RepresentationBaseType` that get inherited from ancestor nodes of the same base type, and find common child nodes - Fix, clean up, reorder and selectively add attributes - Remove `Representation.numChannels` (invalid and unused) - Turn `Representation.lang` into a property which reads its value from the parent `AdaptationSet.lang` (it's not an attribute on `Representation` according to the spec, but it gets read by `DASHStream.parse_manifest`, so keep the alias) - Rename `contentProtection` attribute to `contentProtections` and `subRepresentation` to `subRepresentations`, to stay consistent with other attributes of child node lists - Fix tests with old assertions: non-inhertied attributes that led to different expectations (e.g. the resulting stream name)
This commit is contained in:
parent
fae1be42f3
commit
c04048d52e
@ -273,10 +273,10 @@ class DASHStream(Stream):
|
||||
|
||||
# Search for suitable video and audio representations
|
||||
for aset in mpd.periods[period].adaptationSets:
|
||||
if aset.contentProtection:
|
||||
if aset.contentProtections:
|
||||
raise PluginError(f"{source} is protected by DRM")
|
||||
for rep in aset.representations:
|
||||
if rep.contentProtection:
|
||||
if rep.contentProtections:
|
||||
raise PluginError(f"{source} is protected by DRM")
|
||||
if rep.mimeType.startswith("video"):
|
||||
video.append(rep)
|
||||
|
@ -504,6 +504,182 @@ class EventStream(MPDNode):
|
||||
__tag__ = "EventStream"
|
||||
|
||||
|
||||
class _RepresentationBaseType(MPDNode):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# mimeType must be set on the AdaptationSet or Representation
|
||||
self.mimeType: str = self.attr( # type: ignore[assignment]
|
||||
"mimeType",
|
||||
required=type(self) is Representation,
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
|
||||
self.profiles = self.attr(
|
||||
"profiles",
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.width = self.attr(
|
||||
"width",
|
||||
parser=int,
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.height = self.attr(
|
||||
"height",
|
||||
parser=int,
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.sar = self.attr(
|
||||
"sar",
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.frameRate = self.attr(
|
||||
"frameRate",
|
||||
parser=MPDParsers.frame_rate,
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.audioSamplingRate = self.attr(
|
||||
"audioSamplingRate",
|
||||
parser=int,
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.codecs = self.attr(
|
||||
"codecs",
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
self.scanType = self.attr(
|
||||
"scanType",
|
||||
inherited=_RepresentationBaseType,
|
||||
)
|
||||
|
||||
self.contentProtections = self.children(ContentProtection)
|
||||
|
||||
|
||||
class AdaptationSet(_RepresentationBaseType):
|
||||
__tag__ = "AdaptationSet"
|
||||
|
||||
parent: Period
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.id = self.attr("id")
|
||||
self.group = self.attr("group")
|
||||
self.lang = self.attr("lang")
|
||||
self.contentType = self.attr("contentType")
|
||||
self.par = self.attr("par")
|
||||
self.minBandwidth = self.attr("minBandwidth", parser=int)
|
||||
self.maxBandwidth = self.attr("maxBandwidth", parser=int)
|
||||
self.minWidth = self.attr("minWidth", parser=int)
|
||||
self.maxWidth = self.attr("maxWidth", parser=int)
|
||||
self.minHeight = self.attr("minHeight", parser=int)
|
||||
self.maxHeight = self.attr("maxHeight", parser=int)
|
||||
self.minFrameRate = self.attr("minFrameRate", parser=MPDParsers.frame_rate)
|
||||
self.maxFrameRate = self.attr("maxFrameRate", parser=MPDParsers.frame_rate)
|
||||
self.segmentAlignment = self.attr(
|
||||
"segmentAlignment",
|
||||
parser=MPDParsers.bool_str,
|
||||
default=False,
|
||||
)
|
||||
self.subsegmentAlignment = self.attr(
|
||||
"subsegmentAlignment",
|
||||
parser=MPDParsers.bool_str,
|
||||
default=False,
|
||||
)
|
||||
self.subsegmentStartsWithSAP = self.attr(
|
||||
"subsegmentStartsWithSAP",
|
||||
parser=int,
|
||||
default=0,
|
||||
)
|
||||
self.bitstreamSwitching = self.attr(
|
||||
"bitstreamSwitching",
|
||||
parser=MPDParsers.bool_str,
|
||||
)
|
||||
|
||||
self.baseURLs = self.children(BaseURL)
|
||||
self.segmentBase = self.only_child(SegmentBase, period=self.parent)
|
||||
self.segmentList = self.only_child(SegmentList, period=self.parent)
|
||||
self.segmentTemplate = self.only_child(SegmentTemplate, period=self.parent)
|
||||
self.representations = self.children(Representation, minimum=1, period=self.parent)
|
||||
|
||||
|
||||
class Representation(_RepresentationBaseType):
|
||||
__tag__ = "Representation"
|
||||
|
||||
parent: AdaptationSet
|
||||
|
||||
def __init__(self, *args, period: Period, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.period = period
|
||||
|
||||
self.id: str = self.attr( # type: ignore[assignment]
|
||||
"id",
|
||||
required=True,
|
||||
)
|
||||
self.bandwidth: float = self.attr( # type: ignore[assignment]
|
||||
"bandwidth",
|
||||
parser=lambda b: float(b) / 1000.0,
|
||||
required=True,
|
||||
)
|
||||
|
||||
self.ident = self.parent.parent.id, self.parent.id, self.id
|
||||
|
||||
self.baseURLs = self.children(BaseURL)
|
||||
self.subRepresentations = self.children(SubRepresentation)
|
||||
self.segmentBase = self.only_child(SegmentBase, period=self.period)
|
||||
self.segmentList = self.only_child(SegmentList, period=self.period)
|
||||
self.segmentTemplate = self.only_child(SegmentTemplate, period=self.period)
|
||||
|
||||
@property
|
||||
def lang(self):
|
||||
return self.parent.lang
|
||||
|
||||
@property
|
||||
def bandwidth_rounded(self) -> float:
|
||||
return round(self.bandwidth, 1 - int(math.log10(self.bandwidth)))
|
||||
|
||||
def segments(self, **kwargs) -> Iterator[Segment]:
|
||||
"""
|
||||
Segments are yielded when they are available
|
||||
|
||||
Segments appear on a timeline, for dynamic content they are only available at a certain time
|
||||
and sometimes for a limited time. For static content they are all available at the same time.
|
||||
|
||||
:param kwargs: extra args to pass to the segment template
|
||||
:return: yields Segments
|
||||
"""
|
||||
|
||||
# segmentBase = self.segmentBase or self.walk_back_get_attr("segmentBase")
|
||||
segmentList = self.segmentList or self.walk_back_get_attr("segmentList")
|
||||
segmentTemplate = self.segmentTemplate or self.walk_back_get_attr("segmentTemplate")
|
||||
|
||||
if segmentTemplate:
|
||||
yield from segmentTemplate.segments(
|
||||
self.ident,
|
||||
self.base_url,
|
||||
RepresentationID=self.id,
|
||||
Bandwidth=int(self.bandwidth * 1000),
|
||||
**kwargs,
|
||||
)
|
||||
elif segmentList:
|
||||
yield from segmentList.segments()
|
||||
else:
|
||||
yield Segment(
|
||||
url=self.base_url,
|
||||
number=None,
|
||||
duration=None,
|
||||
available_at=self.period.availabilityStartTime,
|
||||
init=True,
|
||||
content=True,
|
||||
byterange=None,
|
||||
)
|
||||
|
||||
|
||||
class SubRepresentation(_RepresentationBaseType):
|
||||
__tag__ = "SubRepresentation"
|
||||
|
||||
|
||||
class Initialization(MPDNode):
|
||||
__tag__ = "Initialization"
|
||||
|
||||
@ -599,74 +775,6 @@ class SegmentList(MPDNode):
|
||||
return BaseURL.join(self.base_url, url) if url else self.base_url
|
||||
|
||||
|
||||
class AdaptationSet(MPDNode):
|
||||
__tag__ = "AdaptationSet"
|
||||
|
||||
parent: "Period"
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.id = self.attr("id")
|
||||
self.group = self.attr("group")
|
||||
self.mimeType = self.attr("mimeType")
|
||||
self.lang = self.attr("lang")
|
||||
self.contentType = self.attr("contentType")
|
||||
self.par = self.attr("par")
|
||||
self.minBandwidth = self.attr("minBandwidth")
|
||||
self.maxBandwidth = self.attr("maxBandwidth")
|
||||
self.minWidth = self.attr(
|
||||
"minWidth",
|
||||
parser=int,
|
||||
)
|
||||
self.maxWidth = self.attr(
|
||||
"maxWidth",
|
||||
parser=int,
|
||||
)
|
||||
self.minHeight = self.attr(
|
||||
"minHeight",
|
||||
parser=int,
|
||||
)
|
||||
self.maxHeight = self.attr(
|
||||
"maxHeight",
|
||||
parser=int,
|
||||
)
|
||||
self.minFrameRate = self.attr(
|
||||
"minFrameRate",
|
||||
parser=MPDParsers.frame_rate,
|
||||
)
|
||||
self.maxFrameRate = self.attr(
|
||||
"maxFrameRate",
|
||||
parser=MPDParsers.frame_rate,
|
||||
)
|
||||
self.segmentAlignment = self.attr(
|
||||
"segmentAlignment",
|
||||
parser=MPDParsers.bool_str,
|
||||
default=False,
|
||||
)
|
||||
self.bitstreamSwitching = self.attr(
|
||||
"bitstreamSwitching",
|
||||
parser=MPDParsers.bool_str,
|
||||
)
|
||||
self.subsegmentAlignment = self.attr(
|
||||
"subsegmentAlignment",
|
||||
parser=MPDParsers.bool_str,
|
||||
default=False,
|
||||
)
|
||||
self.subsegmentStartsWithSAP = self.attr(
|
||||
"subsegmentStartsWithSAP",
|
||||
parser=int,
|
||||
default=0,
|
||||
)
|
||||
|
||||
self.baseURLs = self.children(BaseURL)
|
||||
self.segmentBase = self.only_child(SegmentBase, period=self.parent)
|
||||
self.segmentList = self.only_child(SegmentList, period=self.parent)
|
||||
self.segmentTemplate = self.only_child(SegmentTemplate, period=self.parent)
|
||||
self.representations = self.children(Representation, minimum=1, period=self.parent)
|
||||
self.contentProtection = self.children(ContentProtection)
|
||||
|
||||
|
||||
class SegmentTemplate(MPDNode):
|
||||
__tag__ = "SegmentTemplate"
|
||||
|
||||
@ -860,118 +968,6 @@ class SegmentTemplate(MPDNode):
|
||||
yield url, number, available_at
|
||||
|
||||
|
||||
class Representation(MPDNode):
|
||||
__tag__ = "Representation"
|
||||
|
||||
parent: "AdaptationSet"
|
||||
|
||||
def __init__(self, *args, period: "Period", **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.period = period
|
||||
|
||||
self.id: str = self.attr( # type: ignore[assignment]
|
||||
"id",
|
||||
required=True,
|
||||
)
|
||||
self.bandwidth: float = self.attr( # type: ignore[assignment]
|
||||
"bandwidth",
|
||||
parser=lambda b: float(b) / 1000.0,
|
||||
required=True,
|
||||
)
|
||||
self.mimeType: str = self.attr( # type: ignore[assignment]
|
||||
"mimeType",
|
||||
required=True,
|
||||
inherited=AdaptationSet,
|
||||
)
|
||||
|
||||
self.codecs = self.attr("codecs")
|
||||
self.startWithSAP = self.attr("startWithSAP")
|
||||
|
||||
# video
|
||||
self.width = self.attr(
|
||||
"width",
|
||||
parser=int,
|
||||
)
|
||||
self.height = self.attr(
|
||||
"height",
|
||||
parser=int,
|
||||
)
|
||||
self.frameRate = self.attr(
|
||||
"frameRate",
|
||||
parser=MPDParsers.frame_rate,
|
||||
)
|
||||
|
||||
# audio
|
||||
self.audioSamplingRate = self.attr(
|
||||
"audioSamplingRate",
|
||||
parser=int,
|
||||
)
|
||||
self.numChannels = self.attr(
|
||||
"numChannels",
|
||||
parser=int,
|
||||
)
|
||||
|
||||
# subtitle
|
||||
self.lang = self.attr(
|
||||
"lang",
|
||||
inherited=AdaptationSet,
|
||||
)
|
||||
|
||||
self.ident = self.parent.parent.id, self.parent.id, self.id
|
||||
|
||||
self.baseURLs = self.children(BaseURL)
|
||||
self.subRepresentation = self.children(SubRepresentation)
|
||||
self.segmentBase = self.only_child(SegmentBase, period=self.period)
|
||||
self.segmentList = self.only_child(SegmentList, period=self.period)
|
||||
self.segmentTemplate = self.only_child(SegmentTemplate, period=self.period)
|
||||
self.contentProtection = self.children(ContentProtection)
|
||||
|
||||
@property
|
||||
def bandwidth_rounded(self) -> float:
|
||||
return round(self.bandwidth, 1 - int(math.log10(self.bandwidth)))
|
||||
|
||||
def segments(self, **kwargs) -> Iterator[Segment]:
|
||||
"""
|
||||
Segments are yielded when they are available
|
||||
|
||||
Segments appear on a timeline, for dynamic content they are only available at a certain time
|
||||
and sometimes for a limited time. For static content they are all available at the same time.
|
||||
|
||||
:param kwargs: extra args to pass to the segment template
|
||||
:return: yields Segments
|
||||
"""
|
||||
|
||||
# segmentBase = self.segmentBase or self.walk_back_get_attr("segmentBase")
|
||||
segmentList = self.segmentList or self.walk_back_get_attr("segmentList")
|
||||
segmentTemplate = self.segmentTemplate or self.walk_back_get_attr("segmentTemplate")
|
||||
|
||||
if segmentTemplate:
|
||||
yield from segmentTemplate.segments(
|
||||
self.ident,
|
||||
self.base_url,
|
||||
RepresentationID=self.id,
|
||||
Bandwidth=int(self.bandwidth * 1000),
|
||||
**kwargs,
|
||||
)
|
||||
elif segmentList:
|
||||
yield from segmentList.segments()
|
||||
else:
|
||||
yield Segment(
|
||||
url=self.base_url,
|
||||
number=None,
|
||||
duration=None,
|
||||
available_at=self.period.availabilityStartTime,
|
||||
init=True,
|
||||
content=True,
|
||||
byterange=None,
|
||||
)
|
||||
|
||||
|
||||
class SubRepresentation(MPDNode):
|
||||
__tag__ = "SubRepresentation"
|
||||
|
||||
|
||||
class SegmentTimeline(MPDNode):
|
||||
__tag__ = "SegmentTimeline"
|
||||
|
||||
|
@ -49,10 +49,10 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_video_only(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -63,10 +63,10 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_audio_only(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="audio/mp4", bandwidth=128.0, lang="en"),
|
||||
Mock(id="2", contentProtection=None, mimeType="audio/mp4", bandwidth=256.0, lang="en"),
|
||||
Mock(id="1", contentProtections=None, mimeType="audio/mp4", bandwidth=128.0, lang="en"),
|
||||
Mock(id="2", contentProtections=None, mimeType="audio/mp4", bandwidth=256.0, lang="en"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -77,11 +77,11 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_audio_single(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -92,12 +92,12 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_audio_multi(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="4", contentProtection=None, mimeType="audio/aac", bandwidth=256.0, lang="en"),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="4", contentProtections=None, mimeType="audio/aac", bandwidth=256.0, lang="en"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -108,12 +108,12 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_audio_multi_lang(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="4", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="es"),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="4", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="es"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -126,12 +126,12 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_audio_multi_lang_alpha3(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="eng"),
|
||||
Mock(id="4", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="spa"),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="eng"),
|
||||
Mock(id="4", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="spa"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -144,11 +144,11 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
def test_audio_invalid_lang(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="en_no_voice"),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="en_no_voice"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -163,12 +163,12 @@ class TestDASHStreamParseManifest:
|
||||
session.set_option("locale", "es_ES")
|
||||
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="4", contentProtection=None, mimeType="audio/aac", bandwidth=128.0, lang="es"),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080),
|
||||
Mock(id="3", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="en"),
|
||||
Mock(id="4", contentProtections=None, mimeType="audio/aac", bandwidth=128.0, lang="es"),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -182,12 +182,12 @@ class TestDASHStreamParseManifest:
|
||||
# Verify the fix for https://github.com/streamlink/streamlink/issues/3365
|
||||
def test_duplicated_resolutions(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=1080, bandwidth=128.0),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080, bandwidth=64.0),
|
||||
Mock(id="3", contentProtection=None, mimeType="video/mp4", height=1080, bandwidth=32.0),
|
||||
Mock(id="4", contentProtection=None, mimeType="video/mp4", height=720),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=1080, bandwidth=128.0),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080, bandwidth=64.0),
|
||||
Mock(id="3", contentProtections=None, mimeType="video/mp4", height=1080, bandwidth=32.0),
|
||||
Mock(id="4", contentProtections=None, mimeType="video/mp4", height=720),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -199,11 +199,11 @@ class TestDASHStreamParseManifest:
|
||||
# Verify the fix for https://github.com/streamlink/streamlink/issues/4217
|
||||
def test_duplicated_resolutions_sorted_bandwidth(self, session: Streamlink, mpd: Mock):
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[
|
||||
Mock(id="1", contentProtection=None, mimeType="video/mp4", height=1080, bandwidth=64.0),
|
||||
Mock(id="2", contentProtection=None, mimeType="video/mp4", height=1080, bandwidth=128.0),
|
||||
Mock(id="3", contentProtection=None, mimeType="video/mp4", height=1080, bandwidth=32.0),
|
||||
Mock(id="1", contentProtections=None, mimeType="video/mp4", height=1080, bandwidth=64.0),
|
||||
Mock(id="2", contentProtections=None, mimeType="video/mp4", height=1080, bandwidth=128.0),
|
||||
Mock(id="3", contentProtections=None, mimeType="video/mp4", height=1080, bandwidth=32.0),
|
||||
],
|
||||
)
|
||||
mpd.return_value = Mock(periods=[Mock(adaptationSets=[adaptationset])])
|
||||
@ -216,11 +216,11 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
@pytest.mark.parametrize("adaptationset", [
|
||||
pytest.param(
|
||||
Mock(contentProtection="DRM", representations=[]),
|
||||
Mock(contentProtections="DRM", representations=[]),
|
||||
id="ContentProtection on AdaptationSet",
|
||||
),
|
||||
pytest.param(
|
||||
Mock(contentProtection=None, representations=[Mock(id="1", contentProtection="DRM")]),
|
||||
Mock(contentProtections=None, representations=[Mock(id="1", contentProtections="DRM")]),
|
||||
id="ContentProtection on Representation",
|
||||
),
|
||||
])
|
||||
@ -239,7 +239,7 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
streams = DASHStream.parse_manifest(session, test_manifest)
|
||||
assert mpd.call_args_list == [call(ANY)]
|
||||
assert list(streams.keys()) == ["2500k"]
|
||||
assert list(streams.keys()) == ["480p"]
|
||||
|
||||
# TODO: Move this test to test_dash_parser and properly test segment URLs.
|
||||
# This test currently achieves nothing... (manifest fixture added in 7aada92)
|
||||
@ -249,7 +249,7 @@ class TestDASHStreamParseManifest:
|
||||
|
||||
streams = DASHStream.parse_manifest(session, "http://test/manifest.mpd")
|
||||
assert mpd.call_args_list == [call(ANY, url="http://test/manifest.mpd", base_url="http://test")]
|
||||
assert list(streams.keys()) == ["2500k"]
|
||||
assert list(streams.keys()) == ["480p"]
|
||||
|
||||
|
||||
class TestDASHStreamOpen:
|
||||
@ -318,7 +318,7 @@ class TestDASHStreamWorker:
|
||||
height=720,
|
||||
)
|
||||
adaptationset = Mock(
|
||||
contentProtection=None,
|
||||
contentProtections=None,
|
||||
representations=[representation],
|
||||
)
|
||||
period = Mock(
|
||||
|
Loading…
Reference in New Issue
Block a user