diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 2db387b5e40..45a162c0756 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -1,6 +1,26 @@ Deprecations ============ +streamlink 2.27.4.0 +------------------- + +Deprecation of url_master in HLSStream +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``url_master`` parameter and attribute of the :py:class:`streamlink.stream.HLSStream` +and :py:class:`streamlink.stream.MuxedHLSStream` classes have been deprecated in favor of the ``multivariant`` parameter +and attribute. ``multivariant`` is an :py:class:`M3U8` reference of the parsed HLS multivariant playlist. + + +Removal of streamlink.plugin.api.utils +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``streamlink.plugin.api.utils`` module including the ``itertags`` function and export aliases for ``streamlink.utils.parse`` +has been removed. Import the parse functions directly and find data in XML/HTML by parsing it via ``parse_{xml,html}`` and +applying XPath queries to the parsed result via the available methods provided by the ``lxml.etree`` API. The +``streamlink.plugin.api.validate`` module also has the necessary validation schema functions for this. + + streamlink 2.27.0.0 ------------------- diff --git a/src/streamlink/plugin/api/utils.py b/src/streamlink/plugin/api/utils.py deleted file mode 100644 index 5c1d2ef9458..00000000000 --- a/src/streamlink/plugin/api/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Useful wrappers and other tools.""" -import re -from collections import namedtuple - -from streamlink.utils.parse import parse_json, parse_qsd as parse_query, parse_xml - -__all__ = ["parse_json", "parse_xml", "parse_query"] - - -tag_re = re.compile(r'''(?=<(?P[a-zA-Z]+)(?P.*?)(?P/)?>(?:(?P.*?))?)''', - re.MULTILINE | re.DOTALL) -attr_re = re.compile(r'''\s*(?P[\w-]+)\s*(?:=\s*(?P["']?)(?P.*?)(?P=quote)\s*)?''') -Tag = namedtuple("Tag", "tag attributes text") - - -def itertags(html, tag): - """ - Brute force regex based HTML tag parser. This is a rough-and-ready searcher to find HTML tags when - standards compliance is not required. Will find tags that are commented out, or inside script tag etc. - - :param html: HTML page - :param tag: tag name to find - :return: generator with Tags - """ - for match in tag_re.finditer(html): - if match.group("tag") == tag: - attrs = dict((a.group("key").lower(), a.group("value")) for a in attr_re.finditer(match.group("attr"))) - yield Tag(match.group("tag"), attrs, match.group("inner")) diff --git a/src/streamlink/stream/hls.py b/src/streamlink/stream/hls.py index b34cd1aec49..0f3356dd986 100644 --- a/src/streamlink/stream/hls.py +++ b/src/streamlink/stream/hls.py @@ -373,9 +373,14 @@ def __init__( ffmpeg_options = ffmpeg_options or {} super(MuxedHLSStream, self).__init__(session, *substreams, format="mpegts", maps=maps, **ffmpeg_options) - self.url_master = url_master + self._url_master = url_master self.multivariant = multivariant if multivariant and multivariant.is_master else None + @property + def url_master(self): + """Deprecated""" + return self.multivariant.uri if self.multivariant and self.multivariant.uri else self._url_master + def to_manifest_url(self): url = self.multivariant.uri if self.multivariant and self.multivariant.uri else self.url_master @@ -421,7 +426,7 @@ def __init__( :param args: Additional keyword arguments passed to :meth:`requests.Session.request` """ super(HLSStream, self).__init__(session_, url, **args) - self.url_master = url_master + self._url_master = url_master self.multivariant = multivariant if multivariant and multivariant.is_master else None self.force_restart = force_restart self.start_offset = start_offset @@ -440,6 +445,11 @@ def __json__(self): return json + @property + def url_master(self): + """Deprecated""" + return self.multivariant.uri if self.multivariant and self.multivariant.uri else self._url_master + def to_manifest_url(self): url = self.multivariant.uri if self.multivariant and self.multivariant.uri else self.url_master diff --git a/tests/stream/test_hls.py b/tests/stream/test_hls.py index 55bf0ef6c4e..0d97296bce6 100644 --- a/tests/stream/test_hls.py +++ b/tests/stream/test_hls.py @@ -99,6 +99,18 @@ def test_variant_playlist(self): stream = next(iter(streams.values())) assert repr(stream) == "".format(base) + assert stream.multivariant is not None + assert stream.multivariant.uri == "{0}/master.m3u8".format(base) + assert stream.url_master == "{0}/master.m3u8".format(base) + + def test_url_master(self): + session = Streamlink() + stream = HLSStream(session, "http://mocked/foo", url_master="http://mocked/master.m3u8") + + assert stream.multivariant is None + assert stream.url == "http://mocked/foo" + assert stream.url_master == "http://mocked/master.m3u8" + class EventedHLSReader(HLSStreamReader): __writer__ = EventedHLSStreamWriter diff --git a/tests/test_plugin_utils.py b/tests/test_plugin_utils.py deleted file mode 100644 index 3c089407c7d..00000000000 --- a/tests/test_plugin_utils.py +++ /dev/null @@ -1,102 +0,0 @@ -from __future__ import unicode_literals - -import sys -import unittest - -from streamlink.plugin.api.utils import itertags - - -def unsupported_versions_1979(): - """Unsupported python versions for itertags - 3.7.0 - 3.7.2 and 3.8.0a1 - - https://github.com/streamlink/streamlink/issues/1979 - - https://bugs.python.org/issue34294 - """ - v = sys.version_info - if (v.major == 3) and ( - # 3.7.0 - 3.7.2 - (v.minor == 7 and v.micro <= 2) - # 3.8.0a1 - or (v.minor == 8 and v.micro == 0 and v.releaselevel == 'alpha' and v.serial <= 1) - ): - return True - else: - return False - - -class TestPluginUtil(unittest.TestCase): - test_html = """ - - -Title - - - - - - -

-bar -

- - """ # noqa: W291 - - def test_itertags_single_text(self): - title = list(itertags(self.test_html, "title")) - self.assertTrue(len(title), 1) - self.assertEqual(title[0].tag, "title") - self.assertEqual(title[0].text, "Title") - self.assertEqual(title[0].attributes, {}) - - def test_itertags_attrs_text(self): - script = list(itertags(self.test_html, "script")) - self.assertTrue(len(script), 2) - self.assertEqual(script[0].tag, "script") - self.assertEqual(script[0].text, "") - self.assertEqual(script[0].attributes, {"src": "https://test.se/test.js"}) - - self.assertEqual(script[1].tag, "script") - self.assertEqual(script[1].text.strip(), """Tester.ready(function () {\nalert("Hello, world!"); });""") - self.assertEqual(script[1].attributes, {}) - - @unittest.skipIf(unsupported_versions_1979(), - "python3.7 issue, see bpo-34294") - def test_itertags_multi_attrs(self): - metas = list(itertags(self.test_html, "meta")) - self.assertTrue(len(metas), 3) - self.assertTrue(all(meta.tag == "meta" for meta in metas)) - - self.assertEqual(metas[0].text, None) - self.assertEqual(metas[1].text, None) - self.assertEqual(metas[2].text, None) - - self.assertEqual(metas[0].attributes, {"property": "og:type", "content": "website"}) - self.assertEqual(metas[1].attributes, {"property": "og:url", "content": "http://test.se/"}) - self.assertEqual(metas[2].attributes, {"property": "og:site_name", "content": "Test"}) - - def test_multi_line_a(self): - anchor = list(itertags(self.test_html, "a")) - self.assertTrue(len(anchor), 1) - self.assertEqual(anchor[0].tag, "a") - self.assertEqual(anchor[0].text, "bar") - self.assertEqual(anchor[0].attributes, {"href": "http://test.se/foo"}) - - @unittest.skipIf(unsupported_versions_1979(), - "python3.7 issue, see bpo-34294") - def test_no_end_tag(self): - links = list(itertags(self.test_html, "link")) - self.assertTrue(len(links), 1) - self.assertEqual(links[0].tag, "link") - self.assertEqual(links[0].text, None) - self.assertEqual(links[0].attributes, {"rel": "stylesheet", - "type": "text/css", - "href": "https://test.se/test.css"}) - - def test_tag_inner_tag(self): - links = list(itertags(self.test_html, "p")) - self.assertTrue(len(links), 1) - self.assertEqual(links[0].tag, "p") - self.assertEqual(links[0].text.strip(), 'bar') - self.assertEqual(links[0].attributes, {})