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
|
||||
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):
|
||||
init_url = self.format_initialization(**kwargs)
|
||||
init_url = self.format_initialization(base_url, **kwargs)
|
||||
if init_url:
|
||||
yield Segment(
|
||||
url=init_url,
|
||||
|
@ -623,7 +623,7 @@ class SegmentTemplate(MPDNode):
|
|||
init=True,
|
||||
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(
|
||||
url=media_url,
|
||||
duration=self.duration_seconds,
|
||||
|
@ -632,18 +632,13 @@ class SegmentTemplate(MPDNode):
|
|||
available_at=available_at,
|
||||
)
|
||||
|
||||
def make_url(self, url: str) -> str:
|
||||
"""
|
||||
Join the URL with the base URL, unless it's an absolute URL
|
||||
:param url: maybe relative URL
|
||||
:return: joined URL
|
||||
"""
|
||||
@staticmethod
|
||||
def make_url(base_url: str, url: str) -> str:
|
||||
return BaseURL.join(base_url, url)
|
||||
|
||||
return BaseURL.join(self.base_url, url)
|
||||
|
||||
def format_initialization(self, **kwargs) -> Optional[str]:
|
||||
def format_initialization(self, base_url: str, **kwargs) -> Optional[str]:
|
||||
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]]:
|
||||
"""
|
||||
|
@ -698,10 +693,10 @@ class SegmentTemplate(MPDNode):
|
|||
|
||||
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:
|
||||
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
|
||||
return
|
||||
|
||||
|
@ -714,7 +709,7 @@ class SegmentTemplate(MPDNode):
|
|||
|
||||
if self.root.type == "static":
|
||||
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 ?!
|
||||
yield url, available_at
|
||||
return
|
||||
|
@ -731,7 +726,7 @@ class SegmentTemplate(MPDNode):
|
|||
# the last segment in the timeline is the most recent one
|
||||
# so, work backwards and calculate when each of the segments was
|
||||
# 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)
|
||||
|
||||
# once the suggested_delay is reach stop
|
||||
|
@ -830,6 +825,7 @@ class Representation(MPDNode):
|
|||
|
||||
if segmentTemplate:
|
||||
yield from segmentTemplate.segments(
|
||||
self.base_url,
|
||||
RepresentationID=self.id,
|
||||
Bandwidth=int(self.bandwidth * 1000),
|
||||
**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)),
|
||||
],
|
||||
]
|
||||
|
||||
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