diff --git a/lib/svtplay_dl/fetcher/hls.py b/lib/svtplay_dl/fetcher/hls.py index 5d6ea6a..2838c15 100644 --- a/lib/svtplay_dl/fetcher/hls.py +++ b/lib/svtplay_dl/fetcher/hls.py @@ -248,7 +248,7 @@ class HLS(VideoRetriever): duration = 0 max_duration = 0 for index, i in enumerate(m3u8.media_segment): - if "duration" in i["EXTINF"]: + if "EXTINF" in i and "duration" in i["EXTINF"]: duration = i["EXTINF"]["duration"] max_duration = max(max_duration, duration) total_duration += duration @@ -261,7 +261,10 @@ class HLS(VideoRetriever): eta.increment() progressbar(size_media, index + 1, "".join(["ETA: ", str(eta)])) - data = self.http.request("get", item, cookies=cookies) + headers = {} + if "EXT-X-BYTERANGE" in i: + headers["Range"] = f'bytes={i["EXT-X-BYTERANGE"]["o"]}-{i["EXT-X-BYTERANGE"]["o"] + i["EXT-X-BYTERANGE"]["n"] - 1}' + data = self.http.request("get", item, cookies=cookies, headers=headers) if data.status_code == 404: break data = data.content @@ -426,7 +429,19 @@ class M3U8: # 4.3.2.5. EXT-X-MAP elif tag == "EXT-X-MAP": info = _get_tuple_attribute(attr) - self.media_segment.insert(0, {"URI": info["URI"], "EXTINF": {"duration": 0}}) + if "BYTERANGE" in info: + if "@" in info["BYTERANGE"]: + n, o = info["BYTERANGE"].split("@", 1) + info["EXT-X-BYTERANGE"] = {} + info["EXT-X-BYTERANGE"]["n"], info["EXT-X-BYTERANGE"]["o"] = (int(n), int(o)) + else: + info["EXT-X-BYTERANGE"] = {} + info["EXT-X-BYTERANGE"]["n"] = int(attr) + info["EXT-X-BYTERANGE"]["o"] = 0 + if "BYTERANGE" not in info: + info["EXTINF"] = {} + info["EXTINF"]["duration"] = 0 + self.media_segment.insert(0, info) # 4.3.2.6. EXT-X-PROGRAM-DATE-TIME" elif tag == "EXT-X-PROGRAM-DATE-TIME": diff --git a/lib/svtplay_dl/tests/m3u8-playlists/ext-x-byterange.m3u8 b/lib/svtplay_dl/tests/m3u8-playlists/ext-x-byterange.m3u8 new file mode 100644 index 0000000..6dd5c58 --- /dev/null +++ b/lib/svtplay_dl/tests/m3u8-playlists/ext-x-byterange.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:10 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-MAP:URI="Frank---Kastaniegaarde_bfc1970adae844c180ae0c447e079c1e_5500_audio.mp4",BYTERANGE="748@0" +#EXTINF:10.005333, +#EXT-X-BYTERANGE:242398@2828 +Frank---Kastaniegaarde_bfc1970adae844c180ae0c447e079c1e_5500_audio.mp4 +#EXTINF:10.005333, +#EXT-X-BYTERANGE:242050@245226 +Frank---Kastaniegaarde_bfc1970adae844c180ae0c447e079c1e_5500_audio.mp4 +#EXTINF:10.005333, +#EXT-X-BYTERANGE:242104@487276 +Frank---Kastaniegaarde_bfc1970adae844c180ae0c447e079c1e_5500_audio.mp4 +#EXT-X-ENDLIST diff --git a/lib/svtplay_dl/tests/m3u8-playlists/x-map.m3u8 b/lib/svtplay_dl/tests/m3u8-playlists/x-map.m3u8 new file mode 100644 index 0000000..5b867e5 --- /dev/null +++ b/lib/svtplay_dl/tests/m3u8-playlists/x-map.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:17 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="video/init.mp4" +#EXTINF:11.378, +video/1.m4s +#EXTINF:10.677, +video/2.m4s +#EXTINF:10.944, +video/3.m4s diff --git a/lib/svtplay_dl/tests/test_hls.py b/lib/svtplay_dl/tests/test_hls.py index 2a08c66..a600cb8 100644 --- a/lib/svtplay_dl/tests/test_hls.py +++ b/lib/svtplay_dl/tests/test_hls.py @@ -25,6 +25,12 @@ def parse(playlist): return streams +def parse_m3u8(playlist): + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "m3u8-playlists", playlist)) as fd: + manifest = fd.read() + return M3U8(manifest) + + # Example HLS playlist, source: # loosly inspired by # https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8 @@ -130,3 +136,16 @@ def test_audio_bottom(): data = parse("audio-uri-bottom.m3u8") assert data[2639].segments assert data[2639].audio + + +def test_x_byterange(): + data = parse_m3u8("ext-x-byterange.m3u8") + assert data.media_segment[0]["EXT-X-BYTERANGE"]["n"] == 748 + assert data.media_segment[1]["EXT-X-BYTERANGE"]["n"] == 242398 + assert data.media_segment[1]["EXT-X-BYTERANGE"]["n"] + data.media_segment[1]["EXT-X-BYTERANGE"]["o"] - 1 == 245225 + + +def test_x_map(): + data = parse_m3u8("x-map.m3u8") + assert data.media_segment[0]["URI"] == "video/init.mp4" + assert data.media_segment[2]["URI"] == "video/2s.m4s"