diff --git a/lib/svtplay_dl/tests/m3u8-playlists/audio-uri-bottom.m3u8 b/lib/svtplay_dl/tests/m3u8-playlists/audio-uri-bottom.m3u8 new file mode 100644 index 0000000..c69d51c --- /dev/null +++ b/lib/svtplay_dl/tests/m3u8-playlists/audio-uri-bottom.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-STREAM-INF:BANDWIDTH=902653,AVERAGE-BANDWIDTH=606754,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=25.0,AUDIO="stereo",CLOSED-CAPTIONS=NONE +cmaf-video1/cmaf-video1.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=551018,AVERAGE-BANDWIDTH=380051,CODECS="avc1.42c01f,mp4a.40.2",RESOLUTION=416x234,FRAME-RATE=25.0,AUDIO="stereo",CLOSED-CAPTIONS=NONE +cmaf-video0/cmaf-video0.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1481074,AVERAGE-BANDWIDTH=1004301,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=960x540,FRAME-RATE=25.0,AUDIO="stereo",CLOSED-CAPTIONS=NONE +cmaf-video2/cmaf-video2.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2183399,AVERAGE-BANDWIDTH=1442884,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=25.0,AUDIO="stereo",CLOSED-CAPTIONS=NONE +cmaf-video3/cmaf-video3.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=3958939,AVERAGE-BANDWIDTH=2639085,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=25.0,AUDIO="stereo",CLOSED-CAPTIONS=NONE +cmaf-video4/cmaf-video4.m3u8 +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video2/cmaf-video2-iframe.m3u8",BANDWIDTH=272228,AVERAGE-BANDWIDTH=122290,CODECS="avc1.4d401f",RESOLUTION=960x540 +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video3/cmaf-video3-iframe.m3u8",BANDWIDTH=392503,AVERAGE-BANDWIDTH=159327,CODECS="avc1.4d401f",RESOLUTION=1280x720 +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video0/cmaf-video0-iframe.m3u8",BANDWIDTH=86490,AVERAGE-BANDWIDTH=35782,CODECS="avc1.42c01f",RESOLUTION=416x234 +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video1/cmaf-video1-iframe.m3u8",BANDWIDTH=170992,AVERAGE-BANDWIDTH=77337,CODECS="avc1.4d401f",RESOLUTION=640x360 +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video4/cmaf-video4-iframe.m3u8",BANDWIDTH=679486,AVERAGE-BANDWIDTH=217635,CODECS="avc1.640029",RESOLUTION=1920x1080 +#EXT-X-MEDIA:TYPE=AUDIO,URI="cmaf-audio0/cmaf-audio0.m3u8",GROUP-ID="stereo",LANGUAGE="sv",NAME="Svenska",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2" diff --git a/lib/svtplay_dl/tests/m3u8-playlists/audio-uri-top.m3u8 b/lib/svtplay_dl/tests/m3u8-playlists/audio-uri-top.m3u8 new file mode 100644 index 0000000..07385f9 --- /dev/null +++ b/lib/svtplay_dl/tests/m3u8-playlists/audio-uri-top.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA:TYPE=AUDIO,URI="cmaf-audio-stereo-1/cmaf-audio-stereo-1.m3u8",GROUP-ID="stereo",LANGUAGE="sv",NAME="Svenska",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="cmaf-audio-surround-0/cmaf-audio-surround-0.m3u8",GROUP-ID="surround",LANGUAGE="sv",NAME="Svenska",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="6" +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="text/text-0.m3u8",GROUP-ID="text",LANGUAGE="sv",NAME="Svenska",DEFAULT=NO,AUTOSELECT=YES,CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound" +#EXT-X-STREAM-INF:BANDWIDTH=2699177,AVERAGE-BANDWIDTH=1759008,CODECS="avc1.4d401f,ac-3",RESOLUTION=960x540,FRAME-RATE=25.0,AUDIO="surround",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-2/cmaf-video-avc-2.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1005400,AVERAGE-BANDWIDTH=773881,CODECS="avc1.42c01f,ac-3",RESOLUTION=416x234,FRAME-RATE=25.0,AUDIO="surround",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-0/cmaf-video-avc-0.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1919404,AVERAGE-BANDWIDTH=1255439,CODECS="avc1.4d401f,ac-3",RESOLUTION=640x360,FRAME-RATE=25.0,AUDIO="surround",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-1/cmaf-video-avc-1.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4053150,AVERAGE-BANDWIDTH=2513734,CODECS="avc1.4d401f,ac-3",RESOLUTION=1280x720,FRAME-RATE=25.0,AUDIO="surround",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-3/cmaf-video-avc-3.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=5870867,AVERAGE-BANDWIDTH=3549836,CODECS="avc1.640029,ac-3",RESOLUTION=1920x1080,FRAME-RATE=25.0,AUDIO="surround",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-4/cmaf-video-avc-4.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=753024,AVERAGE-BANDWIDTH=519455,CODECS="avc1.42c01f,mp4a.40.2",RESOLUTION=416x234,FRAME-RATE=25.0,AUDIO="stereo",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-0/cmaf-video-avc-0.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1667028,AVERAGE-BANDWIDTH=1001013,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=25.0,AUDIO="stereo",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-1/cmaf-video-avc-1.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2446801,AVERAGE-BANDWIDTH=1504582,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=960x540,FRAME-RATE=25.0,AUDIO="stereo",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-2/cmaf-video-avc-2.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=3800774,AVERAGE-BANDWIDTH=2259308,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=25.0,AUDIO="stereo",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-3/cmaf-video-avc-3.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=5618491,AVERAGE-BANDWIDTH=3295410,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=25.0,AUDIO="stereo",SUBTITLES="text",VIDEO-RANGE=SDR +cmaf-video-avc-4/cmaf-video-avc-4.m3u8 +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video-avc-0/cmaf-video-avc-0-iframe.m3u8",BANDWIDTH=47743,AVERAGE-BANDWIDTH=26960,CODECS="avc1.42c01f",RESOLUTION=416x234,VIDEO-RANGE=SDR +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video-avc-1/cmaf-video-avc-1-iframe.m3u8",BANDWIDTH=137468,AVERAGE-BANDWIDTH=73586,CODECS="avc1.4d401f",RESOLUTION=640x360,VIDEO-RANGE=SDR +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video-avc-2/cmaf-video-avc-2-iframe.m3u8",BANDWIDTH=221189,AVERAGE-BANDWIDTH=119197,CODECS="avc1.4d401f",RESOLUTION=960x540,VIDEO-RANGE=SDR +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video-avc-3/cmaf-video-avc-3-iframe.m3u8",BANDWIDTH=332252,AVERAGE-BANDWIDTH=171843,CODECS="avc1.4d401f",RESOLUTION=1280x720,VIDEO-RANGE=SDR +#EXT-X-I-FRAME-STREAM-INF:URI="cmaf-video-avc-4/cmaf-video-avc-4-iframe.m3u8",BANDWIDTH=476954,AVERAGE-BANDWIDTH=232325,CODECS="avc1.640029",RESOLUTION=1920x1080,VIDEO-RANGE=SDR diff --git a/lib/svtplay_dl/tests/m3u8-playlists/no-audio-uri.m3u8 b/lib/svtplay_dl/tests/m3u8-playlists/no-audio-uri.m3u8 new file mode 100644 index 0000000..8408c6d --- /dev/null +++ b/lib/svtplay_dl/tests/m3u8-playlists/no-audio-uri.m3u8 @@ -0,0 +1,33 @@ +#EXTM3U +#EXT-X-VERSION:4 +## Created with Unified Streaming Platform(version=1.9.5) + +# AUDIO groups +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-aacl-93",NAME="English",LANGUAGE="en",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2" + +# SUBTITLES groups +#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="textstream",NAME="Swedish",LANGUAGE="sv",AUTOSELECT=YES,DEFAULT=YES,URI="pid200028961_3910568(3910568_ISMUSP)-textstream_swe=1000.m3u8" + +# variants +#EXT-X-STREAM-INF:BANDWIDTH=206000,CODECS="mp4a.40.2,avc1.42001E",RESOLUTION=128x72,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=99000.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=369000,CODECS="mp4a.40.2,avc1.42001E",RESOLUTION=384x216,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=253000.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=847000,CODECS="mp4a.40.2,avc1.42001E",RESOLUTION=640x360,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=704000.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1589000,CODECS="mp4a.40.2,avc1.4D001E",RESOLUTION=768x432,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=1404000.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2581000,CODECS="mp4a.40.2,avc1.4D001F",RESOLUTION=1024x576,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=2340000.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=3642000,CODECS="mp4a.40.2,avc1.4D001F",RESOLUTION=1280x720,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=3341000.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4702000,CODECS="mp4a.40.2,avc1.4D001F",RESOLUTION=1280x720,FRAME-RATE=25,AUDIO="audio-aacl-93",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE +pid200028961_3910568(3910568_ISMUSP)-audio_eng=93468-video_eng=4341000.m3u8 + +# keyframes +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=14000,CODECS="avc1.42001E",RESOLUTION=128x72,URI="keyframes/pid200028961_3910568(3910568_ISMUSP)-video_eng=99000.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=34000,CODECS="avc1.42001E",RESOLUTION=384x216,URI="keyframes/pid200028961_3910568(3910568_ISMUSP)-video_eng=253000.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=94000,CODECS="avc1.42001E",RESOLUTION=640x360,URI="keyframes/pid200028961_3910568(3910568_ISMUSP)-video_eng=704000.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=187000,CODECS="avc1.4D001E",RESOLUTION=768x432,URI="keyframes/pid200028961_3910568(3910568_ISMUSP)-video_eng=1404000.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=311000,CODECS="avc1.4D001F",RESOLUTION=1024x576,URI="keyframes/pid200028961_3910568(3910568_ISMUSP)-video_eng=2340000.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=443000,CODECS="avc1.4D001F",RESOLUTION=1280x720,URI="keyframes/pid200028961_3910568(3910568_ISMUSP)-video_eng=3341000.m3u8" diff --git a/lib/svtplay_dl/tests/test_hls.py b/lib/svtplay_dl/tests/test_hls.py index df8e315..2a08c66 100644 --- a/lib/svtplay_dl/tests/test_hls.py +++ b/lib/svtplay_dl/tests/test_hls.py @@ -5,9 +5,25 @@ # pylint: disable-msg=C0103 # We're a test, we go where ever we want (within reason, of course): # pylint: disable-msg=protected-access +import os import unittest +import requests_mock +from svtplay_dl import fetcher +from svtplay_dl.fetcher.hls import _hlsparse from svtplay_dl.fetcher.hls import M3U8 +from svtplay_dl.utils.parser import setup_defaults + + +def parse(playlist): + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "m3u8-playlists", playlist)) as fd: + manifest = fd.read() + streams = {} + for i in list(_hlsparse(setup_defaults(), manifest, "http://localhost.com/", {})): + if isinstance(i, fetcher.VideoRetriever): + streams[i.bitrate] = i + return streams + # Example HLS playlist, source: # loosly inspired by @@ -31,6 +47,24 @@ something4/else.m3u8 something0/else.m3u8 """ +M3U_EXAMPLE2 = """#EXTM3U +#EXT-X-VERSION:4 +## Created with Unified Streaming Platform(version=1.9.5) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:60 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXTINF:60, no desc +pid200028961_3910568(3910568_ISMUSP)-textstream_swe=1000-1.webvtt +#EXTINF:60, no desc +pid200028961_3910568(3910568_ISMUSP)-textstream_swe=1000-2.webvtt +#EXTINF:60, no desc +pid200028961_3910568(3910568_ISMUSP)-textstream_swe=1000-3.webvtt +#EXTINF:60, no desc +pid200028961_3910568(3910568_ISMUSP)-textstream_swe=1000-4.webvtt +""" + class HlsTest(unittest.TestCase): def test_parse_m3u8(self): @@ -74,3 +108,25 @@ class HlsTest(unittest.TestCase): # More examples can be found on "https://developer.apple.com/streaming/examples/" ]: assert M3U8(test["playlist"]).master_playlist == test["expected"] + + +def test_noaudio_segment(): + with requests_mock.Mocker() as m: + m.get("http://localhost.com/pid200028961_3910568(3910568_ISMUSP)-textstream_swe=1000.m3u8", text=M3U_EXAMPLE2) + data = parse("no-audio-uri.m3u8") + assert data[3642].segments is False + assert data[3642].audio is None + + +def test_audio_top(): + with requests_mock.Mocker() as m: + m.get("http://localhost.com/text/text-0.m3u8", text=M3U_EXAMPLE2) + data = parse("audio-uri-top.m3u8") + assert data[3295].segments + assert data[3295].audio + + +def test_audio_bottom(): + data = parse("audio-uri-bottom.m3u8") + assert data[2639].segments + assert data[2639].audio diff --git a/requirements-dev.txt b/requirements-dev.txt index c99cca9..ff769fe 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,7 @@ tox nose mock +requests-mock wheel pre-commit pytest