Repository: streamlink/streamlink
Pull request #835 (2017-04-24 20:53:28)

Add support for rte.ie/player
This plugin adds support for the Irsh public TV video platform RTÉ Player, as requested in https://github.com/streamlink/streamlink/issues/819. It supports:

* live streams:
  * RTÉ News: http://www.rte.ie/player/fr/live/7/
  * RTÉ 1: http://www.rte.ie/player/fr/live/8/ (not available all the time)
  * RTÉ 2: http://www.rte.ie/player/fr/live/10/ (not available all the time)
  * RTÉjr http://www.rte.ie/player/fr/live/6/ (not available all the time)

* VOD; for example:
  * http://www.rte.ie/player/fr/show/weather-30003847/10717886/
  * http://www.rte.ie/player/fr/show/rte-news-six-one-30003249/10717883/
  * http://www.rte.ie/player/fr/show/the-week-in-politics-extras-30004539/10717332/

Streams don't seem to be georestricted.



Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index fd3ede1f..2b099b79 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -150,6 +150,7 @@ pluzz               - pluzz.francetv.fr  Yes   Yes   Streams may be geo-restrict
 powerapp            powerapp.com.tr      Yes   No
 raiplay             raiplay.it           Yes   No    Most streams are geo-restricted to Italy.
 rtlxl               rtlxl.nl             No    Yes   Streams may be geo-restricted to The Netherlands. Livestreams not supported.
+rte                 rte.ie/player        Yes   Yes
 rtve                rtve.es              Yes   No
 rtvs                rtvs.sk              Yes   No    Streams may be geo-restricted to Slovakia.
 ruv                 ruv.is               Yes   Yes   Streams may be geo-restricted to Iceland.
diff --git a/src/streamlink/plugins/rte.py b/src/streamlink/plugins/rte.py
new file mode 100644
index 00000000..04fb9824
--- /dev/null
+++ b/src/streamlink/plugins/rte.py
@@ -0,0 +1,110 @@
+from datetime import datetime
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, validate
+from streamlink.stream import HDSStream, HLSStream
+
+
+class RTE(Plugin):
+    VOD_API_URL = 'http://www.rte.ie/rteavgen/getplaylist/?type=web&format=json&id={0}'
+    LIVE_API_URL = 'http://feeds.rasset.ie/livelistings/playlist'
+
+    _url_re = re.compile(r'http://www\.rte\.ie/player/[a-z0-9]+/(?:show/[a-z-]+-[0-9]+/(?P<video_id>[0-9]+)|live/(?P<channel_id>[0-9]+))')
+
+    _vod_api_schema = validate.Schema({
+        'current_date': validate.text,
+        'shows': validate.Schema(
+            list,
+            validate.length(1),
+            validate.get(0),
+            validate.Schema({
+                'valid_start': validate.text,
+                'valid_end': validate.text,
+                'media:group': validate.Schema(
+                    list,
+                    validate.length(1),
+                    validate.get(0),
+                    validate.Schema(
+                        {
+                            'hls_server': validate.url(),
+                            'hls_url': validate.text,
+                            'hds_server': validate.url(),
+                            'hds_url': validate.text,
+                            # API returns RTMP streams that don't seem to work, ignore them
+                            # 'url': validate.any(
+                            #     validate.url(scheme="rtmp"),
+                            #     validate.url(scheme="rtmpe")
+                            # )
+                        },
+                        validate.transform(lambda x: [x['hls_server'] + x['hls_url'], x['hds_server'] + x['hds_url']])
+                    ),
+                ),
+            }),
+        )
+    })
+
+    _live_api_schema = validate.Schema(
+        validate.xml_findall('.//{http://search.yahoo.com/mrss/}content'),
+        [
+            validate.all(
+                validate.xml_element(attrib={'url': validate.url()}),
+                validate.get('url')
+            )
+        ]
+    )
+    _live_api_iphone_schema = validate.Schema(
+        list,
+        validate.length(1),
+        validate.get(0),
+        validate.Schema(
+            {'fullUrl': validate.any(validate.url(), 'none')},
+            validate.get('fullUrl')
+        )
+    )
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return RTE._url_re.match(url)
+
+    def _get_streams(self):
+        match = self._url_re.match(self.url)
+        video_id = match.group('video_id')
+
+        if video_id is not None:
+            # VOD
+            res = http.get(self.VOD_API_URL.format(video_id))
+            stream_data = http.json(res, schema=self._vod_api_schema)
+
+            # Check whether video format is expired
+            current_date = datetime.strptime(stream_data['current_date'], '%Y-%m-%dT%H:%M:%S.%f')
+            valid_start = datetime.strptime(stream_data['shows']['valid_start'], '%Y-%m-%dT%H:%M:%S')
+            valid_end = datetime.strptime(stream_data['shows']['valid_end'], '%Y-%m-%dT%H:%M:%S')
+            if current_date < valid_start or current_date > valid_end:
+                self.logger.error('Failed to access stream, may be due to expired content')
+                return
+
+            streams = stream_data['shows']['media:group']
+        else:
+            # Live
+            channel_id = match.group('channel_id')
+            # Get live streams for desktop
+            res = http.get(self.LIVE_API_URL, params={'channelid': channel_id})
+            streams = http.xml(res, schema=self._live_api_schema)
+
+            # Get HLS streams for Iphone
+            res = http.get(self.LIVE_API_URL, params={'channelid': channel_id, 'platform': 'iphone'})
+            stream = http.json(res, schema=self._live_api_iphone_schema)
+            if stream != 'none':
+                streams.append(stream)
+
+        for stream in streams:
+            if '.f4m' in stream:
+                for s in HDSStream.parse_manifest(self.session, stream).items():
+                    yield s
+            if '.m3u8' in stream:
+                for s in HLSStream.parse_variant_playlist(self.session, stream).items():
+                    yield s
+
+
+__plugin__ = RTE


Test patch:
diff --git a/tests/test_plugin_rte.py b/tests/test_plugin_rte.py
new file mode 100644
index 00000000..62114a52
--- /dev/null
+++ b/tests/test_plugin_rte.py
@@ -0,0 +1,23 @@
+import unittest
+
+from streamlink.plugins.rte import RTE
+
+
+class TestPluginRTE(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/8/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/10/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/6/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/7/"))
+
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/show/rte-news-one-oclock-30003248/10714679/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/show/the-ray-darcy-show-extras-30003588/10714469/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/show/lotto-1251/10714463/"))
+
+        # shouldn't match
+        self.assertFalse(RTE.can_handle_url("http://www.rte.ie/"))
+        self.assertFalse(RTE.can_handle_url("http://www.rte.ie/tv/"))
+        self.assertFalse(RTE.can_handle_url("http://www.rte.ie/player/"))
+        self.assertFalse(RTE.can_handle_url("http://www.tvcatchup.com/"))
+        self.assertFalse(RTE.can_handle_url("http://www.youtube.com/"))