mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
fix(dash): number SegmentTimeline segments from startNumber
Timeline manifests with startNumber between 2 and the segment count capped the range at the count, truncating live replays.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Segment numbering must start at ``startNumber`` and cover every segment, in both
|
||||
``SegmentTemplate`` forms: an explicit ``SegmentTimeline``, and a fixed segment duration."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from unshackle.core.manifests.dash import DASH
|
||||
from unshackle.core.utils.xml import load_xml
|
||||
|
||||
TIMELINE_MPD = """
|
||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" mediaPresentationDuration="PT10S">
|
||||
<Period id="0" start="PT0S">
|
||||
<AdaptationSet mimeType="video/mp4">
|
||||
<SegmentTemplate timescale="1" media="v/$Number$.mp4" startNumber="{start}">
|
||||
<SegmentTimeline><S t="0" d="1" r="{repeat}"/></SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
<Representation id="v" bandwidth="1"/>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
"""
|
||||
|
||||
# github.com/unshackle-dl/unshackle/issues/37: 96.72s at 6s per segment is 17 segments, 0 to 16, never a segment 17.
|
||||
DURATION_MPD = """
|
||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" mediaPresentationDuration="PT{seconds}S">
|
||||
<Period id="0" start="PT0S" duration="PT{seconds}S">
|
||||
<AdaptationSet mimeType="video/mp4">
|
||||
<SegmentTemplate timescale="25" duration="150" media="v/$Number$.mp4" startNumber="{start}"/>
|
||||
<Representation id="v" bandwidth="1"/>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
"""
|
||||
|
||||
|
||||
def _numbers(mpd: str) -> list[int]:
|
||||
tree = load_xml(mpd.encode())
|
||||
period = tree.find("Period")
|
||||
aset = period.find("AdaptationSet")
|
||||
rep = aset.find("Representation")
|
||||
_, segments, _, _, _ = DASH.get_period_segments(
|
||||
period, aset, rep, tree, MagicMock(), "https://example.com/main.mpd", MagicMock()
|
||||
)
|
||||
return [int(url.rsplit("/", 1)[1].split(".")[0]) for url, _ in segments]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("start", "count"),
|
||||
[
|
||||
(0, 5), # zero-based numbering
|
||||
(1, 5), # plain VOD
|
||||
(2642, 4667), # DVR catch-up: startNumber below segment count
|
||||
(5000, 10), # DVR catch-up: startNumber above segment count
|
||||
],
|
||||
)
|
||||
def test_timeline_numbers_follow_start_number(start: int, count: int) -> None:
|
||||
assert _numbers(TIMELINE_MPD.format(start=start, repeat=count - 1)) == list(range(start, start + count))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("start", "seconds", "count"), [(0, "1M36.720", 17), (1, "1M36.720", 17), (2642, "30", 5)])
|
||||
def test_duration_numbers_follow_start_number(start: int, seconds: str, count: int) -> None:
|
||||
assert _numbers(DURATION_MPD.format(start=start, seconds=seconds)) == list(range(start, start + count))
|
||||
@@ -830,9 +830,6 @@ class DASH:
|
||||
current_time += int(s.get("d"))
|
||||
|
||||
if not end_number:
|
||||
end_number = len(segment_durations)
|
||||
# Handle high startNumber in DVR/catch-up manifests where startNumber > segment count
|
||||
if start_number > end_number:
|
||||
end_number = start_number + len(segment_durations) - 1
|
||||
|
||||
for t, n in zip(segment_durations, range(start_number, end_number + 1)):
|
||||
|
||||
Reference in New Issue
Block a user