Skip to content

Commit cadd180

Browse files
committed
plugins.tv3cat: rewrite plugin, add missing VODs
1 parent 251fe08 commit cadd180

File tree

2 files changed

+99
-30
lines changed

2 files changed

+99
-30
lines changed

src/streamlink/plugins/tv3cat.py

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,49 +8,75 @@
88
import logging
99
import re
1010

11+
from streamlink.exceptions import NoStreamsError, PluginError
1112
from streamlink.plugin import Plugin, pluginmatcher
1213
from streamlink.plugin.api import validate
14+
from streamlink.stream.dash import DASHStream
1315
from streamlink.stream.hls import HLSStream
16+
from streamlink.stream.http import HTTPStream
1417

1518

1619
log = logging.getLogger(__name__)
1720

1821

19-
@pluginmatcher(re.compile(
20-
r"https?://(?:www\.)?ccma\.cat/tv3/directe/(?P<ident>.+?)/",
21-
))
22+
@pluginmatcher(
23+
name="live",
24+
pattern=re.compile(r"https://(?:www)?\.ccma\.cat/3cat/directes/(?P<ident>[^/?]+)"),
25+
)
26+
@pluginmatcher(
27+
name="vod",
28+
pattern=re.compile(r"https://(?:www)?\.ccma\.cat/3cat/[^/]+/video/(?P<ident>\d+)"),
29+
)
2230
class TV3Cat(Plugin):
23-
_URL_STREAM_INFO = "https://dinamics.ccma.cat/pvideo/media.jsp"
31+
_URL_API_GEO = "https://dinamics.ccma.cat/geo.json"
32+
_URL_API_MEDIA = "https://api-media.ccma.cat/pvideo/media.jsp"
2433

25-
_MAP_CHANNELS = {
26-
"tv3": "tvi",
34+
_MAP_CHANNEL_IDENTS = {
35+
"catalunya-radio": "cr",
36+
"catalunya-informacio": "ci",
37+
"catalunya-musica": "cm",
38+
"icat": "ic",
2739
}
2840

29-
def _get_streams(self):
30-
ident = self.match.group("ident")
41+
def _call_api_media(self, fmt, schema, params):
42+
geo = self.session.http.get(
43+
self._URL_API_GEO,
44+
schema=validate.Schema(
45+
validate.parse_json(),
46+
{"geo": str},
47+
validate.get("geo"),
48+
),
49+
)
50+
if not geo:
51+
raise PluginError("Missing 'geo' value")
3152

32-
schema_media = {
33-
"geo": str,
34-
"url": validate.url(path=validate.endswith(".m3u8")),
35-
}
53+
log.debug(f"{geo=}")
54+
schema = validate.all(
55+
{
56+
"geo": str,
57+
"format": fmt,
58+
"url": schema,
59+
},
60+
validate.union_get("geo", "url"),
61+
)
3662

37-
stream_infos = self.session.http.get(
38-
self._URL_STREAM_INFO,
63+
ident = self.match["ident"]
64+
streams = self.session.http.get(
65+
self._URL_API_MEDIA,
3966
params={
4067
"media": "video",
4168
"versio": "vast",
42-
"idint": self._MAP_CHANNELS.get(ident, ident),
43-
"profile": "pc",
44-
"desplacament": "0",
45-
"broadcast": "false",
69+
"idint": self._MAP_CHANNEL_IDENTS.get(ident, ident),
70+
"profile": "pc_3cat",
71+
**(params or {}),
4672
},
4773
schema=validate.Schema(
4874
validate.parse_json(),
4975
{
5076
"media": validate.any(
51-
[schema_media],
77+
[schema],
5278
validate.all(
53-
schema_media,
79+
schema,
5480
validate.transform(lambda item: [item]),
5581
),
5682
),
@@ -59,12 +85,41 @@ def _get_streams(self):
5985
),
6086
)
6187

62-
for stream in stream_infos:
63-
log.info(f"Accessing stream from region {stream['geo']}")
64-
try:
65-
return HLSStream.parse_variant_playlist(self.session, stream["url"], name_fmt="{pixels}_{bitrate}")
66-
except OSError:
67-
pass
88+
log.debug(f"{streams=}")
89+
for _geo, data in streams:
90+
if _geo == geo:
91+
return data
92+
93+
log.error("The content is geo-blocked")
94+
raise NoStreamsError
95+
96+
def _get_live(self):
97+
schema = validate.url(path=validate.endswith(".m3u8"))
98+
url = self._call_api_media("HLS", schema, {"desplacament": 0})
99+
return HLSStream.parse_variant_playlist(self.session, url)
100+
101+
def _get_vod(self):
102+
schema = [
103+
validate.all(
104+
{
105+
"label": str,
106+
"file": validate.url(),
107+
},
108+
validate.union_get("label", "file"),
109+
),
110+
]
111+
urls = self._call_api_media("MP4", schema, {"format": "dm"})
112+
for label, url in urls:
113+
if label == "DASH":
114+
yield from DASHStream.parse_manifest(self.session, url).items()
115+
else:
116+
yield label, HTTPStream(self.session, url)
117+
118+
def _get_streams(self):
119+
if self.matches["live"]:
120+
return self._get_live()
121+
else:
122+
return self._get_vod()
68123

69124

70125
__plugin__ = TV3Cat

tests/plugins/test_tv3cat.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,22 @@ class TestPluginCanHandleUrlTV3Cat(PluginCanHandleUrl):
66
__plugin__ = TV3Cat
77

88
should_match_groups = [
9-
("https://ccma.cat/tv3/directe/tv3/", {"ident": "tv3"}),
10-
("https://ccma.cat/tv3/directe/324/", {"ident": "324"}),
11-
("https://www.ccma.cat/tv3/directe/tv3/", {"ident": "tv3"}),
12-
("https://www.ccma.cat/tv3/directe/324/", {"ident": "324"}),
9+
(("live", "https://www.ccma.cat/3cat/directes/tv3/"), {"ident": "tv3"}),
10+
(("live", "https://www.ccma.cat/3cat/directes/324/"), {"ident": "324"}),
11+
(("live", "https://www.ccma.cat/3cat/directes/esport3/"), {"ident": "esport3"}),
12+
(("live", "https://www.ccma.cat/3cat/directes/sx3/"), {"ident": "sx3"}),
13+
(("live", "https://www.ccma.cat/3cat/directes/catalunya-radio/"), {"ident": "catalunya-radio"}),
14+
15+
(
16+
("vod", "https://www.ccma.cat/3cat/t1xc1-arribada/video/6260741/"),
17+
{"ident": "6260741"},
18+
),
19+
(
20+
("vod", "https://www.ccma.cat/3cat/merli-els-peripatetics-capitol-1/video/5549976/"),
21+
{"ident": "5549976"},
22+
),
23+
(
24+
("vod", "https://www.ccma.cat/3cat/buscant-la-sostenibilitat-i-la-tecnologia-del-futur/video/6268863/"),
25+
{"ident": "6268863"},
26+
),
1327
]

0 commit comments

Comments
 (0)