2018-06-21 20:56:47 +02:00
|
|
|
import unittest
|
2018-05-30 21:30:38 +02:00
|
|
|
|
|
|
|
from streamlink import PluginError
|
|
|
|
from streamlink.stream import *
|
2018-06-21 20:56:47 +02:00
|
|
|
from streamlink.stream.dash import DASHStreamWorker
|
2018-08-06 12:18:26 +02:00
|
|
|
from streamlink.stream.dash_manifest import MPD
|
2018-06-21 20:56:47 +02:00
|
|
|
from tests.mock import MagicMock, patch, ANY, Mock, call
|
2018-08-06 12:18:26 +02:00
|
|
|
from tests.resources import xml
|
2018-05-30 21:30:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
class TestDASHStream(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.session = MagicMock()
|
|
|
|
self.test_url = "http://test.bar/foo.mpd"
|
|
|
|
self.session.http.get.return_value = Mock(url=self.test_url)
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_video_only(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080)
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p", "1080p"])
|
|
|
|
)
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_only(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
2018-07-14 19:46:13 +02:00
|
|
|
Mock(id=1, mimeType="audio/mp4", bandwidth=128.0, lang='en'),
|
|
|
|
Mock(id=2, mimeType="audio/mp4", bandwidth=256.0, lang='en')
|
2018-05-30 21:30:38 +02:00
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["a128k", "a256k"])
|
|
|
|
)
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_single(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080),
|
2018-07-14 19:46:13 +02:00
|
|
|
Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en')
|
2018-05-30 21:30:38 +02:00
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p", "1080p"])
|
|
|
|
)
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_multi(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080),
|
2018-07-14 19:46:13 +02:00
|
|
|
Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en'),
|
|
|
|
Mock(id=4, mimeType="audio/aac", bandwidth=256.0, lang='en')
|
2018-05-30 21:30:38 +02:00
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p+a128k", "1080p+a128k", "720p+a256k", "1080p+a256k"])
|
|
|
|
)
|
|
|
|
|
2018-07-14 19:46:13 +02:00
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_multi_lang(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080),
|
|
|
|
Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en'),
|
|
|
|
Mock(id=4, mimeType="audio/aac", bandwidth=128.0, lang='es')
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p", "1080p"])
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(streams["720p"].audio_representation.lang, "en")
|
|
|
|
self.assertEqual(streams["1080p"].audio_representation.lang, "en")
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_multi_lang_alpha3(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080),
|
|
|
|
Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='eng'),
|
|
|
|
Mock(id=4, mimeType="audio/aac", bandwidth=128.0, lang='spa')
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p", "1080p"])
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(streams["720p"].audio_representation.lang, "eng")
|
|
|
|
self.assertEqual(streams["1080p"].audio_representation.lang, "eng")
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_invalid_lang(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080),
|
|
|
|
Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en_no_voice'),
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p", "1080p"])
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(streams["720p"].audio_representation.lang, "en_no_voice")
|
|
|
|
self.assertEqual(streams["1080p"].audio_representation.lang, "en_no_voice")
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_audio_multi_lang_locale(self, mpdClass):
|
|
|
|
self.session.localization.language.alpha2 = "es"
|
|
|
|
self.session.localization.explicit = True
|
|
|
|
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
Mock(id=1, mimeType="video/mp4", height=720),
|
|
|
|
Mock(id=2, mimeType="video/mp4", height=1080),
|
|
|
|
Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en'),
|
|
|
|
Mock(id=4, mimeType="audio/aac", bandwidth=128.0, lang='es')
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(
|
|
|
|
sorted(list(streams.keys())),
|
|
|
|
sorted(["720p", "1080p"])
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(streams["720p"].audio_representation.lang, "es")
|
|
|
|
self.assertEqual(streams["1080p"].audio_representation.lang, "es")
|
|
|
|
|
2018-05-30 21:30:38 +02:00
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_parse_manifest_drm(self, mpdClass):
|
|
|
|
mpd = mpdClass.return_value = Mock(periods=[Mock(adaptationSets=[Mock(contentProtection="DRM")])])
|
|
|
|
|
|
|
|
self.assertRaises(PluginError,
|
|
|
|
DASHStream.parse_manifest,
|
|
|
|
self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.DASHStreamReader')
|
|
|
|
@patch('streamlink.stream.dash.FFMPEGMuxer')
|
|
|
|
def test_stream_open_video_only(self, muxer, reader):
|
2018-06-26 00:25:10 +02:00
|
|
|
stream = DASHStream(self.session, Mock(), Mock(id=1, mimeType="video/mp4"))
|
2018-05-30 21:30:38 +02:00
|
|
|
open_reader = reader.return_value = Mock()
|
|
|
|
|
|
|
|
stream.open()
|
|
|
|
|
2018-06-26 00:25:10 +02:00
|
|
|
reader.assert_called_with(stream, 1, "video/mp4")
|
2018-05-30 21:30:38 +02:00
|
|
|
open_reader.open.assert_called_with()
|
|
|
|
muxer.assert_not_called()
|
|
|
|
|
|
|
|
@patch('streamlink.stream.dash.DASHStreamReader')
|
|
|
|
@patch('streamlink.stream.dash.FFMPEGMuxer')
|
|
|
|
def test_stream_open_video_audio(self, muxer, reader):
|
2018-07-14 19:46:13 +02:00
|
|
|
stream = DASHStream(self.session, Mock(), Mock(id=1, mimeType="video/mp4"), Mock(id=2, mimeType="audio/mp3", lang='en'))
|
2018-05-30 21:30:38 +02:00
|
|
|
open_reader = reader.return_value = Mock()
|
|
|
|
|
|
|
|
stream.open()
|
|
|
|
|
2018-06-26 00:25:10 +02:00
|
|
|
self.assertSequenceEqual(reader.mock_calls, [call(stream, 1, "video/mp4"),
|
2018-05-30 21:30:38 +02:00
|
|
|
call().open(),
|
2018-06-26 00:25:10 +02:00
|
|
|
call(stream, 2, "audio/mp3"),
|
2018-05-30 21:30:38 +02:00
|
|
|
call().open()])
|
|
|
|
self.assertSequenceEqual(muxer.mock_calls, [call(self.session, open_reader, open_reader, copyts=True),
|
|
|
|
call().open()])
|
|
|
|
|
2018-08-06 12:18:26 +02:00
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_segments_number_time(self, mpdClass):
|
|
|
|
with xml("dash/test_9.mpd") as mpd_xml:
|
|
|
|
mpdClass.return_value = MPD(mpd_xml, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
streams = DASHStream.parse_manifest(self.session, self.test_url)
|
|
|
|
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
|
|
|
|
|
|
|
|
self.assertSequenceEqual(list(streams.keys()), ['2500k'])
|
|
|
|
|
2018-05-30 21:30:38 +02:00
|
|
|
|
|
|
|
class TestDASHStreamWorker(unittest.TestCase):
|
2018-06-01 21:58:11 +02:00
|
|
|
@patch("streamlink.stream.dash_manifest.time.sleep")
|
2018-05-30 21:30:38 +02:00
|
|
|
@patch('streamlink.stream.dash.MPD')
|
|
|
|
def test_dynamic_reload(self, mpdClass, sleep):
|
|
|
|
reader = MagicMock()
|
|
|
|
worker = DASHStreamWorker(reader)
|
|
|
|
reader.representation_id = 1
|
2018-06-26 00:25:10 +02:00
|
|
|
reader.mime_type = "video/mp4"
|
2018-05-30 21:30:38 +02:00
|
|
|
|
|
|
|
representation = Mock(id=1, mimeType="video/mp4", height=720)
|
|
|
|
segments = [Mock(url="init_segment"), Mock(url="first_segment"), Mock(url="second_segment")]
|
|
|
|
representation.segments.return_value = [segments[0]]
|
|
|
|
mpdClass.return_value = worker.mpd = Mock(dynamic=True,
|
|
|
|
publishTime=1,
|
|
|
|
periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
representation
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
worker.mpd.type = "dynamic"
|
|
|
|
worker.mpd.minimumUpdatePeriod.total_seconds.return_value = 0
|
|
|
|
worker.mpd.periods[0].duration.total_seconds.return_value = 0
|
|
|
|
|
|
|
|
segment_iter = worker.iter_segments()
|
|
|
|
|
|
|
|
representation.segments.return_value = segments[:1]
|
|
|
|
self.assertEqual(next(segment_iter), segments[0])
|
|
|
|
representation.segments.assert_called_with(init=True)
|
|
|
|
|
|
|
|
representation.segments.return_value = segments[1:]
|
|
|
|
self.assertSequenceEqual([next(segment_iter), next(segment_iter)], segments[1:])
|
|
|
|
representation.segments.assert_called_with(init=False)
|
|
|
|
|
2018-06-01 21:58:11 +02:00
|
|
|
@patch("streamlink.stream.dash_manifest.time.sleep")
|
|
|
|
def test_static(self, sleep):
|
2018-05-30 21:30:38 +02:00
|
|
|
reader = MagicMock()
|
|
|
|
worker = DASHStreamWorker(reader)
|
|
|
|
reader.representation_id = 1
|
2018-06-26 00:25:10 +02:00
|
|
|
reader.mime_type = "video/mp4"
|
2018-05-30 21:30:38 +02:00
|
|
|
|
|
|
|
representation = Mock(id=1, mimeType="video/mp4", height=720)
|
|
|
|
segments = [Mock(url="init_segment"), Mock(url="first_segment"), Mock(url="second_segment")]
|
|
|
|
representation.segments.return_value = [segments[0]]
|
|
|
|
worker.mpd = Mock(dynamic=False,
|
|
|
|
publishTime=1,
|
|
|
|
periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
representation
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
worker.mpd.type = "static"
|
|
|
|
worker.mpd.minimumUpdatePeriod.total_seconds.return_value = 0
|
|
|
|
worker.mpd.periods[0].duration.total_seconds.return_value = 0
|
|
|
|
|
|
|
|
representation.segments.return_value = segments
|
|
|
|
self.assertSequenceEqual(list(worker.iter_segments()), segments)
|
|
|
|
representation.segments.assert_called_with(init=True)
|
|
|
|
|
2018-06-26 00:25:10 +02:00
|
|
|
@patch("streamlink.stream.dash_manifest.time.sleep")
|
|
|
|
def test_duplicate_rep_id(self, sleep):
|
|
|
|
representation_vid = Mock(id=1, mimeType="video/mp4", height=720)
|
2018-07-14 19:46:13 +02:00
|
|
|
representation_aud = Mock(id=1, mimeType="audio/aac", lang='en')
|
2018-06-26 00:25:10 +02:00
|
|
|
|
|
|
|
mpd = Mock(dynamic=False,
|
|
|
|
publishTime=1,
|
|
|
|
periods=[
|
|
|
|
Mock(adaptationSets=[
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
representation_vid
|
|
|
|
]),
|
|
|
|
Mock(contentProtection=None,
|
|
|
|
representations=[
|
|
|
|
representation_aud
|
|
|
|
])
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
self.assertEqual(representation_vid, DASHStreamWorker.get_representation(mpd, 1, "video/mp4"))
|
|
|
|
self.assertEqual(representation_aud, DASHStreamWorker.get_representation(mpd, 1, "audio/aac"))
|
|
|
|
|
2018-05-30 21:30:38 +02:00
|
|
|
|
2018-08-06 12:18:26 +02:00
|
|
|
|
|
|
|
|
2018-05-30 21:30:38 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|