mirror of https://github.com/streamlink/streamlink
stream.dash: fix SegmentTemplate's BaseURL context
This commit is contained in:
parent
2a24f314f2
commit
e6849137c2
|
@ -613,9 +613,9 @@ class SegmentTemplate(MPDNode):
|
||||||
# children
|
# children
|
||||||
self.segmentTimeline = self.only_child(SegmentTimeline)
|
self.segmentTimeline = self.only_child(SegmentTimeline)
|
||||||
|
|
||||||
def segments(self, **kwargs) -> Iterator[Segment]:
|
def segments(self, base_url: str, **kwargs) -> Iterator[Segment]:
|
||||||
if kwargs.pop("init", True):
|
if kwargs.pop("init", True):
|
||||||
init_url = self.format_initialization(**kwargs)
|
init_url = self.format_initialization(base_url, **kwargs)
|
||||||
if init_url:
|
if init_url:
|
||||||
yield Segment(
|
yield Segment(
|
||||||
url=init_url,
|
url=init_url,
|
||||||
|
@ -623,7 +623,7 @@ class SegmentTemplate(MPDNode):
|
||||||
init=True,
|
init=True,
|
||||||
content=False,
|
content=False,
|
||||||
)
|
)
|
||||||
for media_url, available_at in self.format_media(**kwargs):
|
for media_url, available_at in self.format_media(base_url, **kwargs):
|
||||||
yield Segment(
|
yield Segment(
|
||||||
url=media_url,
|
url=media_url,
|
||||||
duration=self.duration_seconds,
|
duration=self.duration_seconds,
|
||||||
|
@ -632,18 +632,13 @@ class SegmentTemplate(MPDNode):
|
||||||
available_at=available_at,
|
available_at=available_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_url(self, url: str) -> str:
|
@staticmethod
|
||||||
"""
|
def make_url(base_url: str, url: str) -> str:
|
||||||
Join the URL with the base URL, unless it's an absolute URL
|
return BaseURL.join(base_url, url)
|
||||||
:param url: maybe relative URL
|
|
||||||
:return: joined URL
|
|
||||||
"""
|
|
||||||
|
|
||||||
return BaseURL.join(self.base_url, url)
|
def format_initialization(self, base_url: str, **kwargs) -> Optional[str]:
|
||||||
|
|
||||||
def format_initialization(self, **kwargs) -> Optional[str]:
|
|
||||||
if self.initialization:
|
if self.initialization:
|
||||||
return self.make_url(self.initialization(**kwargs))
|
return self.make_url(base_url, self.initialization(**kwargs))
|
||||||
|
|
||||||
def segment_numbers(self) -> Iterator[Tuple[int, datetime.datetime]]:
|
def segment_numbers(self) -> Iterator[Tuple[int, datetime.datetime]]:
|
||||||
"""
|
"""
|
||||||
|
@ -698,10 +693,10 @@ class SegmentTemplate(MPDNode):
|
||||||
|
|
||||||
yield from zip(number_iter, available_iter)
|
yield from zip(number_iter, available_iter)
|
||||||
|
|
||||||
def format_media(self, **kwargs) -> Iterator[Tuple[str, datetime.datetime]]:
|
def format_media(self, base_url: str, **kwargs) -> Iterator[Tuple[str, datetime.datetime]]:
|
||||||
if not self.segmentTimeline:
|
if not self.segmentTimeline:
|
||||||
for number, available_at in self.segment_numbers():
|
for number, available_at in self.segment_numbers():
|
||||||
url = self.make_url(self.media(Number=number, **kwargs))
|
url = self.make_url(base_url, self.media(Number=number, **kwargs))
|
||||||
yield url, available_at
|
yield url, available_at
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -714,7 +709,7 @@ class SegmentTemplate(MPDNode):
|
||||||
|
|
||||||
if self.root.type == "static":
|
if self.root.type == "static":
|
||||||
for segment, n in zip(self.segmentTimeline.segments, count(self.startNumber)):
|
for segment, n in zip(self.segmentTimeline.segments, count(self.startNumber)):
|
||||||
url = self.make_url(self.media(Time=segment.t, Number=n, **kwargs))
|
url = self.make_url(base_url, self.media(Time=segment.t, Number=n, **kwargs))
|
||||||
available_at = datetime.datetime.now(tz=UTC) # TODO: replace with EPOCH_START ?!
|
available_at = datetime.datetime.now(tz=UTC) # TODO: replace with EPOCH_START ?!
|
||||||
yield url, available_at
|
yield url, available_at
|
||||||
return
|
return
|
||||||
|
@ -731,7 +726,7 @@ class SegmentTemplate(MPDNode):
|
||||||
# the last segment in the timeline is the most recent one
|
# the last segment in the timeline is the most recent one
|
||||||
# so, work backwards and calculate when each of the segments was
|
# so, work backwards and calculate when each of the segments was
|
||||||
# available, based on the durations relative to the publish-time
|
# available, based on the durations relative to the publish-time
|
||||||
url = self.make_url(self.media(Time=segment.t, Number=n, **kwargs))
|
url = self.make_url(base_url, self.media(Time=segment.t, Number=n, **kwargs))
|
||||||
duration = datetime.timedelta(seconds=segment.d / self.timescale)
|
duration = datetime.timedelta(seconds=segment.d / self.timescale)
|
||||||
|
|
||||||
# once the suggested_delay is reach stop
|
# once the suggested_delay is reach stop
|
||||||
|
@ -830,6 +825,7 @@ class Representation(MPDNode):
|
||||||
|
|
||||||
if segmentTemplate:
|
if segmentTemplate:
|
||||||
yield from segmentTemplate.segments(
|
yield from segmentTemplate.segments(
|
||||||
|
self.base_url,
|
||||||
RepresentationID=self.id,
|
RepresentationID=self.id,
|
||||||
Bandwidth=int(self.bandwidth * 1000),
|
Bandwidth=int(self.bandwidth * 1000),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<MPD
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||||
|
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
||||||
|
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
||||||
|
type="static"
|
||||||
|
mediaPresentationDuration="PT0H0M6.00S"
|
||||||
|
minBufferTime="PT6.0S"
|
||||||
|
>
|
||||||
|
<BaseURL>https://hostname/</BaseURL>
|
||||||
|
<Period id="0" start="PT0.0S">
|
||||||
|
<BaseURL>period/</BaseURL>
|
||||||
|
<AdaptationSet
|
||||||
|
id="0"
|
||||||
|
mimeType="video/mp4"
|
||||||
|
maxWidth="1920"
|
||||||
|
maxHeight="1080"
|
||||||
|
par="16:9"
|
||||||
|
frameRate="60"
|
||||||
|
segmentAlignment="true"
|
||||||
|
startWithSAP="1"
|
||||||
|
subsegmentAlignment="true"
|
||||||
|
subsegmentStartsWithSAP="1"
|
||||||
|
>
|
||||||
|
<SegmentTemplate
|
||||||
|
presentationTimeOffset="0"
|
||||||
|
timescale="90000"
|
||||||
|
duration="540000"
|
||||||
|
startNumber="1"
|
||||||
|
media="media_$RepresentationID$-$Number$.m4s"
|
||||||
|
initialization="init_$RepresentationID$.m4s"
|
||||||
|
/>
|
||||||
|
<Representation id="video_5000kbps" codecs="avc1.640028" width="1920" height="1080" sar="1:1" bandwidth="5000000"/>
|
||||||
|
<Representation id="video_9000kbps" codecs="avc1.640028" width="1920" height="1080" sar="1:1" bandwidth="9000000">
|
||||||
|
<BaseURL>representation/</BaseURL>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet
|
||||||
|
id="1"
|
||||||
|
mimeType="audio/mp4"
|
||||||
|
lang="eng"
|
||||||
|
segmentAlignment="true"
|
||||||
|
startWithSAP="1"
|
||||||
|
subsegmentAlignment="true"
|
||||||
|
subsegmentStartsWithSAP="1"
|
||||||
|
>
|
||||||
|
<BaseURL>adaptationset/</BaseURL>
|
||||||
|
<SegmentTemplate
|
||||||
|
presentationTimeOffset="0"
|
||||||
|
timescale="44100"
|
||||||
|
duration="264600"
|
||||||
|
startNumber="1"
|
||||||
|
media="media_$RepresentationID$-$Number$.m4s"
|
||||||
|
initialization="init_$RepresentationID$.m4s"
|
||||||
|
/>
|
||||||
|
<Representation id="audio_128kbps" codecs="mp4a.40.2" audioSamplingRate="44100" sar="1:1" bandwidth="128000"/>
|
||||||
|
<Representation id="audio_256kbps" codecs="mp4a.40.2" audioSamplingRate="44100" sar="1:1" bandwidth="256000">
|
||||||
|
<BaseURL>representation/</BaseURL>
|
||||||
|
</Representation>
|
||||||
|
<Representation id="audio_320kbps" codecs="mp4a.40.2" audioSamplingRate="44100" sar="1:1" bandwidth="320000">
|
||||||
|
<BaseURL>https://other/</BaseURL>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
|
@ -287,3 +287,33 @@ class TestMPDParser(unittest.TestCase):
|
||||||
("http://test/audio-frag.mp4", False, (374366, 471)),
|
("http://test/audio-frag.mp4", False, (374366, 471)),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def test_nested_baseurls(self):
|
||||||
|
with xml("dash/test_nested_baseurls.mpd") as mpd_xml:
|
||||||
|
mpd = MPD(mpd_xml, base_url="https://foo/", url="https://test/manifest.mpd")
|
||||||
|
segment_urls = [
|
||||||
|
[seg.url for seg in itertools.islice(representation.segments(), 2)]
|
||||||
|
for adaptationset in mpd.periods[0].adaptationSets for representation in adaptationset.representations
|
||||||
|
]
|
||||||
|
assert segment_urls == [
|
||||||
|
[
|
||||||
|
"https://hostname/period/init_video_5000kbps.m4s",
|
||||||
|
"https://hostname/period/media_video_5000kbps-1.m4s",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"https://hostname/period/representation/init_video_9000kbps.m4s",
|
||||||
|
"https://hostname/period/representation/media_video_9000kbps-1.m4s",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"https://hostname/period/adaptationset/init_audio_128kbps.m4s",
|
||||||
|
"https://hostname/period/adaptationset/media_audio_128kbps-1.m4s",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"https://hostname/period/adaptationset/representation/init_audio_256kbps.m4s",
|
||||||
|
"https://hostname/period/adaptationset/representation/media_audio_256kbps-1.m4s",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"https://other/init_audio_320kbps.m4s",
|
||||||
|
"https://other/media_audio_320kbps-1.m4s",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
Loading…
Reference in New Issue