Repository: streamlink/streamlink
Pull request #2119 (2018-10-14 22:29:08)

plugins.pluzz: fixes and francetvinfo.fr support
This PR:
* speeds up stream resolution for HDS streams (by using a static URL for the SWF player instead of searching it each time the plugin is run)
* makes the plugin use token generator for all kind of streams (since a recent change on France TV)
* enable full support for videos on francetvinfo.fr and its subdomains (fixes https://github.com/streamlink/streamlink/issues/1959)

codecov[bot]: # [Codecov](https://codecov.io/gh/streamlink/streamlink/pull/2119?src=pr&el=h1) Report
> Merging [#2119](https://codecov.io/gh/streamlink/streamlink/pull/2119?src=pr&el=desc) into [master](https://codecov.io/gh/streamlink/streamlink/commit/cb587d80f91ee41f70c1177e184af8b616b06b25?src=pr&el=desc) will **increase** coverage by `0.03%`.
> The diff coverage is `42.85%`.


```diff
@@            Coverage Diff             @@
##           master    #2119      +/-   ##
==========================================
+ Coverage   35.82%   35.86%   +0.03%     
==========================================
  Files         232      232              
  Lines       20324    20294      -30     
==========================================
- Hits         7281     7278       -3     
+ Misses      13043    13016      -27
```


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 5b6694bc..664e80f6 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -141,7 +141,7 @@ playtv                  - playtv.fr          Yes   --    Streams may be geo-rest
 pluzz                   - france.tv          Yes   Yes   Streams may be geo-restricted to France, Andorra and Monaco.
                         - ludo.fr
                         - zouzous.fr
-                        - france3-reg.. [8]_
+                        - francetvinfo.fr
 powerapp                powerapp.com.tr      Yes   No
 qq                      live.qq.com          Yes   No
 radionet                - radio.net          Yes   --
@@ -215,7 +215,7 @@ tv4play                 - tv4play.se         Yes   Yes   Streams may be geo-rest
                         - fotbollskanalen.se
 tv5monde                - tv5monde.com       Yes   Yes   Streams may be geo-restricted to France, Belgium and Switzerland.
                         - tv5mondeplus.com
-                        - tv5mondepl... [9]_
+                        - tv5mondepl... [8]_
 tv8                     tv8.com.tr           Yes   No
 tv360                   tv360.com.tr         Yes   No
 tvcatchup               tvcatchup.com        Yes   No    Streams may be geo-restricted to Great Britain.
@@ -257,10 +257,10 @@ youtube                 - youtube.com        Yes   Yes   Protected videos are no
                         - youtu.be
 yupptv                  yupptv.com           Yes   Yes   Some streams require an account and subscription.
 zattoo                  - zattoo.com         Yes   Yes
-                        - nettv.net... [10]_
+                        - nettv.net... [9]_
                         - tvonline.ewe.de
-                        - iptv.glat... [11]_
-                        - mobiltv.q... [12]_
+                        - iptv.glat... [10]_
+                        - mobiltv.q... [11]_
                         - player.waly.tv
                         - tvplus.m-net.de
                         - www.bbv-tv.net
@@ -280,8 +280,7 @@ zhanqi                  zhanqi.tv            Yes   No
 .. [5] mediathek.daserste.de
 .. [6] players.brightcove.net
 .. [7] player.theplatform.com
-.. [8] france3-regions.francetvinfo.fr
-.. [9] tv5mondeplusafrique.com
-.. [10] nettv.netcologne.de
-.. [11] iptv.glattvision.ch
-.. [12] mobiltv.quickline.com
+.. [8] tv5mondeplusafrique.com
+.. [9] nettv.netcologne.de
+.. [10] iptv.glattvision.ch
+.. [11] mobiltv.quickline.com
diff --git a/src/streamlink/_version.py b/src/streamlink/_version.py
index 5e81ded0..48366bc0 100644
--- a/src/streamlink/_version.py
+++ b/src/streamlink/_version.py
@@ -235,7 +235,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
     # if there isn't one, this yields HEX[-dirty] (no NUM)
     describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
-                                          "--always", "--long",
+                                          "--always", "--long", "--abbrev=7",
                                           "--match", "%s*" % tag_prefix],
                                    cwd=root)
     # --long was added in git-1.5.5
diff --git a/src/streamlink/plugins/pluzz.py b/src/streamlink/plugins/pluzz.py
index 3e00b4eb..7ba24028 100644
--- a/src/streamlink/plugins/pluzz.py
+++ b/src/streamlink/plugins/pluzz.py
@@ -6,25 +6,24 @@ from streamlink.plugin import Plugin, PluginArguments, PluginArgument
 from streamlink.plugin.api import validate
 from streamlink.stream import DASHStream, HDSStream, HLSStream, HTTPStream
 from streamlink.stream.ffmpegmux import MuxedStream
-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}'
-    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'https?://((?:www)\.france\.tv/.+\.html|www\.(ludo|zouzous)\.fr/heros/[\w-]+|(sport|france3-regions)\.francetvinfo\.fr/.+?/(tv/direct)?)')
+    SWF_PLAYER_URL = 'https://staticftv-a.akamaihd.net/player/bower_components/player_flash/dist/FranceTVNVPVFlashPlayer.akamai-7301b6035a43c4e29b7935c9c36771d2.swf'
+
+    _url_re = re.compile(r'''
+        https?://(
+            (?:www\.)france\.tv/.+\.html |
+            www\.(ludo|zouzous)\.fr/heros/[\w-]+ |
+            (.+\.)?francetvinfo\.fr)
+    ''', re.VERBOSE)
     _pluzz_video_id_re = re.compile(r'data-main-video="(?P<video_id>.+?)"')
     _jeunesse_video_id_re = re.compile(r'playlist: \[{.*?,"identity":"(?P<video_id>.+?)@(?P<catalogue>Ludo|Zouzous)"')
