"""One stream row per bitrate, and the variant selection behind it.
vidcore hands us a master playlist; we serve a single VARIANT (see _cap_variant's
comment for why: the master has no VOD/ENDLIST markers so Kodi calls it live and
refuses pause, and bare ffmpeg picks the highest bandwidth, which on this title
set means a 2160p stream the Pi cannot decode). That left exactly one quality.
These cover offering the user a choice of quality instead, without giving up
either property: every row is still a single variant playlist.
Run: .venv/bin/python3 -m pytest test_vidcore_variants.py -q
"""
import pytest
import addon
MASTER_URL = "https://cdn.example/vd/TOKEN/master.m3u8"
MASTER = """#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=15800000,RESOLUTION=3840x2160,CODECS="avc1.640033"
sd/52/index-s2160p-v1-a1.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6047207,RESOLUTION=1920x1080,CODECS="avc1.640028"
sd/52/index-s1080p-v1-a1.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3049124,RESOLUTION=1280x720,CODECS="avc1.4d401f"
sd/52/index-s720p-v1-a1.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1287760,RESOLUTION=852x480,CODECS="avc1.4d401e"
sd/52/index-s480p-v1-a1.m3u8
"""
MEDIA = """#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:6.0,
seg-1.ts
#EXT-X-ENDLIST
"""
# A master whose best is 720p, for the "nothing at or below the ask" case.
LOW_MASTER = """#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=3049124,RESOLUTION=1280x720
only-720.m3u8
"""
class _Resp:
def __init__(self, text):
self.text = text
def raise_for_status(self):
pass
@pytest.fixture(autouse=True)
def no_network(monkeypatch):
monkeypatch.setattr(addon, "_variant_cache", {})
monkeypatch.setattr(addon.requests, "get",
lambda *a, **k: _Resp(_no_network.body))
yield
class _NoNetwork:
body = MASTER
_no_network = _NoNetwork()
@pytest.fixture(autouse=True)
def reset_body():
_no_network.body = MASTER
yield
_no_network.body = MASTER
# --- picking a variant ------------------------------------------------------
def test_caps_to_the_best_at_or_below_the_ask():
assert addon._cap_variant(MASTER_URL, 1080).endswith("index-s1080p-v1-a1.m3u8")
assert addon._cap_variant(MASTER_URL, 720).endswith("index-s720p-v1-a1.m3u8")
assert addon._cap_variant(MASTER_URL, 480).endswith("index-s480p-v1-a1.m3u8")
def test_each_height_is_a_distinct_playable_url():
urls = {addon._cap_variant(MASTER_URL, h) for h in addon.VIDCORE_HEIGHTS}
assert len(urls) == len(addon.VIDCORE_HEIGHTS)
assert all(u.startswith("https://cdn.example/vd/TOKEN/") for u in urls)
def test_relative_uri_is_resolved_against_the_master():
assert addon._cap_variant(MASTER_URL, 720) == \
"https://cdn.example/vd/TOKEN/sd/52/index-s720p-v1-a1.m3u8"
def test_4k_is_never_selected():
"""The Pi's V4L2 decoder cannot decode it, so no row may reach it."""
for h in addon.VIDCORE_HEIGHTS:
assert "2160" not in addon._cap_variant(MASTER_URL, h)
def test_falls_back_to_the_lowest_variant_not_the_master():
"""Asking for 480p from a 720p-only master must still yield a VARIANT.
Returning the master here would hand Kodi a playlist with no ENDLIST, which
it treats as live and refuses to pause -- the exact bug _cap_variant exists
to prevent, reintroduced through the fallback path.
"""
_no_network.body = LOW_MASTER
assert addon._cap_variant(MASTER_URL, 480).endswith("only-720.m3u8")
# --- not making things worse ------------------------------------------------
def test_media_playlist_passes_through():
_no_network.body = MEDIA
assert addon._cap_variant(MASTER_URL, 1080) == MASTER_URL
def test_fetch_failure_serves_the_master(monkeypatch):
def boom(*a, **k):
raise RuntimeError("connection reset")
monkeypatch.setattr(addon.requests, "get", boom)
assert addon._cap_variant(MASTER_URL, 1080) == MASTER_URL
def test_a_failed_fetch_is_not_cached(monkeypatch):
calls = {"n": 0}
def boom(*a, **k):
calls["n"] += 1
raise RuntimeError("connection reset")
monkeypatch.setattr(addon.requests, "get", boom)
addon._cap_variant(MASTER_URL, 1080)
addon._cap_variant(MASTER_URL, 1080)
assert calls["n"] == 2
def test_the_master_is_parsed_once_per_title():
"""Three quality rows must not mean three fetches of the same playlist."""
calls = {"n": 0}
real = addon.requests.get
def counting(*a, **k):
calls["n"] += 1
return real(*a, **k)
addon.requests.get = counting
try:
for h in addon.VIDCORE_HEIGHTS:
addon._cap_variant(MASTER_URL, h)
finally:
addon.requests.get = real
assert calls["n"] == 1
# --- the rows themselves ----------------------------------------------------
def test_row_carries_the_height_in_url_and_label():
row = addon._vidcore_row("host:7220", "series", "tmdb:138502", 2, 7, height=720)
assert row.url == "http://host:7220/vidcore/series/tmdb:138502:2:7.m3u8?h=720"
assert "720p" in row.title_extra
def test_series_rows_keep_the_SxxExx_tag():
"""Jacktook drops TV results whose title has no SxxEyy tag."""
for h in addon.VIDCORE_HEIGHTS:
row = addon._vidcore_row("h:1", "series", "tmdb:1", 2, 7, height=h)
assert "S02E07" in row.title_extra
def test_highest_quality_is_offered_first():
assert list(addon.VIDCORE_HEIGHTS) == sorted(addon.VIDCORE_HEIGHTS, reverse=True)
assert addon.VIDCORE_HEIGHTS[0] == addon.VIDCORE_MAX_HEIGHT