Repository: streamlink/streamlink
Pull request #379 (2017-01-08 06:08:17)

Links on crunchy's rss no longer contain the show name in the url
I don't know since when, but rss links on crunchy no longer contain the show name slug on the url, so the current regex fails to match against those links.
I didn't remove the group since some people get the link directly from the page, which still use the previous format.

codecov-io: ## [Current coverage](https://codecov.io/gh/streamlink/streamlink/pull/379?src=pr) is 49.08% (diff: 100%)
> Merging [#379](https://codecov.io/gh/streamlink/streamlink/pull/379?src=pr) into [master](https://codecov.io/gh/streamlink/streamlink/branch/master?src=pr) will not change coverage

```diff
@@             master       #379   diff @@
==========================================
  Files            43         43          
  Lines          4156       4156          
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
  Hits           2040       2040          
  Misses         2116       2116          
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [697d6d3...d9ad87f](https://codecov.io/gh/streamlink/streamlink/compare/697d6d3d3e956e75a64309fdff180d733120c937...d9ad87fc64181a0262f9972605fd8bdab9f7134d?src=pr)
beardypig: Thanks! Looks good, do you have some example URLs so I can verify and get it merged :-)

If you feel like it you could add some tests for the URL matching for crunchyroll similar to this [test_plugin_adultswim.py](https://github.com/streamlink/streamlink/blob/master/tests/test_plugin_adultswim.py). :-)
asermax: Added some tests, but just in case here are some examples of valid urls:

```
http://www.crunchyroll.com/march-comes-in-like-a-lion/episode-115-omnibus-special-episode-728151
http://www.crunchyroll.com/march-comes-in-like-a-lion/media-728151
http://www.crunchyroll.com/media-728151
```

Let me know if there's anything else I should do 👍 

Patch:
diff --git a/src/streamlink/plugins/artetv.py b/src/streamlink/plugins/artetv.py
index 3565a128..8cf78f37 100644
--- a/src/streamlink/plugins/artetv.py
+++ b/src/streamlink/plugins/artetv.py
@@ -7,23 +7,21 @@ from itertools import chain
 from streamlink.compat import urlparse
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, validate
-from streamlink.stream import HLSStream, HTTPStream, RTMPStream
+from streamlink.stream import HDSStream, HLSStream, HTTPStream, RTMPStream
 
 SWF_URL = "http://www.arte.tv/player/v2/jwplayer6/mediaplayer.6.6.swf"
-
-_url_re = re.compile(r"http(s)?://(\w+\.)?arte.tv/")
-_json_re = re.compile(r'arte_vp_(?:live-)?url=([\'"])(.+?)\1')
-
-_schema = validate.Schema(
-    validate.transform(_json_re.search),
-    validate.any(
-        None,
-        validate.all(
-            validate.get(2),
-            validate.url(scheme="http")
-        )
+JSON_VOD_URL = "https://api.arte.tv/api/player/v1/config/{}/{}"
+JSON_LIVE_URL = "https://api.arte.tv/api/player/v1/livestream/{}"
+
+_url_re = re.compile(r"""
+    https?://(?:\w+\.)?arte.tv/guide/
+    (?P<language>[a-z]{2})/
+    (?:
+        (?P<video_id>.+?)/.+ | # VOD
+        (?:direct|live)        # Live TV
     )
-)
+""", re.VERBOSE)
+
 _video_schema = validate.Schema({
     "videoJsonPlayer": {
         "VSR": validate.any(
@@ -41,7 +39,6 @@ _video_schema = validate.Schema({
     }
 })
 
-
 class ArteTV(Plugin):
     @classmethod
     def can_handle_url(self, url):
@@ -65,6 +62,15 @@ class ArteTV(Plugin):
             else:
                 yield stream_name, HTTPStream(self.session, stream_url)
 
+        elif stream_type == "f4m":
+            try:
+                streams = HDSStream.parse_manifest(self.session, stream_url)
+
+                for stream in streams.items():
+                    yield stream
+            except IOError as err:
+                self.logger.error("Failed to extract HDS streams: {0}", err)
+
         elif stream_type == "rtmp":
             params = {
                 "rtmp": stream["streamer"],
@@ -82,10 +88,13 @@ class ArteTV(Plugin):
             yield stream_name, stream
 
     def _get_streams(self):
-        json_url = http.get(self.url, schema=_schema)
-        if not json_url:
-            return
-
+        match = _url_re.match(self.url)
+        language = match.group('language')
+        video_id = match.group('video_id')
+        if video_id is None:
+            json_url = JSON_LIVE_URL.format(language)
+        else:
+            json_url = JSON_VOD_URL.format(language, video_id)
         res = http.get(json_url)
         video = http.json(res, schema=_video_schema)
 
diff --git a/src/streamlink/plugins/crunchyroll.py b/src/streamlink/plugins/crunchyroll.py
index 285c351b..a2d823a3 100644
--- a/src/streamlink/plugins/crunchyroll.py
+++ b/src/streamlink/plugins/crunchyroll.py
@@ -46,7 +46,7 @@ _url_re = re.compile(r"""
     (?:
         com|de|es|fr|co.jp
     )
-    /[^/&?]+
+    (?:/[^/&?]+)?
     /[^/&?]+-(?P<media_id>\d+)
 """, re.VERBOSE)
 


Test patch:
diff --git a/tests/test_plugin_artetv.py b/tests/test_plugin_artetv.py
new file mode 100644
index 00000000..033d1edf
--- /dev/null
+++ b/tests/test_plugin_artetv.py
@@ -0,0 +1,21 @@
+import unittest
+
+from streamlink.plugins.artetv import ArteTV
+
+
+class TestPluginArteTV(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/fr/direct"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/de/live"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/fr/024031-000-A/le-testament-du-docteur-mabuse"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/de/024031-000-A/das-testament-des-dr-mabuse"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/en/072544-002-A/christmas-carols-from-cork"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/es/068380-000-A/una-noche-en-florencia"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/pl/068916-006-A/belle-and-sebastian-route-du-rock"))
+
+        # shouldn't match
+        self.assertFalse(ArteTV.can_handle_url("http://www.arte.tv/guide/fr/plus7/"))
+        self.assertFalse(ArteTV.can_handle_url("http://www.arte.tv/guide/de/plus7/"))
+        self.assertFalse(ArteTV.can_handle_url("http://www.tvcatchup.com/"))
+        self.assertFalse(ArteTV.can_handle_url("http://www.youtube.com/"))
diff --git a/tests/test_plugin_crunchyroll.py b/tests/test_plugin_crunchyroll.py
new file mode 100644
index 00000000..edcc4736
--- /dev/null
+++ b/tests/test_plugin_crunchyroll.py
@@ -0,0 +1,17 @@
+import unittest
+
+from streamlink.plugins.crunchyroll import Crunchyroll
+
+
+class TestPluginCrunchyroll(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(Crunchyroll.can_handle_url("http://www.crunchyroll.com/idol-incidents/episode-1-why-become-a-dietwoman-728233"))
+        self.assertTrue(Crunchyroll.can_handle_url("http://www.crunchyroll.com/idol-incidents/media-728233"))
+        self.assertTrue(Crunchyroll.can_handle_url("http://www.crunchyroll.com/media-728233"))
+        self.assertTrue(Crunchyroll.can_handle_url("http://www.crunchyroll.fr/media-728233"))
+
+        # shouldn't match
+        self.assertFalse(Crunchyroll.can_handle_url("http://www.crunchyroll.com/gintama"))
+        self.assertFalse(Crunchyroll.can_handle_url("http://www.crunchyroll.es/gintama"))
+        self.assertFalse(Crunchyroll.can_handle_url("http://www.youtube.com/"))