Repository: streamlink/streamlink
Pull request #1383 (2017-12-19 20:11:14)

[viasat] Added support for urls without a stream_id
- removed dead domains from _url_re
- added a error message for geo blocking
- new regex for stream_id from image url
- Removed old embed plugin
- try to find an iframe if no stream_id was found.
- added tests

Fixed https://github.com/streamlink/streamlink/issues/727
Fixed https://github.com/streamlink/streamlink/issues/1376


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index fee4b3a5..3fd510d7 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -243,19 +243,16 @@ vaughnlive          - vaughnlive.tv      Yes   --
                     - instagib.tv
                     - vapers.tv
 vgtv                vgtv.no              Yes   Yes
-viasat              - tv3play.se         Yes   Yes   Streams may be geo-restricted.
-                    - tv3play.no
-                    - tv3play.dk
-                    - tv3play.ee
-                    - tv3play.lt
-                    - tv3play.lv
-                    - tv6play.se
+viasat              - juicyplay.dk       Yes   Yes   Streams may be geo-restricted.
+                    - play.nova.bg
+                    - skaties.lv
+                    - tv3.dk
+                    - tv3.ee
+                    - tv3.lt
                     - tv6play.no
-                    - tv8play.se
-                    - tv10play.se
-                    - viasat4play.no
-                    - play.tv3.lt
-                    - juicyplay.se
+                    - viafree.dk
+                    - viafree.no
+                    - viafree.se
 vidio               vidio.com            Yes   Yes
 vk                  vk.com               Yes   Yes
 vrtbe               vrt.be/vrtnu         Yes   Yes
diff --git a/src/streamlink/plugins/viasat.py b/src/streamlink/plugins/viasat.py
index ee55de99..1281a8dc 100644
--- a/src/streamlink/plugins/viasat.py
+++ b/src/streamlink/plugins/viasat.py
@@ -1,50 +1,66 @@
-"""Plugin for Viasat's on demand content sites, such as tv6play.se."""
-
 import re
 
+from streamlink import NoStreamsError
 from streamlink.exceptions import PluginError
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import StreamMapper, http, validate
 from streamlink.stream import HDSStream, HLSStream, RTMPStream
 from streamlink.utils import rtmpparse
 
-STREAM_API_URL = "http://playapi.mtgx.tv/v3/videos/stream/{0}"
+STREAM_API_URL = "https://playapi.mtgx.tv/v3/videos/stream/{0}"
 
 _swf_url_re = re.compile(r"data-flashplayer-url=\"([^\"]+)\"")
 _player_data_re = re.compile(r"window.fluxData\s*=\s*JSON.parse\(\"(.+)\"\);")
 
-_url_re = re.compile(r"""
-    http(s)?://(www\.)?
-    (?:
-        tv(3|6|8|10)play |
-        viasat4play |
-        play.tv3 |
-        juicyplay |
-        viafree
-    )
-    \.
-    (?:
-        dk|ee|lt|lv|no|se|com
-    )
-    (/.+?/|/embed\?id=)
-    (?P<stream_id>\d+)
-""", re.VERBOSE)
-
 _stream_schema = validate.Schema(
-    {
-        "streams": validate.all(
-            {validate.text: validate.any(validate.text, int, None)},
-            validate.filter(lambda k, v: isinstance(v, validate.text))
-        )
-    },
-    validate.get("streams")
+    validate.any(
+        None,
+        validate.all({"msg": validate.text}),
+        validate.all({
+            "streams": validate.all(
+                {validate.text: validate.any(validate.text, int, None)},
+                validate.filter(lambda k, v: isinstance(v, validate.text))
+            )
+        }, validate.get("streams"))
+    )
 )
 
 
 class Viasat(Plugin):
+    """Streamlink Plugin for Viasat"""
+
+    _iframe_re = re.compile(r"""<iframe.+src=["'](?P<url>[^"']+)["'].+allowfullscreen""")
+    _image_re = re.compile(r"""<meta\sproperty=["']og:image["']\scontent=".+/(?P<stream_id>\d+)/[^/]+\.jpg""")
+
+    _url_re = re.compile(r"""https?://(?:www\.)?
+        (?:
+            juicyplay\.dk
+            |
+            play\.nova\.bg
+            |
+            (?:tvplay\.)?
+                skaties\.lv
+            |
+            (?:(?:tv3)?play\.)?
+                tv3\.(?:dk|ee|lt)
+            |
+            tv6play\.no
+            |
+            viafree\.(?:dk|no|se)
+        )
+        /(?:
+            (?:
+                .+/
+            |
+                embed\?id=
+            )
+            (?P<stream_id>\d+)
+        )?
+    """, re.VERBOSE)
+
     @classmethod
     def can_handle_url(cls, url):
-        return _url_re.match(url)
+        return cls._url_re.match(url)
 
     def _get_swf_url(self):
         res = http.get(self.url)
@@ -82,6 +98,11 @@ class Viasat(Plugin):
         res = http.get(STREAM_API_URL.format(stream_id), raise_for_status=False)
         stream_info = http.json(res, schema=_stream_schema)
 
