Repository: streamlink/streamlink
Pull request #3905 (2021-08-06 20:58:00)

plugins.rtve: update for /play/ URLs
- update pluginmatcher URL
- use https
- remove to many mirror streams
- fix idAsset

closes https://github.com/streamlink/streamlink/issues/3897


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 05fe68a1..ae61d298 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -152,7 +152,7 @@ rotana                  rotana.net           Yes   --    Streams are geo-restric
 rtbf                    - rtbf.be/auvio      Yes   Yes   Streams may be geo-restricted to Belgium or Europe.
                         - rtbfradioplayer.be
 rtpplay                 rtp.pt/play          Yes   Yes   Streams may be geo-restricted to Portugal.
-rtve                    rtve.es              Yes   No    Streams may be geo-restricted to Spain.
+rtve                    rtve.es              Yes   Yes   Streams may be geo-restricted to Spain.
 rtvs                    rtvs.sk              Yes   No    Streams may be geo-restricted to Slovakia.
 ruv                     ruv.is               Yes   Yes   Streams may be geo-restricted to Iceland.
 sbscokr                 play.sbs.co.kr       Yes   No    Streams may be geo-restricted to South Korea.
diff --git a/src/streamlink/plugins/raiplay.py b/src/streamlink/plugins/raiplay.py
index 63377e93..15c9713e 100644
--- a/src/streamlink/plugins/raiplay.py
+++ b/src/streamlink/plugins/raiplay.py
@@ -10,6 +10,13 @@ from streamlink.utils import parse_json
 log = logging.getLogger(__name__)
 
 
+class RaiPlayHLSStream(HLSStream):
+    @classmethod
+    def _get_variant_playlist(cls, res):
+        res.encoding = "UTF-8"
+        return super()._get_variant_playlist(res)
+
+
 @pluginmatcher(re.compile(
     r"https?://(?:www\.)?raiplay\.it/dirette/(\w+)/?"
 ))
@@ -43,7 +50,7 @@ class RaiPlay(Plugin):
             log.error("Geo-restricted content")
             return
 
-        yield from HLSStream.parse_variant_playlist(self.session, stream_url).items()
+        yield from RaiPlayHLSStream.parse_variant_playlist(self.session, stream_url).items()
 
 
 __plugin__ = RaiPlay
diff --git a/src/streamlink/plugins/rtve.py b/src/streamlink/plugins/rtve.py
index aad1be02..e30b7782 100644
--- a/src/streamlink/plugins/rtve.py
+++ b/src/streamlink/plugins/rtve.py
@@ -2,12 +2,12 @@ import base64
 import logging
 import re
 from functools import partial
+from urllib.parse import urlparse
 
 from Crypto.Cipher import Blowfish
 
 from streamlink.plugin import Plugin, PluginArgument, PluginArguments, pluginmatcher
-from streamlink.plugin.api import useragents, validate
-from streamlink.plugin.api.utils import itertags
+from streamlink.plugin.api import validate
 from streamlink.stream import HLSStream, HTTPStream
 from streamlink.stream.ffmpegmux import MuxedStream
 from streamlink.utils import parse_xml
@@ -16,7 +16,7 @@ log = logging.getLogger(__name__)
 
 
 class ZTNRClient:
-    base_url = "http://ztnr.rtve.es/ztnr/res/"
+    base_url = "https://ztnr.rtve.es/ztnr/res/"
     block_size = 16
 
     def __init__(self, key, session):
@@ -51,9 +51,10 @@ class ZTNRClient:
 
 
 @pluginmatcher(re.compile(
-    r"https?://(?:www\.)?rtve\.es/(?:directo|infantil|noticias|television|deportes|alacarta|drmn)/.*?/?"
+    r"https?://(?:www\.)?rtve\.es/play/videos/.+"
 ))
 class Rtve(Plugin):
+    _re_idAsset = re.compile(r"\"idAsset\":\"(\d+)\"")
     secret_key = base64.b64decode("eWVMJmRhRDM=")
     cdn_schema = validate.Schema(
         validate.transform(partial(parse_xml, invalid_char_entities=True)),
@@ -69,7 +70,7 @@ class Rtve(Plugin):
             })
         ]
     )
