]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/err.py
[ie/ERRJupiter] Add extractor (#8549)
[yt-dlp.git] / yt_dlp / extractor / err.py
1 from .common import InfoExtractor
2 from ..utils import (
3 clean_html,
4 int_or_none,
5 str_or_none,
6 url_or_none,
7 )
8 from ..utils.traversal import traverse_obj
9
10
11 class ERRJupiterIE(InfoExtractor):
12 _VALID_URL = r'https?://jupiter(?:pluss)?\.err\.ee/(?P<id>\d+)'
13 _TESTS = [{
14 'note': 'Jupiter: Movie: siin-me-oleme',
15 'url': 'https://jupiter.err.ee/1211107/siin-me-oleme',
16 'md5': '9b45d1682a98853acaa1e1b0c791f425',
17 'info_dict': {
18 'id': '1211107',
19 'ext': 'mp4',
20 'title': 'Siin me oleme!',
21 'alt_title': '',
22 'description': 'md5:1825b795f5f7584241aeb59e5bbb4f70',
23 'release_date': '20231226',
24 'upload_date': '20201217',
25 'modified_date': '20201217',
26 'release_timestamp': 1703577600,
27 'timestamp': 1608210000,
28 'modified_timestamp': 1608220800,
29 'release_year': 1978,
30 },
31 }, {
32 'note': 'Jupiter: Series: Impulss',
33 'url': 'https://jupiter.err.ee/1609145945/impulss',
34 'md5': 'a378486df07ed1ba74e46cc861886243',
35 'info_dict': {
36 'id': '1609145945',
37 'ext': 'mp4',
38 'title': 'Impulss',
39 'alt_title': 'Loteriipilet hooldekodusse',
40 'description': 'md5:fa8a2ed0cdccb130211513443ee4d571',
41 'release_date': '20231107',
42 'upload_date': '20231026',
43 'modified_date': '20231118',
44 'release_timestamp': 1699380000,
45 'timestamp': 1698327601,
46 'modified_timestamp': 1700311802,
47 'series': 'Impulss',
48 'season': 'Season 1',
49 'season_number': 1,
50 'episode': 'Loteriipilet hooldekodusse',
51 'episode_number': 6,
52 'series_id': '1609108187',
53 'release_year': 2023,
54 'episode_id': '1609145945',
55 },
56 }, {
57 'note': 'Jupiter: Radio Show: mnemoturniir episode',
58 'url': 'https://jupiter.err.ee/1037919/mnemoturniir',
59 'md5': 'f1eb95fe66f9620ff84e81bbac37076a',
60 'info_dict': {
61 'id': '1037919',
62 'ext': 'm4a',
63 'title': 'Mnemoturniir',
64 'alt_title': '',
65 'description': 'md5:626db52394e7583c26ab74d6a34d9982',
66 'release_date': '20240121',
67 'upload_date': '20240108',
68 'modified_date': '20240121',
69 'release_timestamp': 1705827900,
70 'timestamp': 1704675602,
71 'modified_timestamp': 1705827601,
72 'series': 'Mnemoturniir',
73 'season': 'Season 0',
74 'season_number': 0,
75 'episode': 'Episode 0',
76 'episode_number': 0,
77 'series_id': '1037919',
78 'release_year': 2024,
79 'episode_id': '1609215101',
80 },
81 }, {
82 'note': 'Jupiter+: Clip: bolee-zelenyj-tallinn',
83 'url': 'https://jupiterpluss.err.ee/1609180445/bolee-zelenyj-tallinn',
84 'md5': '1b812270c4daf6ce51c06bfeaf33ed95',
85 'info_dict': {
86 'id': '1609180445',
87 'ext': 'mp4',
88 'title': 'Более зеленый Таллинн',
89 'alt_title': '',
90 'description': 'md5:fd34d9bf939c28c4a725b19a7f0d6320',
91 'release_date': '20231224',
92 'upload_date': '20231130',
93 'modified_date': '20231207',
94 'release_timestamp': 1703423400,
95 'timestamp': 1701338400,
96 'modified_timestamp': 1701967200,
97 'release_year': 2023,
98 },
99 }, {
100 'note': 'Jupiter+: Series: The Sniffer',
101 'url': 'https://jupiterpluss.err.ee/1608311387/njuhach',
102 'md5': '2abdeb7131ce551bce49e8d0cea08536',
103 'info_dict': {
104 'id': '1608311387',
105 'ext': 'mp4',
106 'title': 'Нюхач',
107 'alt_title': '',
108 'description': 'md5:8c5c7d8f32ec6e54cd498c9e59ca83bc',
109 'release_date': '20230601',
110 'upload_date': '20210818',
111 'modified_date': '20210903',
112 'release_timestamp': 1685633400,
113 'timestamp': 1629318000,
114 'modified_timestamp': 1630686000,
115 'release_year': 2013,
116 'episode': 'Episode 1',
117 'episode_id': '1608311390',
118 'episode_number': 1,
119 'season': 'Season 1',
120 'season_number': 1,
121 'series': 'Нюхач',
122 'series_id': '1608311387',
123 },
124 }, {
125 'note': 'Jupiter+: Podcast: lesnye-istorii-aisty',
126 'url': 'https://jupiterpluss.err.ee/1608990335/lesnye-istorii-aisty',
127 'md5': '8b46d7e4510b254a14b7a52211b5bf96',
128 'info_dict': {
129 'id': '1608990335',
130 'ext': 'm4a',
131 'title': 'Лесные истории | Аисты',
132 'alt_title': '',
133 'description': 'md5:065e721623e271e7a63e6540d409ca6b',
134 'release_date': '20230609',
135 'upload_date': '20230527',
136 'modified_date': '20230608',
137 'release_timestamp': 1686308700,
138 'timestamp': 1685145600,
139 'modified_timestamp': 1686252600,
140 'release_year': 2023,
141 'episode': 'Episode 0',
142 'episode_id': '1608990335',
143 'episode_number': 0,
144 'season': 'Season 0',
145 'season_number': 0,
146 'series': 'Лесные истории | Аисты',
147 'series_id': '1037497',
148 },
149 }]
150
151 def _real_extract(self, url):
152 video_id = self._match_id(url)
153 data = self._download_json(
154 'https://services.err.ee/api/v2/vodContent/getContentPageData', video_id,
155 query={'contentId': video_id})['data']['mainContent']
156
157 media_data = traverse_obj(data, ('medias', ..., {dict}), get_all=False)
158 if traverse_obj(media_data, ('restrictions', 'drm', {bool})):
159 self.report_drm(video_id)
160
161 formats, subtitles = [], {}
162 for format_url in set(traverse_obj(media_data, ('src', ('hls', 'hls2', 'hlsNew'), {url_or_none}))):
163 fmts, subs = self._extract_m3u8_formats_and_subtitles(
164 format_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
165 formats.extend(fmts)
166 self._merge_subtitles(subs, target=subtitles)
167 for format_url in set(traverse_obj(media_data, ('src', ('dash', 'dashNew'), {url_or_none}))):
168 fmts, subs = self._extract_mpd_formats_and_subtitles(
169 format_url, video_id, mpd_id='dash', fatal=False)
170 formats.extend(fmts)
171 self._merge_subtitles(subs, target=subtitles)
172 if format_url := traverse_obj(media_data, ('src', 'file', {url_or_none})):
173 formats.append({
174 'url': format_url,
175 'format_id': 'http',
176 })
177
178 return {
179 'id': video_id,
180 'formats': formats,
181 'subtitles': subtitles,
182 **traverse_obj(data, {
183 'title': ('heading', {str}),
184 'alt_title': ('subHeading', {str}),
185 'description': (('lead', 'body'), {clean_html}, {lambda x: x or None}),
186 'timestamp': ('created', {int_or_none}),
187 'modified_timestamp': ('updated', {int_or_none}),
188 'release_timestamp': (('scheduleStart', 'publicStart'), {int_or_none}),
189 'release_year': ('year', {int_or_none}),
190 }, get_all=False),
191 **(traverse_obj(data, {
192 'series': ('heading', {str}),
193 'series_id': ('rootContentId', {str_or_none}),
194 'episode': ('subHeading', {str}),
195 'season_number': ('season', {int_or_none}),
196 'episode_number': ('episode', {int_or_none}),
197 'episode_id': ('id', {str_or_none}),
198 }) if data.get('type') == 'episode' else {}),
199 }