perf(dash): collapse single-file tracks that have a sidx gap

This commit is contained in:
imSp4rky
2026-10-25 13:23:53 -06:00
parent 43fcb030f8
commit 0c58575a5b
4 changed files with 152 additions and 83 deletions
+2 -2
View File
@@ -34,7 +34,7 @@ STREAMS: list[tuple[str, str]] = [
("sony-1a", "https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd"),
]
guard_results: list[bool] = []
guard_results: list[Optional[tuple[int, int]]] = []
def install_guard_spy() -> bool:
@@ -43,7 +43,7 @@ def install_guard_spy() -> bool:
if orig is None:
return False
def spy(is_subtitle: bool, segments: Any, init_len: Optional[int]) -> bool:
def spy(is_subtitle: bool, segments: Any, init_len: int) -> Optional[tuple[int, int]]:
result = orig(is_subtitle, segments, init_len)
guard_results.append(result)
return result
+10 -11
View File
@@ -4,15 +4,13 @@ resource instead of per-segment files + merge."""
from __future__ import annotations
from typing import Optional
from unshackle.core.manifests import DASH
URL = "https://cdn/media.mp4"
def collapsible(segments, init_len: Optional[int], is_subtitle: bool = False) -> bool:
return DASH.collapsible_single_url(is_subtitle, segments, init_len)
def collapsible(segments, init_len: int, is_subtitle: bool = False) -> bool:
return DASH.collapsible_single_url(is_subtitle, segments, init_len) is not None
def test_segmentbase_single_range_eligible():
@@ -29,10 +27,6 @@ def test_subtitle_never_eligible():
assert collapsible([(URL, "863-999999")], init_len=863, is_subtitle=True) is False
def test_missing_init_len_ineligible():
assert collapsible([(URL, "863-999999")], init_len=None) is False
def test_missing_range_ineligible():
assert collapsible([(URL, None)], init_len=863) is False
assert collapsible([(URL, "863-1999"), (URL, None)], init_len=863) is False
@@ -43,9 +37,14 @@ def test_mixed_urls_ineligible():
assert collapsible(segments, init_len=863) is False
def test_first_segment_gap_after_init_ineligible():
# first media byte does not immediately follow the init prefix
assert collapsible([(URL, "900-1999")], init_len=863) is False
def test_first_segment_gap_after_init_left_to_prefix_check():
# the gap may be the sidx; single_url_prefix_matches decides from the bytes
segments = [(URL, "900-1999"), (URL, "2000-2999")]
assert DASH.collapsible_single_url(False, segments, 863) == (900, 2999)
def test_first_segment_inside_init_ineligible():
assert collapsible([(URL, "800-1999")], init_len=863) is False
def test_gap_between_segments_ineligible():
+85 -44
View File
@@ -18,6 +18,7 @@ localhost server, asserting that:
from __future__ import annotations
import shutil
import struct
import threading
from functools import partial
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@@ -99,48 +100,6 @@ def base(srv) -> str:
return f"http://127.0.0.1:{srv.server_address[1]}/"
def collapse_mpd(srv) -> str:
# SegmentList on one media.mp4: init is bytes [0, INIT_LEN), media ranges cover the rest
# contiguously -> collapsible_single_url returns the URL and download_track collapses.
return f"""<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
<BaseURL>{base(srv)}</BaseURL>
<Period id="0">
<AdaptationSet contentType="video" mimeType="video/mp4" lang="en">
<Representation id="v0" codecs="avc1.640028" bandwidth="1000000" width="1920" height="1080">
<BaseURL>media.mp4</BaseURL>
<SegmentList timescale="1">
<Initialization range="0-{INIT_LEN - 1}"/>
<SegmentURL mediaRange="{INIT_LEN}-5119"/>
<SegmentURL mediaRange="5120-{len(PARENT) - 1}"/>
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>"""
def clipped_mpd(srv) -> str:
# same parent, but the ranges stop short of its end -> the collapsed file must be trimmed
# to the last indexed byte, not left holding the parent's trailing bytes.
return f"""<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
<BaseURL>{base(srv)}</BaseURL>
<Period id="0">
<AdaptationSet contentType="video" mimeType="video/mp4" lang="en">
<Representation id="v0" codecs="avc1.640028" bandwidth="1000000" width="1920" height="1080">
<BaseURL>media.mp4</BaseURL>
<SegmentList timescale="1">
<Initialization range="0-{INIT_LEN - 1}"/>
<SegmentURL mediaRange="{INIT_LEN}-5119"/>
<SegmentURL mediaRange="5120-{CLIP_END}"/>
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>"""
def mixed_mpd(srv) -> str:
# Two SegmentURLs on distinct paths -> mixed URLs -> predicate False -> segmented path.
return f"""<?xml version="1.0"?>
@@ -160,6 +119,33 @@ def mixed_mpd(srv) -> str:
</MPD>"""
def sidx_box(sizes: list[int]) -> bytes:
# version 0 sidx: one MEDIA reference per size, each starting with a SAP
refs = b"".join(struct.pack(">III", size, 1, 0x90000000) for size in sizes)
body = struct.pack(">IIIIIHH", 0, 1, 1, 0, 0, 0, len(sizes)) + refs
return struct.pack(">I", 8 + len(body)) + b"sidx" + body
def ranged_mpd(srv, ranges: list[tuple[int, int]], init: str = f'range="0-{INIT_LEN - 1}"') -> str:
# SegmentList of byte ranges on one media.mp4, with the given Initialization attributes
segment_urls = "\n".join(f' <SegmentURL mediaRange="{s}-{e}"/>' for s, e in ranges)
return f"""<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
<BaseURL>{base(srv)}</BaseURL>
<Period id="0">
<AdaptationSet contentType="video" mimeType="video/mp4" lang="en">
<Representation id="v0" codecs="avc1.640028" bandwidth="1000000" width="1920" height="1080">
<BaseURL>media.mp4</BaseURL>
<SegmentList timescale="1">
<Initialization {init}/>
{segment_urls}
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>"""
def make_track(mpd: str, srv):
return DASH.from_text(mpd, url=f"{base(srv)}manifest.mpd").to_tracks(language="en").videos[0]
@@ -174,7 +160,7 @@ def make_ctx(tmp_path, name="out.mp4"):
def test_collapse_downloads_parent_whole_in_one_direct_request(server, tmp_path):
server.routes["/media.mp4"] = PARENT
track = make_track(collapse_mpd(server), server)
track = make_track(ranged_mpd(server, [(INIT_LEN, 5119), (5120, len(PARENT) - 1)]), server)
ctx = make_ctx(tmp_path, "collapsed.mp4")
DASH.download_track(track, ctx)
@@ -194,7 +180,8 @@ def test_collapse_downloads_parent_whole_in_one_direct_request(server, tmp_path)
def test_collapse_trims_parent_bytes_past_the_last_segment(server, tmp_path):
server.routes["/media.mp4"] = PARENT
track = make_track(clipped_mpd(server), server)
# the ranges stop short of the parent's end, so the collapsed file must be trimmed
track = make_track(ranged_mpd(server, [(INIT_LEN, 5119), (5120, CLIP_END)]), server)
ctx = make_ctx(tmp_path, "clipped.mp4")
DASH.download_track(track, ctx)
@@ -203,6 +190,60 @@ def test_collapse_trims_parent_bytes_past_the_last_segment(server, tmp_path):
assert ctx.save_path.read_bytes() == PARENT[: CLIP_END + 1]
def test_collapse_keeps_the_sidx_between_init_and_first_segment(server, tmp_path):
media = [bytes([0xC0]) * 3000, bytes([0xD1]) * 2000]
head = PARENT[:INIT_LEN] + sidx_box([len(m) for m in media])
parent = head + b"".join(media)
server.routes["/media.mp4"] = parent
ranges = [(len(head), len(head) + 2999), (len(head) + 3000, len(parent) - 1)]
ctx = make_ctx(tmp_path, "sidx.mp4")
DASH.download_track(make_track(ranged_mpd(server, ranges), server), ctx)
assert ctx.save_path.read_bytes() == parent
assert [path for path, rng in server.requests if rng is None] == ["/media.mp4"]
def test_leading_gap_that_is_not_a_sidx_takes_segmented_path(server, tmp_path):
# a filtered period cut from the same file leaves a gap of media, not an index
gap = struct.pack(">I", 1000) + b"moof" + bytes(992)
media = bytes([0xC0]) * 3000
server.routes["/media.mp4"] = PARENT[:INIT_LEN] + gap + media
start = INIT_LEN + len(gap)
ctx = make_ctx(tmp_path, "gap.mp4")
DASH.download_track(make_track(ranged_mpd(server, [(start, start + 2999)]), server), ctx)
assert ctx.save_path.read_bytes() == PARENT[:INIT_LEN] + media
assert not [path for path, rng in server.requests if rng is None]
def test_sidx_indexing_more_than_the_listed_segments_takes_segmented_path(server, tmp_path):
# the file carries a post-roll after the listed segments, and the sidx indexes it too
media, post_roll = bytes([0xC0]) * 3000, bytes([0xEE]) * 500
head = PARENT[:INIT_LEN] + sidx_box([len(media), len(post_roll)])
server.routes["/media.mp4"] = head + media + post_roll
ctx = make_ctx(tmp_path, "post-roll.mp4")
DASH.download_track(make_track(ranged_mpd(server, [(len(head), len(head) + 2999)]), server), ctx)
assert ctx.save_path.read_bytes() == PARENT[:INIT_LEN] + media
def test_init_from_another_file_takes_segmented_path(server, tmp_path):
# the media ranges start where an init of the same length would end, but the init is elsewhere
init = bytes([0x11]) * INIT_LEN
server.routes["/init.bin"] = init
server.routes["/media.mp4"] = PARENT
ranges = [(INIT_LEN, 5119), (5120, len(PARENT) - 1)]
ctx = make_ctx(tmp_path, "init-elsewhere.mp4")
DASH.download_track(make_track(ranged_mpd(server, ranges, init='sourceURL="init.bin"'), server), ctx)
assert ctx.save_path.read_bytes() == init + PARENT[INIT_LEN:]
assert not [path for path, rng in server.requests if rng is None and path == "/media.mp4"]
def test_non_collapse_takes_segmented_merge_path(server, tmp_path):
server.routes["/init.bin"] = INIT2
server.routes["/part0.bin"] = PART0
+55 -26
View File
@@ -40,6 +40,7 @@ from unshackle.core.tracks.track import (
)
from unshackle.core.utilities import is_close_match, log_event, try_ensure_utf8
from unshackle.core.utils import run_proxy
from unshackle.core.utils.bitrate import ranged_get
from unshackle.core.utils.redact import safe_display_url
from unshackle.core.utils.xml import load_xml
@@ -558,14 +559,17 @@ class DASH:
downloader = track.downloader
# When every segment is a byte range of one parent resource and init is its [0, len)
# prefix, the whole resource is itself a valid MP4, so one direct download to save_path
# replaces the per-segment files, init write and merge pass.
collapse_single_url = not config.sample and DASH.collapsible_single_url(
isinstance(track, Subtitle),
segments,
len(init_data) if init_data is not None else None,
# A parent resource that is itself a valid MP4 downloads in one pass to save_path,
# replacing the per-segment files, init write and merge pass.
collapse_span = (
DASH.collapsible_single_url(isinstance(track, Subtitle), segments, len(init_data))
if init_data is not None and not config.sample
else None
)
if collapse_span and init_data:
if not DASH.single_url_prefix_matches(session, segments[0][0], collapse_span, init_data):
collapse_span = None
collapse_single_url = collapse_span is not None
use_segment_decrypt = not collapse_single_url and bool(init_data) and can_use(drm, config.decryption)
# subtitle segments are tiny, the post-download merge costs nothing there
@@ -674,9 +678,9 @@ class DASH:
save_path.unlink(missing_ok=True)
raise
if collapse_single_url:
if collapse_span:
with open(save_path, "r+b") as collapsed:
collapsed.truncate(int(segments[-1][1].split("-")[1]) + 1)
collapsed.truncate(collapse_span[1] + 1)
elif merger:
for control_file in save_dir.glob("*.!dev"):
control_file.unlink(missing_ok=True)
@@ -805,42 +809,67 @@ class DASH:
def collapsible_single_url(
is_subtitle: bool,
segments: list[tuple[str, Optional[str]]],
init_len: Optional[int],
) -> bool:
init_len: int,
) -> Optional[tuple[int, int]]:
"""
Whether every segment is a byte range of one parent resource whose [0, init_len)
prefix is the init segment, so unshackle can download the whole resource in one pass.
Get the first and last byte of the segments when every segment is a byte range of one
parent resource that follows an init of init_len bytes, so unshackle can download the
whole resource in one pass.
Returns False for subtitle tracks, an unknown init length, mixed URLs, malformed
ranges, and anything but one gapless run in document order starting at the init
prefix. A gap would pull bytes the segmented merge path excludes (a period the
manifest filters out) into the collapsed file, so it fails closed.
Returns None for subtitle tracks, mixed URLs, malformed ranges, and anything but one
gapless run in document order. A gap between segments would pull bytes the segmented
merge path excludes (a period the manifest filters out) into the collapsed file, so it
fails closed. A gap before the first segment passes here, and single_url_prefix_matches
must then prove that it is the index.
"""
if is_subtitle or init_len is None or not segments:
return False
if is_subtitle or not segments:
return None
first_url = segments[0][0]
ranges: list[tuple[int, int]] = []
for url, bytes_range in segments:
if url != first_url or not bytes_range:
return False
return None
parts = bytes_range.split("-")
if len(parts) != 2 or not parts[0] or not parts[1]:
return False
return None
try:
start, end = int(parts[0]), int(parts[1])
except ValueError:
return False
return None
if end < start:
return False
return None
ranges.append((start, end))
prev_end = init_len - 1
if ranges[0][0] < init_len:
return None
prev_end = ranges[0][0] - 1
for start, end in ranges:
if start != prev_end + 1:
return False
return None
prev_end = end
return True
return ranges[0][0], prev_end
@staticmethod
def single_url_prefix_matches(
session: Union[Session, RnetSession],
url: str,
span: tuple[int, int],
init_data: bytes,
) -> bool:
"""
Whether the resource at url starts with init_data and, when bytes sit between the init
and the first byte of span, whether they are one sidx that indexes exactly span.
Fetches the resource up to the start of span.
"""
first_start, last_end = span
head = ranged_get(url, session, f"bytes=0-{first_start - 1}")
if not head or len(head) != first_start or not head.startswith(init_data):
return False
if first_start == len(init_data):
return True
_, subsegments = parse_sidx(head[len(init_data) :], len(init_data))
return bool(subsegments) and subsegments[0][1] == first_start and subsegments[-1][2] == last_end
@staticmethod
def is_content_period(period: Element, filtered_period_ids: list[str]) -> bool: