]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/orf.py
[extractor/FranceCulture] Fix extractor (#3874)
[yt-dlp.git] / yt_dlp / extractor / orf.py
CommitLineData
b695e3f9 1import functools
5d73273f 2import re
54543467
JMF
3
4from .common import InfoExtractor
5from ..utils import (
aca2fd22 6 clean_html,
0732a905
PH
7 determine_ext,
8 float_or_none,
5d73273f 9 HEADRequest,
b695e3f9 10 InAdvancePagedList,
14137b57 11 int_or_none,
34921b43 12 join_nonempty,
0732a905 13 orderedSet,
14137b57 14 remove_end,
b695e3f9 15 smuggle_url,
aca2fd22 16 str_or_none,
0732a905 17 strip_jsonp,
73c801d6 18 unescapeHTML,
0732a905 19 unified_strdate,
b695e3f9 20 unsmuggle_url,
4b6aca17 21 url_or_none,
54543467
JMF
22)
23
5d73273f 24
eb368012
S
25class ORFTVthekIE(InfoExtractor):
26 IE_NAME = 'orf:tvthek'
27 IE_DESC = 'ORF TVthek'
b695e3f9 28 _VALID_URL = r'(?P<url>https?://tvthek\.orf\.at/(?:(?:[^/]+/){2}){1,2}(?P<id>\d+))(/[^/]+/(?P<vid>\d+))?(?:$|[?#])'
5d73273f 29
13d27a42 30 _TESTS = [{
b695e3f9 31 'url': 'https://tvthek.orf.at/profile/ZIB-2/1211/ZIB-2/14121079',
32 'info_dict': {
33 'id': '14121079',
34 },
35 'playlist_count': 11,
36 'params': {'noplaylist': True}
37 }, {
38 'url': 'https://tvthek.orf.at/profile/ZIB-2/1211/ZIB-2/14121079/Umfrage-Welches-Tier-ist-Sebastian-Kurz/15083150',
39 'info_dict': {
40 'id': '14121079',
41 },
42 'playlist_count': 1,
43 'params': {'playlist_items': '5'}
44 }, {
45 'url': 'https://tvthek.orf.at/profile/ZIB-2/1211/ZIB-2/14121079/Umfrage-Welches-Tier-ist-Sebastian-Kurz/15083150',
46 'info_dict': {
47 'id': '14121079',
48 'playlist_count': 1
49 },
50 'playlist': [{
51 'info_dict': {
52 'id': '15083150',
53 'ext': 'mp4',
54 'description': 'md5:7be1c485425f5f255a5e4e4815e77d04',
55 'thumbnail': 'https://api-tvthek.orf.at/uploads/media/segments/0130/59/824271ea35cd8931a0fb08ab316a5b0a1562342c.jpeg',
56 'title': 'Umfrage: Welches Tier ist Sebastian Kurz?',
57 }
58 }],
59 'playlist_count': 1,
60 'params': {'noplaylist': True, 'skip_download': 'm3u8'}
61 }, {
a6620ac2
PH
62 'url': 'http://tvthek.orf.at/program/Aufgetischt/2745173/Aufgetischt-Mit-der-Steirischen-Tafelrunde/8891389',
63 'playlist': [{
64 'md5': '2942210346ed779588f428a92db88712',
65 'info_dict': {
66 'id': '8896777',
67 'ext': 'mp4',
68 'title': 'Aufgetischt: Mit der Steirischen Tafelrunde',
69 'description': 'md5:c1272f0245537812d4e36419c207b67d',
70 'duration': 2668,
71 'upload_date': '20141208',
72 },
73 }],
13d27a42
PH
74 'skip': 'Blocked outside of Austria / Germany',
75 }, {
76 'url': 'http://tvthek.orf.at/topic/Im-Wandel-der-Zeit/8002126/Best-of-Ingrid-Thurnher/7982256',
7b0d333a
NP
77 'info_dict': {
78 'id': '7982259',
79 'ext': 'mp4',
80 'title': 'Best of Ingrid Thurnher',
81 'upload_date': '20140527',
82 'description': 'Viele Jahre war Ingrid Thurnher das "Gesicht" der ZIB 2. Vor ihrem Wechsel zur ZIB 2 im Jahr 1995 moderierte sie unter anderem "Land und Leute", "Österreich-Bild" und "Niederösterreich heute".',
83 },
84 'params': {
85 'skip_download': True, # rtsp downloads
86 },
7512aa98 87 'skip': 'Blocked outside of Austria / Germany',
73c801d6
S
88 }, {
89 'url': 'http://tvthek.orf.at/topic/Fluechtlingskrise/10463081/Heimat-Fremde-Heimat/13879132/Senioren-betreuen-Migrantenkinder/13879141',
7512aa98 90 'only_matching': True,
73c801d6
S
91 }, {
92 'url': 'http://tvthek.orf.at/profile/Universum/35429',
7512aa98 93 'only_matching': True,
13d27a42 94 }]
54543467 95
b695e3f9 96 def _pagefunc(self, url, data_jsb, n, *, image=None):
97 sd = data_jsb[n]
98 video_id, title = str(sd['id']), sd['title']
99 formats = []
100 for fd in sd['sources']:
101 src = url_or_none(fd.get('src'))
102 if not src:
103 continue
104 format_id = join_nonempty('delivery', 'quality', 'quality_string', from_dict=fd)
105 ext = determine_ext(src)
106 if ext == 'm3u8':
107 m3u8_formats = self._extract_m3u8_formats(
108 src, video_id, 'mp4', m3u8_id=format_id, fatal=False, note=f'Downloading {format_id} m3u8 manifest')
109 if any('/geoprotection' in f['url'] for f in m3u8_formats):
110 self.raise_geo_restricted()
111 formats.extend(m3u8_formats)
112 elif ext == 'f4m':
113 formats.extend(self._extract_f4m_formats(
114 src, video_id, f4m_id=format_id, fatal=False))
115 elif ext == 'mpd':
116 formats.extend(self._extract_mpd_formats(
117 src, video_id, mpd_id=format_id, fatal=False, note=f'Downloading {format_id} mpd manifest'))
118 else:
119 formats.append({
120 'format_id': format_id,
121 'url': src,
122 'protocol': fd.get('protocol'),
123 })
124
125 # Check for geoblocking.
126 # There is a property is_geoprotection, but that's always false
127 geo_str = sd.get('geoprotection_string')
128 http_url = next(
129 (f['url'] for f in formats if re.match(r'^https?://.*\.mp4$', f['url'])),
130 None) if geo_str else None
131 if http_url:
132 self._request_webpage(
133 HEADRequest(http_url), video_id, fatal=False, note='Testing for geoblocking',
134 errnote=f'This video seems to be blocked outside of {geo_str}. You may want to try the streaming-* formats')
135
136 self._sort_formats(formats)
137
138 subtitles = {}
139 for sub in sd.get('subtitles', []):
140 sub_src = sub.get('src')
141 if not sub_src:
142 continue
143 subtitles.setdefault(sub.get('lang', 'de-AT'), []).append({
144 'url': sub_src,
145 })
146
147 upload_date = unified_strdate(sd.get('created_date'))
148
149 thumbnails = []
150 preview = sd.get('preview_image_url')
151 if preview:
152 thumbnails.append({
153 'id': 'preview',
154 'url': preview,
155 'preference': 0,
156 })
157 image = sd.get('image_full_url') or image
158 if image:
159 thumbnails.append({
160 'id': 'full',
161 'url': image,
162 'preference': 1,
163 })
164
165 yield {
166 'id': video_id,
167 'title': title,
168 'webpage_url': smuggle_url(f'{url}/part/{video_id}', {'force_noplaylist': True}),
169 'formats': formats,
170 'subtitles': subtitles,
171 'description': sd.get('description'),
172 'duration': int_or_none(sd.get('duration_in_seconds')),
173 'upload_date': upload_date,
174 'thumbnails': thumbnails,
175 }
176
54543467 177 def _real_extract(self, url):
b695e3f9 178 url, smuggled_data = unsmuggle_url(url)
179 playlist_id, video_id, base_url = self._match_valid_url(url).group('id', 'vid', 'url')
54543467
JMF
180 webpage = self._download_webpage(url, playlist_id)
181
73c801d6
S
182 data_jsb = self._parse_json(
183 self._search_regex(
184 r'<div[^>]+class=(["\']).*?VideoPlaylist.*?\1[^>]+data-jsb=(["\'])(?P<json>.+?)\2',
185 webpage, 'playlist', group='json'),
186 playlist_id, transform_source=unescapeHTML)['playlist']['videos']
5d73273f 187
b695e3f9 188 if not self._yes_playlist(playlist_id, video_id, smuggled_data):
189 data_jsb = [sd for sd in data_jsb if str(sd.get('id')) == video_id]
46358f64 190
b695e3f9 191 playlist_count = len(data_jsb)
192 image = self._og_search_thumbnail(webpage) if playlist_count == 1 else None
5d73273f 193
b695e3f9 194 page_func = functools.partial(self._pagefunc, base_url, data_jsb, image=image)
5d73273f
PH
195 return {
196 '_type': 'playlist',
b695e3f9 197 'entries': InAdvancePagedList(page_func, playlist_count, 1),
5d73273f
PH
198 'id': playlist_id,
199 }
eb368012
S
200
201
efe93167 202class ORFRadioIE(InfoExtractor):
eb368012 203 def _real_extract(self, url):
5ad28e7f 204 mobj = self._match_valid_url(url)
eb368012
S
205 show_date = mobj.group('date')
206 show_id = mobj.group('show')
207
208 data = self._download_json(
aca2fd22 209 'http://audioapi.orf.at/%s/api/json/current/broadcast/%s/%s'
13283058 210 % (self._API_STATION, show_id, show_date), show_id)
aca2fd22
S
211
212 entries = []
213 for info in data['streams']:
214 loop_stream_id = str_or_none(info.get('loopStreamId'))
215 if not loop_stream_id:
216 continue
217 title = str_or_none(data.get('title'))
218 if not title:
219 continue
220 start = int_or_none(info.get('start'), scale=1000)
221 end = int_or_none(info.get('end'), scale=1000)
222 duration = end - start if end and start else None
223 entries.append({
224 'id': loop_stream_id.replace('.mp3', ''),
b73612a2 225 'url': 'https://loopstream01.apa.at/?channel=%s&id=%s' % (self._LOOP_STATION, loop_stream_id),
eb368012 226 'title': title,
aca2fd22
S
227 'description': clean_html(data.get('subtitle')),
228 'duration': duration,
229 'timestamp': start,
0146c6cd 230 'ext': 'mp3',
aca2fd22
S
231 'series': data.get('programTitle'),
232 })
eb368012
S
233
234 return {
235 '_type': 'playlist',
236 'id': show_id,
aca2fd22
S
237 'title': data.get('title'),
238 'description': clean_html(data.get('subtitle')),
239 'entries': entries,
5f6a1245 240 }
14137b57
S
241
242
efe93167 243class ORFFM4IE(ORFRadioIE):
244 IE_NAME = 'orf:fm4'
245 IE_DESC = 'radio FM4'
9ba179c1 246 _VALID_URL = r'https?://(?P<station>fm4)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>4\w+)'
13283058 247 _API_STATION = 'fm4'
248 _LOOP_STATION = 'fm4'
efe93167 249
1339ecb2 250 _TEST = {
9ba179c1 251 'url': 'http://fm4.orf.at/player/20170107/4CC',
1339ecb2
S
252 'md5': '2b0be47375432a7ef104453432a19212',
253 'info_dict': {
254 'id': '2017-01-07_2100_tl_54_7DaysSat18_31295',
255 'ext': 'mp3',
256 'title': 'Solid Steel Radioshow',
257 'description': 'Die Mixshow von Coldcut und Ninja Tune.',
258 'duration': 3599,
259 'timestamp': 1483819257,
260 'upload_date': '20170107',
261 },
aca2fd22
S
262 'skip': 'Shows from ORF radios are only available for 7 days.',
263 'only_matching': True,
1339ecb2 264 }
efe93167 265
266
13283058 267class ORFNOEIE(ORFRadioIE):
268 IE_NAME = 'orf:noe'
269 IE_DESC = 'Radio Niederösterreich'
270 _VALID_URL = r'https?://(?P<station>noe)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
271 _API_STATION = 'noe'
272 _LOOP_STATION = 'oe2n'
273
274 _TEST = {
275 'url': 'https://noe.orf.at/player/20200423/NGM',
276 'only_matching': True,
277 }
278
279
280class ORFWIEIE(ORFRadioIE):
281 IE_NAME = 'orf:wien'
282 IE_DESC = 'Radio Wien'
283 _VALID_URL = r'https?://(?P<station>wien)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
284 _API_STATION = 'wie'
285 _LOOP_STATION = 'oe2w'
286
287 _TEST = {
288 'url': 'https://wien.orf.at/player/20200423/WGUM',
289 'only_matching': True,
290 }
291
292
293class ORFBGLIE(ORFRadioIE):
294 IE_NAME = 'orf:burgenland'
295 IE_DESC = 'Radio Burgenland'
296 _VALID_URL = r'https?://(?P<station>burgenland)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
297 _API_STATION = 'bgl'
298 _LOOP_STATION = 'oe2b'
299
300 _TEST = {
301 'url': 'https://burgenland.orf.at/player/20200423/BGM',
302 'only_matching': True,
303 }
304
305
306class ORFOOEIE(ORFRadioIE):
307 IE_NAME = 'orf:oberoesterreich'
308 IE_DESC = 'Radio Oberösterreich'
309 _VALID_URL = r'https?://(?P<station>ooe)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
310 _API_STATION = 'ooe'
311 _LOOP_STATION = 'oe2o'
312
313 _TEST = {
314 'url': 'https://ooe.orf.at/player/20200423/OGMO',
315 'only_matching': True,
316 }
317
318
319class ORFSTMIE(ORFRadioIE):
320 IE_NAME = 'orf:steiermark'
321 IE_DESC = 'Radio Steiermark'
322 _VALID_URL = r'https?://(?P<station>steiermark)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
323 _API_STATION = 'stm'
324 _LOOP_STATION = 'oe2st'
325
326 _TEST = {
327 'url': 'https://steiermark.orf.at/player/20200423/STGMS',
328 'only_matching': True,
329 }
330
331
332class ORFKTNIE(ORFRadioIE):
333 IE_NAME = 'orf:kaernten'
334 IE_DESC = 'Radio Kärnten'
335 _VALID_URL = r'https?://(?P<station>kaernten)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
336 _API_STATION = 'ktn'
337 _LOOP_STATION = 'oe2k'
338
339 _TEST = {
340 'url': 'https://kaernten.orf.at/player/20200423/KGUMO',
341 'only_matching': True,
342 }
343
344
345class ORFSBGIE(ORFRadioIE):
346 IE_NAME = 'orf:salzburg'
347 IE_DESC = 'Radio Salzburg'
348 _VALID_URL = r'https?://(?P<station>salzburg)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
349 _API_STATION = 'sbg'
350 _LOOP_STATION = 'oe2s'
351
352 _TEST = {
353 'url': 'https://salzburg.orf.at/player/20200423/SGUM',
354 'only_matching': True,
355 }
356
357
358class ORFTIRIE(ORFRadioIE):
359 IE_NAME = 'orf:tirol'
360 IE_DESC = 'Radio Tirol'
361 _VALID_URL = r'https?://(?P<station>tirol)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
362 _API_STATION = 'tir'
363 _LOOP_STATION = 'oe2t'
364
365 _TEST = {
366 'url': 'https://tirol.orf.at/player/20200423/TGUMO',
367 'only_matching': True,
368 }
369
370
371class ORFVBGIE(ORFRadioIE):
372 IE_NAME = 'orf:vorarlberg'
373 IE_DESC = 'Radio Vorarlberg'
374 _VALID_URL = r'https?://(?P<station>vorarlberg)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
375 _API_STATION = 'vbg'
376 _LOOP_STATION = 'oe2v'
377
378 _TEST = {
379 'url': 'https://vorarlberg.orf.at/player/20200423/VGUM',
380 'only_matching': True,
381 }
382
383
384class ORFOE3IE(ORFRadioIE):
385 IE_NAME = 'orf:oe3'
386 IE_DESC = 'Radio Österreich 3'
387 _VALID_URL = r'https?://(?P<station>oe3)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
388 _API_STATION = 'oe3'
389 _LOOP_STATION = 'oe3'
390
391 _TEST = {
392 'url': 'https://oe3.orf.at/player/20200424/3WEK',
393 'only_matching': True,
394 }
395
396
efe93167 397class ORFOE1IE(ORFRadioIE):
398 IE_NAME = 'orf:oe1'
399 IE_DESC = 'Radio Österreich 1'
1339ecb2 400 _VALID_URL = r'https?://(?P<station>oe1)\.orf\.at/player/(?P<date>[0-9]+)/(?P<show>\w+)'
13283058 401 _API_STATION = 'oe1'
402 _LOOP_STATION = 'oe1'
efe93167 403
1339ecb2
S
404 _TEST = {
405 'url': 'http://oe1.orf.at/player/20170108/456544',
406 'md5': '34d8a6e67ea888293741c86a099b745b',
407 'info_dict': {
408 'id': '2017-01-08_0759_tl_51_7DaysSun6_256141',
409 'ext': 'mp3',
410 'title': 'Morgenjournal',
411 'duration': 609,
412 'timestamp': 1483858796,
413 'upload_date': '20170108',
414 },
415 'skip': 'Shows from ORF radios are only available for 7 days.'
416 }
efe93167 417
418
14137b57
S
419class ORFIPTVIE(InfoExtractor):
420 IE_NAME = 'orf:iptv'
421 IE_DESC = 'iptv.ORF.at'
5886b38d 422 _VALID_URL = r'https?://iptv\.orf\.at/(?:#/)?stories/(?P<id>\d+)'
14137b57
S
423
424 _TEST = {
529d26c3
S
425 'url': 'http://iptv.orf.at/stories/2275236/',
426 'md5': 'c8b22af4718a4b4af58342529453e3e5',
14137b57 427 'info_dict': {
529d26c3 428 'id': '350612',
14137b57 429 'ext': 'flv',
529d26c3
S
430 'title': 'Weitere Evakuierungen um Vulkan Calbuco',
431 'description': 'md5:d689c959bdbcf04efeddedbf2299d633',
432 'duration': 68.197,
ec85ded8 433 'thumbnail': r're:^https?://.*\.jpg$',
529d26c3 434 'upload_date': '20150425',
14137b57
S
435 },
436 }
437
438 def _real_extract(self, url):
439 story_id = self._match_id(url)
440
441 webpage = self._download_webpage(
442 'http://iptv.orf.at/stories/%s' % story_id, story_id)
443
444 video_id = self._search_regex(
445 r'data-video(?:id)?="(\d+)"', webpage, 'video id')
446
447 data = self._download_json(
448 'http://bits.orf.at/filehandler/static-api/json/current/data.json?file=%s' % video_id,
449 video_id)[0]
450
451 duration = float_or_none(data['duration'], 1000)
452
453 video = data['sources']['default']
454 load_balancer_url = video['loadBalancerUrl']
455 abr = int_or_none(video.get('audioBitrate'))
456 vbr = int_or_none(video.get('bitrate'))
457 fps = int_or_none(video.get('videoFps'))
458 width = int_or_none(video.get('videoWidth'))
459 height = int_or_none(video.get('videoHeight'))
460 thumbnail = video.get('preview')
461
462 rendition = self._download_json(
463 load_balancer_url, video_id, transform_source=strip_jsonp)
464
465 f = {
466 'abr': abr,
467 'vbr': vbr,
468 'fps': fps,
469 'width': width,
470 'height': height,
471 }
472
473 formats = []
474 for format_id, format_url in rendition['redirect'].items():
475 if format_id == 'rtmp':
476 ff = f.copy()
477 ff.update({
478 'url': format_url,
479 'format_id': format_id,
480 })
481 formats.append(ff)
482 elif determine_ext(format_url) == 'f4m':
483 formats.extend(self._extract_f4m_formats(
484 format_url, video_id, f4m_id=format_id))
485 elif determine_ext(format_url) == 'm3u8':
486 formats.extend(self._extract_m3u8_formats(
487 format_url, video_id, 'mp4', m3u8_id=format_id))
488 else:
489 continue
490 self._sort_formats(formats)
491
492 title = remove_end(self._og_search_title(webpage), ' - iptv.ORF.at')
493 description = self._og_search_description(webpage)
494 upload_date = unified_strdate(self._html_search_meta(
495 'dc.date', webpage, 'upload date'))
496
497 return {
498 'id': video_id,
499 'title': title,
500 'description': description,
501 'duration': duration,
502 'thumbnail': thumbnail,
503 'upload_date': upload_date,
504 'formats': formats,
505 }
0732a905
PH
506
507
508class ORFFM4StoryIE(InfoExtractor):
509 IE_NAME = 'orf:fm4:story'
510 IE_DESC = 'fm4.orf.at stories'
511 _VALID_URL = r'https?://fm4\.orf\.at/stories/(?P<id>\d+)'
512
513 _TEST = {
514 'url': 'http://fm4.orf.at/stories/2865738/',
515 'playlist': [{
516 'md5': 'e1c2c706c45c7b34cf478bbf409907ca',
517 'info_dict': {
518 'id': '547792',
519 'ext': 'flv',
520 'title': 'Manu Delago und Inner Tongue live',
521 'description': 'Manu Delago und Inner Tongue haben bei der FM4 Soundpark Session live alles gegeben. Hier gibt es Fotos und die gesamte Session als Video.',
522 'duration': 1748.52,
523 'thumbnail': r're:^https?://.*\.jpg$',
524 'upload_date': '20170913',
525 },
526 }, {
527 'md5': 'c6dd2179731f86f4f55a7b49899d515f',
528 'info_dict': {
529 'id': '547798',
530 'ext': 'flv',
531 'title': 'Manu Delago und Inner Tongue live (2)',
532 'duration': 1504.08,
533 'thumbnail': r're:^https?://.*\.jpg$',
534 'upload_date': '20170913',
535 'description': 'Manu Delago und Inner Tongue haben bei der FM4 Soundpark Session live alles gegeben. Hier gibt es Fotos und die gesamte Session als Video.',
536 },
537 }],
538 }
539
540 def _real_extract(self, url):
541 story_id = self._match_id(url)
542 webpage = self._download_webpage(url, story_id)
543
544 entries = []
545 all_ids = orderedSet(re.findall(r'data-video(?:id)?="(\d+)"', webpage))
546 for idx, video_id in enumerate(all_ids):
547 data = self._download_json(
548 'http://bits.orf.at/filehandler/static-api/json/current/data.json?file=%s' % video_id,
549 video_id)[0]
550
551 duration = float_or_none(data['duration'], 1000)
552
553 video = data['sources']['q8c']
554 load_balancer_url = video['loadBalancerUrl']
555 abr = int_or_none(video.get('audioBitrate'))
556 vbr = int_or_none(video.get('bitrate'))
557 fps = int_or_none(video.get('videoFps'))
558 width = int_or_none(video.get('videoWidth'))
559 height = int_or_none(video.get('videoHeight'))
560 thumbnail = video.get('preview')
561
562 rendition = self._download_json(
563 load_balancer_url, video_id, transform_source=strip_jsonp)
564
565 f = {
566 'abr': abr,
567 'vbr': vbr,
568 'fps': fps,
569 'width': width,
570 'height': height,
571 }
572
573 formats = []
574 for format_id, format_url in rendition['redirect'].items():
575 if format_id == 'rtmp':
576 ff = f.copy()
577 ff.update({
578 'url': format_url,
579 'format_id': format_id,
580 })
581 formats.append(ff)
582 elif determine_ext(format_url) == 'f4m':
583 formats.extend(self._extract_f4m_formats(
584 format_url, video_id, f4m_id=format_id))
585 elif determine_ext(format_url) == 'm3u8':
586 formats.extend(self._extract_m3u8_formats(
587 format_url, video_id, 'mp4', m3u8_id=format_id))
588 else:
589 continue
590 self._sort_formats(formats)
591
592 title = remove_end(self._og_search_title(webpage), ' - fm4.ORF.at')
593 if idx >= 1:
594 # Titles are duplicates, make them unique
595 title += ' (' + str(idx + 1) + ')'
596 description = self._og_search_description(webpage)
597 upload_date = unified_strdate(self._html_search_meta(
598 'dc.date', webpage, 'upload date'))
599
600 entries.append({
601 'id': video_id,
602 'title': title,
603 'description': description,
604 'duration': duration,
605 'thumbnail': thumbnail,
606 'upload_date': upload_date,
607 'formats': formats,
608 })
609
610 return self.playlist_result(entries)