+        if stream_info.get("msg"):
+            # error message
+            self.logger.error(stream_info.get("msg"))
+            raise NoStreamsError(self.url)
+
         mapper = StreamMapper(lambda pattern, video: re.search(pattern, video[1]))
         mapper.map(
             r"/\w+\.m3u8",
@@ -97,11 +118,31 @@ class Viasat(Plugin):
 
         return mapper(stream_info.items())
 
+    def _get_stream_id(self, text):
+        """Try to find a stream_id"""
+        m = self._image_re.search(text)
+        if m:
+            return m.group("stream_id")
+
+    def _get_iframe(self, text):
+        """Fallback if no stream_id was found before"""
+        m = self._iframe_re.search(text)
+        if m:
+            return self.session.streams(m.group("url"))
+
     def _get_streams(self):
-        match = _url_re.match(self.url)
+        match = self._url_re.match(self.url)
         stream_id = match.group("stream_id")
 
-        return self._extract_streams(stream_id)
+        if not stream_id:
+            text = http.get(self.url).text
+            stream_id = self._get_stream_id(text)
+
+            if not stream_id:
+                return self._get_iframe(text)
+
+        if stream_id:
+            return self._extract_streams(stream_id)
 
 
 __plugin__ = Viasat
diff --git a/src/streamlink/plugins/viasat_embed.py b/src/streamlink/plugins/viasat_embed.py
deleted file mode 100644
index ede940a7..00000000
--- a/src/streamlink/plugins/viasat_embed.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import re
-
-from streamlink.plugin import Plugin
-from streamlink.plugin.api import http
-
-
-_url_re = re.compile(r"http(s)?://(www\.)?tv(3|6|8|10)\.se")
-_embed_re = re.compile(r'<iframe class="iframe-player" src="([^"]+)">')
-
-
-class ViasatEmbed(Plugin):
-    @classmethod
-    def can_handle_url(self, url):
-        return _url_re.match(url)
-
-    def _get_streams(self):
-        res = http.get(self.url)
-
-        match = _embed_re.search(res.text)
-        if match:
-            url = match.group(1)
-            return self.session.streams(url)
-
-
-__plugin__ = ViasatEmbed


Test patch:
diff --git a/tests/test_plugin_viasat.py b/tests/test_plugin_viasat.py
new file mode 100644
index 00000000..4815d08b
--- /dev/null
+++ b/tests/test_plugin_viasat.py
@@ -0,0 +1,40 @@
+import unittest
+
+from streamlink.plugins.viasat import Viasat
+
+
+class TestPluginViasat(unittest.TestCase):
+    def test_can_handle_url(self):
+        should_match = [
+            "http://www.juicyplay.dk/story/se-robinson-benjamins-store-forandring",
+            "http://www.tv3.dk/paradise-hotel/paradise-hotel-2018-her-er-deltagerne",
+            "http://www.tv3.dk/paradise-hotel/paradise-hotel-2018-her-er-deltagerne",
+            "https://play.nova.bg/programi/kh-faktor/902681?autostart=true",
+            "https://play.tv3.lt/programos/eurocup/903167?autostart=true",
+            "https://play.tv3.lt/programos/pamilti-vel/903174?autostart=true",
+            "https://skaties.lv/sports/futbols/video-liverpool-izbraukuma-rada-klasi-pret-bournemouth/",
+            "https://tv3play.tv3.ee/sisu/eesti-otsib-superstaari",
+            "https://tv3play.tv3.ee/sisu/inglite-aeg/902432?autostart=true",
+            "https://tvplay.skaties.lv/parraides/darma-un-gregs/902597?autostart=true&collection=719",
+            "https://tvplay.skaties.lv/parraides/kungfu-panda/900510?autostart=true",
+            "https://www.tv3.lt/naujiena/938699/ispudingiausi-kobe-bryanto-karjeros-epizodai-monstriski-dejimai-ir-pergalingi-metimai",
+            "https://www.tv6play.no/programmer/underholdning/paradise-hotel-sverige/sesong-8/episode-19",
+            "https://www.tv6play.no/programmer/underholdning/paradise-hotel/sesong-9/822763",
+            "https://www.viafree.dk/",
+            "https://www.viafree.dk/embed?id=898974&wmode=transparent&autostart=true",
+            "https://www.viafree.dk/programmer/reality/forside-fruer/saeson-2/872877",
+            "https://www.viafree.dk/programmer/reality/forside-fruer/saeson-2/episode-1",
+            "https://www.viafree.no/programmer/underholdning/paradise-hotel-sverige/sesong-8/episode-19",
+            "https://www.viafree.no/programmer/underholdning/paradise-hotel/sesong-9/822763",
+            "https://www.viafree.se/program/underhallning/det-stora-experimentet/sasong-1/897870",
+            "https://www.viafree.se/program/underhallning/det-stora-experimentet/sasong-1/avsnitt-19",
+        ]
+        for url in should_match:
+            self.assertTrue(Viasat.can_handle_url(url))
+
+        should_not_match = [
+            "http://www.tv3play.no",
+            "http://www.tv6play.se",
+        ]
+        for url in should_not_match:
+            self.assertFalse(Viasat.can_handle_url(url))