mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
fix(sample): stop a malformed sidx from hanging the download
pymp4 loops forever on a sidx that counts more references than it holds. Manifest errors in download threads now raise instead of sys.exit.
This commit is contained in:
@@ -345,6 +345,12 @@ def test_parse_sidx_refuses_what_it_cannot_cut() -> None:
|
||||
assert parse_sidx(sidx_box(count=3)[:-4], 0) == (0, [])
|
||||
|
||||
|
||||
def test_parse_sidx_refuses_a_box_that_counts_more_references_than_it_holds() -> None:
|
||||
box = bytearray(sidx_box(count=3))
|
||||
box[30:32] = (5).to_bytes(2, "big")
|
||||
assert parse_sidx(bytes(box) + bytes(100), 0) == (0, [])
|
||||
|
||||
|
||||
def test_a_single_file_dash_track_is_cut_by_its_segment_index(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
box = sidx_box()
|
||||
requested: list[str] = []
|
||||
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
import math
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from concurrent.futures import Future
|
||||
from contextlib import ExitStack, closing
|
||||
from copy import deepcopy
|
||||
@@ -17,6 +16,7 @@ from urllib.parse import urljoin, urlparse
|
||||
from uuid import UUID
|
||||
from zlib import crc32
|
||||
|
||||
import click
|
||||
import requests
|
||||
from langcodes import Language, tag_is_valid
|
||||
from lxml.etree import Element, ElementTree, XMLSyntaxError
|
||||
@@ -497,14 +497,13 @@ class DASH:
|
||||
if config.sample and segments:
|
||||
break
|
||||
|
||||
# raise, never sys.exit: this runs in a download thread, where SystemExit escapes the failure handler
|
||||
if not segments:
|
||||
log.error("Could not find a way to get segments from this MPD manifest.")
|
||||
log.debug(track.url)
|
||||
sys.exit(1)
|
||||
raise click.ClickException("Could not find a way to get segments from this MPD manifest.")
|
||||
|
||||
if config.sample and not segment_durations and not isinstance(track, Subtitle):
|
||||
log.error("--sample cannot cut this track: the manifest gives it as one file with no segment index.")
|
||||
sys.exit(1)
|
||||
raise click.ClickException("--sample cannot cut this track: the server sent no segment index for it.")
|
||||
|
||||
# TODO: Should we floor/ceil/round, or is int() ok?
|
||||
track.data["dash"]["timescale"] = int(segment_timescale)
|
||||
|
||||
@@ -8,7 +8,6 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from contextlib import ExitStack, closing
|
||||
from itertools import accumulate
|
||||
@@ -18,6 +17,7 @@ from urllib.parse import urljoin, urlparse
|
||||
from uuid import UUID
|
||||
from zlib import crc32
|
||||
|
||||
import click
|
||||
import m3u8
|
||||
import requests
|
||||
from langcodes import Language, tag_is_valid
|
||||
@@ -644,14 +644,12 @@ class HLS:
|
||||
if not isinstance(response, (requests.Response, RnetResponse)):
|
||||
raise TypeError(f"Expected response to be a requests.Response or rnet.Response, not {type(response)}")
|
||||
if not response.ok:
|
||||
log.error(f"Failed to request the invariant M3U8 playlist: {response.status_code}")
|
||||
sys.exit(1)
|
||||
raise click.ClickException(f"Failed to request the invariant M3U8 playlist: {response.status_code}")
|
||||
|
||||
master = m3u8.loads(utf8_text(response), uri=track.url)
|
||||
|
||||
if not master.segments:
|
||||
log.error("Track's HLS playlist has no segments, expecting an invariant M3U8 playlist.")
|
||||
sys.exit(1)
|
||||
raise click.ClickException("Track's HLS playlist has no segments, expecting an invariant M3U8 playlist.")
|
||||
|
||||
unwanted_segments = [
|
||||
segment for segment in master.segments if callable(track.OnSegmentFilter) and track.OnSegmentFilter(segment)
|
||||
|
||||
@@ -360,11 +360,15 @@ def parse_sidx(data: bytes, offset: int) -> tuple[int, list[tuple[int, int, int]
|
||||
Returns the timescale and one (start time, first byte, last byte) for each subsegment. The list
|
||||
is empty when the box is not a ``sidx`` or when it points at more indexes and not at media.
|
||||
"""
|
||||
size, header = int.from_bytes(data[:4], "big"), 32 if data[8:9] == b"\x00" else 40
|
||||
# pymp4 loops forever on a box that counts more references than its size holds
|
||||
if data[4:8] != b"sidx" or size < header + 12 * int.from_bytes(data[header - 2 : header], "big"):
|
||||
return 0, []
|
||||
try:
|
||||
box = Box.parse(data)
|
||||
except ConstructError:
|
||||
return 0, []
|
||||
if box.type != b"sidx" or any(reference.reference_type != "MEDIA" for reference in box.references):
|
||||
if any(reference.reference_type != "MEDIA" for reference in box.references):
|
||||
return 0, []
|
||||
start, byte = box.earliest_presentation_time, offset + box.end + box.first_offset
|
||||
subsegments: list[tuple[int, int, int]] = []
|
||||
|
||||
Reference in New Issue
Block a user