-    _f3_regions_video_id_re = re.compile(r'"http://videos\.francetv\.fr/video/(?P<video_id>.+?)@Regions"')
     _sport_video_id_re = re.compile(r'data-video="(?P<video_id>.+?)"')
-    _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'"(bower_components/player_flash/dist/FranceTVNVPVFlashPlayer\.akamai-[0-9a-f]+\.swf)"')
+    _embed_video_id_re = re.compile(r'href="http://videos\.francetv\.fr/video/(?P<video_id>.+?)(?:@.+?)?"')
     _hds_pv_data_re = re.compile(r"~data=.+?!")
     _mp4_bitrate_re = re.compile(r'.*-(?P<bitrate>[0-9]+k)\.mp4')
 
@@ -107,23 +106,14 @@ class Pluzz(Plugin):
             match = self._pluzz_video_id_re.search(res.text)
         elif 'ludo.fr' in self.url or 'zouzous.fr' in self.url:
             match = self._jeunesse_video_id_re.search(res.text)
-        elif 'france3-regions.francetvinfo.fr' in self.url:
-            match = self._f3_regions_video_id_re.search(res.text)
         elif 'sport.francetvinfo.fr' in self.url:
             match = self._sport_video_id_re.search(res.text)
+        else:
+            match = self._embed_video_id_re.search(res.text)
         if match is None:
             return
         video_id = match.group('video_id')
 
-        # Retrieve SWF player URL
-        swf_url = None
-        res = self.session.http.get(self.PLAYER_GENERATOR_URL)
-        player_url = update_scheme(self.url, self.session.http.json(res, schema=self._player_schema)['result'])
-        res = self.session.http.get(player_url)
-        match = self._swf_re.search(res.text)
-        if match is not None:
-            swf_url = 'https://staticftv-a.akamaihd.net/player/' + match.group(1)
-
         res = self.session.http.get(self.API_URL.format(video_id))
         videos = self.session.http.json(res, schema=self._api_schema)
         now = time.time()
@@ -162,12 +152,8 @@ class Pluzz(Plugin):
                 expired = expired or True
                 continue
 
-            if ('.f4m' in video_url or
-                '.mpd' in video_url or
-                'france.tv' in self.url or
-                'sport.francetvinfo.fr' in self.url):
-                res = self.session.http.get(self.TOKEN_URL.format(video_url))
-                video_url = res.text
+            res = self.session.http.get(self.TOKEN_URL.format(video_url))
+            video_url = res.text
 
             if '.mpd' in video_url:
                 # Get redirect video URL
@@ -176,11 +162,11 @@ class Pluzz(Plugin):
                 for bitrate, stream in DASHStream.parse_manifest(self.session,
                                                                  video_url).items():
                     streams.append((bitrate, stream))
-            elif '.f4m' in video_url and swf_url is not None:
+            elif '.f4m' in video_url:
                 for bitrate, stream in HDSStream.parse_manifest(self.session,
                                                                 video_url,
                                                                 is_akamai=True,
-                                                                pvswf=swf_url).items():
+                                                                pvswf=self.SWF_PLAYER_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
diff --git a/versioneer.py b/versioneer.py
index 64fea1c8..831dac8b 100644
--- a/versioneer.py
+++ b/versioneer.py
@@ -1047,7 +1047,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
     # if there isn't one, this yields HEX[-dirty] (no NUM)
     describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
-                                          "--always", "--long",
+                                          "--always", "--long", "--abbrev=7",
                                           "--match", "%s*" % tag_prefix],
                                    cwd=root)
     # --long was added in git-1.5.5


Test patch:
diff --git a/tests/plugins/test_pluzz.py b/tests/plugins/test_pluzz.py
index 5a709c31..f49018eb 100644
--- a/tests/plugins/test_pluzz.py
+++ b/tests/plugins/test_pluzz.py
@@ -23,6 +23,7 @@ class TestPluginPluzz(unittest.TestCase):
         self.assertTrue(Pluzz.can_handle_url("http://sport.francetvinfo.fr/roland-garros/direct"))
         self.assertTrue(Pluzz.can_handle_url("http://sport.francetvinfo.fr/roland-garros/live-court-3"))
         self.assertTrue(Pluzz.can_handle_url("http://sport.francetvinfo.fr/roland-garros/andy-murray-gbr-1-andrey-kuznetsov-rus-1er-tour-court-philippe-chatrier"))
+        self.assertTrue(Pluzz.can_handle_url("https://www.francetvinfo.fr/en-direct/tv.html"))
 
         # shouldn't match
         self.assertFalse(Pluzz.can_handle_url("http://www.france.tv/"))
@@ -30,6 +31,5 @@ class TestPluginPluzz(unittest.TestCase):
         self.assertFalse(Pluzz.can_handle_url("http://www.ludo.fr/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.ludo.fr/jeux"))
         self.assertFalse(Pluzz.can_handle_url("http://www.zouzous.fr/"))
-        self.assertFalse(Pluzz.can_handle_url("http://france3-regions.francetvinfo.fr/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.tvcatchup.com/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.youtube.com/"))