Repository: streamlink/streamlink
Pull request #903 (2017-05-09 16:32:45)

plugins.pluzz: rework for new france.tv platform replacing pluzz.francetv.fr
See https://github.com/streamlink/streamlink/issues/902


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 4fdc7e46..13374ee7 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -149,7 +149,7 @@ pcyourfreetv        pc-yourfreetv.com    Yes   --    Requires a login.
 periscope           periscope.tv         Yes   Yes   Replay/VOD is supported.
 picarto             picarto.tv           Yes   --
 playtv              playtv.fr            Yes   --    Streams may be geo-restricted to France.
-pluzz               - pluzz.francetv.fr  Yes   Yes   Streams may be geo-restricted to France, Andorra and Monaco.
+pluzz               - france.tv          Yes   Yes   Streams may be geo-restricted to France, Andorra and Monaco.
                     - ludo.fr
                     - zouzous.fr
 powerapp            powerapp.com.tr      Yes   No
diff --git a/src/streamlink/plugins/pluzz.py b/src/streamlink/plugins/pluzz.py
index db49cdc1..0e9377c0 100644
--- a/src/streamlink/plugins/pluzz.py
+++ b/src/streamlink/plugins/pluzz.py
@@ -1,4 +1,3 @@
-import os.path
 import re
 import sys
 import time
@@ -12,11 +11,12 @@ from streamlink.utils import update_scheme
 
 class Pluzz(Plugin):
     GEO_URL = 'http://geo.francetv.fr/ws/edgescape.json'
-    API_URL = 'http://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion={0}&catalogue={1}'
+    API_URL = 'http://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion={0}'
+    PLAYER_GENERATOR_URL = 'https://sivideo.webservices.francetelevisions.fr/assets/staticmd5/getUrl?id=jquery.player.7.js'
     TOKEN_URL = 'http://hdfauthftv-a.akamaihd.net/esi/TA?url={0}'
 
-    _url_re = re.compile(r'http://(pluzz\.francetv\.fr/(videos/.+\.html|[\w-]+)|www\.(ludo|zouzous)\.fr/heros/[\w-]+)')
-    _pluzz_video_id_re = re.compile(r'id="current_video" href="http://.+?\.(?:francetv|francetelevisions)\.fr/(?:video/|\?id-video=)(?P<video_id>.+?)"')
+    _url_re = re.compile(r'https?://((?:www)\.france\.tv/.+\.html|www\.(ludo|zouzous)\.fr/heros/[\w-]+)')
+    _pluzz_video_id_re = re.compile(r'data-main-video="(?P<video_id>.+?)"')
     _other_video_id_re = re.compile(r'playlist: \[{.*?,"identity":"(?P<video_id>.+?)@(?P<catalogue>Ludo|Zouzous)"')
     _player_re = re.compile(r'src="(?P<player>//staticftv-a\.akamaihd\.net/player/jquery\.player.+?-[0-9a-f]+?\.js)"></script>')
     _swf_re = re.compile(r'//staticftv-a\.akamaihd\.net/player/bower_components/player_flash/dist/FranceTVNVPVFlashPlayer\.akamai-[0-9a-f]+\.swf')
@@ -71,6 +71,8 @@ class Pluzz(Plugin):
         )
     })
 
+    _player_schema = validate.Schema({'result': validate.url()})
+
     options = PluginOptions({
         "mux_subtitles": False
     })
@@ -87,29 +89,21 @@ class Pluzz(Plugin):
 
         # Retrieve URL page and search for video ID
         res = http.get(self.url)
-        if 'pluzz.francetv.fr' in self.url:
-            video_re = self._pluzz_video_id_re
-        else:
-            video_re = self._other_video_id_re
-        match = video_re.search(res.text)
+        match = self._pluzz_video_id_re.search(res.text) or self._other_video_id_re.search(res.text)
         if match is None:
             return
-        catalogue = 'Pluzz'
         video_id = match.group('video_id')
-        if 'catalogue' in match.groupdict():
-            catalogue = match.group('catalogue')
 
         # Retrieve SWF player URL
-        match = self._player_re.search(res.text)
         swf_url = None
+        res = http.get(self.PLAYER_GENERATOR_URL)
+        player_url = update_scheme(self.url, http.json(res, schema=self._player_schema)['result'])
+        res = http.get(player_url)
+        match = self._swf_re.search(res.text)
         if match is not None:
-            player_url = update_scheme(self.url, match.group('player'))
-            res = http.get(player_url)
-            match = self._swf_re.search(res.text)
-            if match is not None:
-                swf_url = update_scheme(self.url, match.group(0))
+            swf_url = update_scheme(self.url, match.group(0))
 
-        res = http.get(self.API_URL.format(video_id, catalogue))
+        res = http.get(self.API_URL.format(video_id))
         videos = http.json(res, schema=self._api_schema)
         now = time.time()
 
@@ -151,12 +145,15 @@ class Pluzz(Plugin):
             if '.mpd' in video_url:
                 continue
 
-            if catalogue == 'Pluzz' or '.f4m' in video_url:
+            if '.f4m' in video_url or 'france.tv' in self.url:
                 res = http.get(self.TOKEN_URL.format(video_url))
                 video_url = res.text
 
             if '.f4m' in video_url and swf_url is not None:
-                for bitrate, stream in HDSStream.parse_manifest(self.session, video_url, pvswf=swf_url).items():
+                for bitrate, stream in HDSStream.parse_manifest(self.session,
+                                                                video_url,
+                                                                is_akamai=True,
+                                                                pvswf=swf_url).items():
                     # HDS videos with data in their manifest fragment token
                     # doesn't seem to be supported by HDSStream. Ignore such
                     # stream (but HDS stream having only the hdntl parameter in


Test patch:
diff --git a/tests/test_plugin_pluzz.py b/tests/test_plugin_pluzz.py
index 70680220..cac35e71 100644
--- a/tests/test_plugin_pluzz.py
+++ b/tests/test_plugin_pluzz.py
@@ -6,21 +6,22 @@ from streamlink.plugins.pluzz import Pluzz
 class TestPluginPluzz(unittest.TestCase):
     def test_can_handle_url(self):
         # should match
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/france2"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/france3"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/france3-franche-comte"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/france4"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/france5"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/franceo"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/franceinfo"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/videos/jt20h.html"))
-        self.assertTrue(Pluzz.can_handle_url("http://pluzz.francetv.fr/videos/jt_1213_franche_comte.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-2/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-3/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-3-franche-comte/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-4/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-5/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-o/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/franceinfo/direct.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-2/journal-20h00/141003-edition-du-lundi-8-mai-2017.html"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.france.tv/france-o/underground/saison-1/132187-underground.html"))
         self.assertTrue(Pluzz.can_handle_url("http://www.ludo.fr/heros/the-batman"))
         self.assertTrue(Pluzz.can_handle_url("http://www.ludo.fr/heros/il-etait-une-fois-la-vie"))
         self.assertTrue(Pluzz.can_handle_url("http://www.zouzous.fr/heros/oui-oui"))
         self.assertTrue(Pluzz.can_handle_url("http://www.zouzous.fr/heros/marsupilami-1"))
 
         # shouldn't match
+        self.assertFalse(Pluzz.can_handle_url("http://www.france.tv/"))
         self.assertFalse(Pluzz.can_handle_url("http://pluzz.francetv.fr/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.ludo.fr/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.ludo.fr/jeux"))