-    subtitles_api = "http://www.rtve.es/api/videos/{id}/subtitulos.json"
+    subtitles_api = "https://www.rtve.es/api/videos/{id}/subtitulos.json"
     subtitles_schema = validate.Schema({
         "page": {
             "items": [{
@@ -80,7 +81,7 @@ class Rtve(Plugin):
     },
         validate.get("page"),
         validate.get("items"))
-    video_api = "http://www.rtve.es/api/videos/{id}.json"
+    video_api = "https://www.rtve.es/api/videos/{id}.json"
     video_schema = validate.Schema({
         "page": {
             "items": [{
@@ -100,18 +101,9 @@ class Rtve(Plugin):
     )
 
     def __init__(self, url):
-        Plugin.__init__(self, url)
-        self.session.http.headers = {"User-Agent": useragents.SAFARI_8}
+        super().__init__(url)
         self.zclient = ZTNRClient(self.secret_key, self.session)
 
-    def _get_content_id(self):
-        res = self.session.http.get(self.url)
-        for div in itertags(res.text, "div"):
-            if div.attributes.get("data-id"):
-                return int(div.attributes.get("data-id"))
-        else:
-            log.error("Failed to get content_id")
-
     def _get_subtitles(self, content_id):
         res = self.session.http.get(self.subtitles_api.format(id=content_id))
         return self.session.http.json(res, schema=self.subtitles_schema)
@@ -126,27 +118,38 @@ class Rtve(Plugin):
         return qmap
 
     def _get_streams(self):
-        streams = []
-        content_id = self._get_content_id()
-        if content_id:
+        res = self.session.http.get(self.url)
+        m = self._re_idAsset.search(res.text)
+        if m:
+            content_id = m.group(1)
             log.debug(f"Found content with id: {content_id}")
             stream_data = self.zclient.get_cdn_list(content_id, schema=self.cdn_schema)
             quality_map = None
 
+            streams = []
             for stream in stream_data:
+                # only use one stream
+                _one_m3u8 = False
+                _one_mp4 = False
                 for url in stream["urls"]:
-                    if ".m3u8" in url:
+                    p_url = urlparse(url)
+                    if p_url.path.endswith(".m3u8"):
+                        if _one_m3u8:
+                            continue
                         try:
                             streams.extend(HLSStream.parse_variant_playlist(self.session, url).items())
+                            _one_m3u8 = True
                         except OSError as err:
                             log.error(str(err))
-                    elif ((url.endswith("mp4") or url.endswith("mov") or url.endswith("avi"))
-                          and self.session.http.head(url, raise_for_status=False).status_code == 200):
+                    elif p_url.path.endswith(".mp4"):
+                        if _one_mp4:
+                            continue
                         if quality_map is None:  # only make the request when it is necessary
                             quality_map = self._get_quality_map(content_id)
                         # rename the HTTP sources to match the HLS sources
                         quality = quality_map.get(stream["quality"], stream["quality"])
                         streams.append((quality, HTTPStream(self.session, url)))
+                        _one_mp4 = True
 
             subtitles = None
             if self.get_option("mux_subtitles"):
diff --git a/src/streamlink/stream/hls.py b/src/streamlink/stream/hls.py
index 20c75334..3d1d2271 100644
--- a/src/streamlink/stream/hls.py
+++ b/src/streamlink/stream/hls.py
@@ -318,7 +318,11 @@ class HLSStreamWorker(SegmentedStreamWorker):
         return default
 
     def iter_segments(self):
-        self.reload_playlist()
+        try:
+            self.reload_playlist()
+        except StreamError as err:
+            log.error(f'{err}')
+            return
 
         if self.playlist_end is None:
             if self.duration_offset_start > 0:


Test patch:
diff --git a/tests/plugins/test_rtve.py b/tests/plugins/test_rtve.py
index 08528588..dd30b034 100644
--- a/tests/plugins/test_rtve.py
+++ b/tests/plugins/test_rtve.py
@@ -6,13 +6,16 @@ class TestPluginCanHandleUrlRtve(PluginCanHandleUrl):
     __plugin__ = Rtve
 
     should_match = [
+        'https://www.rtve.es/play/videos/directo/la-1/',
+        'https://www.rtve.es/play/videos/directo/canales-lineales/24h/',
+        'https://www.rtve.es/play/videos/rebelion-en-el-reino-salvaje/mata-reyes/5803959/',
+    ]
+
+    should_not_match = [
+        'https://www.rtve.es',
         'http://www.rtve.es/directo/la-1',
         'http://www.rtve.es/directo/la-2/',
         'http://www.rtve.es/directo/teledeporte/',
         'http://www.rtve.es/directo/canal-24h/',
         'http://www.rtve.es/infantil/directo/',
     ]
-
-    should_not_match = [
-        'https://www.rtve.es',
-    ]