]> jfr.im git - yt-dlp.git/blame - youtube_dl/extractor/generic.py
[hbo] Add new extractor
[yt-dlp.git] / youtube_dl / extractor / generic.py
CommitLineData
cfe50f04
JMF
1# encoding: utf-8
2
79649588
PH
3from __future__ import unicode_literals
4
9b122384
PH
5import os
6import re
6c91a5a7 7import sys
9b122384
PH
8
9from .common import InfoExtractor
fc9713a1 10from .youtube import YoutubeIE
8c25f81b 11from ..compat import (
f7854627 12 compat_etree_fromstring,
1ddb9456 13 compat_urllib_parse_unquote,
a5caba1e 14 compat_urlparse,
f7300c5c 15 compat_xml_parse_error,
8c25f81b
PH
16)
17from ..utils import (
b759a0d4 18 determine_ext,
9b122384 19 ExtractorError,
c8e9a235 20 float_or_none,
aa94a6d3 21 HEADRequest,
61ca9a80 22 is_html,
ed2d6a19 23 orderedSet,
5c2266df 24 sanitized_Request,
9d4660ca
PH
25 smuggle_url,
26 unescapeHTML,
42393ce2 27 unified_strdate,
4d54ef20 28 unsmuggle_url,
416c7fcb 29 UnsupportedError,
42393ce2 30 url_basename,
76c73715 31 xpath_text,
9b122384 32)
ed126900 33from .brightcove import (
4fcaa4f4 34 BrightcoveLegacyIE,
5c17f0a6 35 BrightcoveNewIE,
ed126900 36)
a2edf2e7 37from .nbc import NBCSportsVPlayerIE
c0d0b01f 38from .ooyala import OoyalaIE
93d020dd 39from .rutv import RUTVIE
954c1d05 40from .tvc import TVCIE
d40a3b5b 41from .sportbox import SportBoxEmbedIE
cb3ac1c6 42from .smotri import SmotriIE
6dd94d3a 43from .myvi import MyviIE
1419fafd 44from .condenast import CondeNastIE
418c5cc3 45from .udn import UDNEmbedIE
2fe1b5bd 46from .senateisvp import SenateISVPIE
bab19a8e 47from .svt import SVTIE
65d161c4 48from .pornhub import PornHubIE
2bb5b6d0 49from .xhamster import XHamsterEmbedIE
2c9ca782 50from .tnaflix import TNAFlixNetworkEmbedIE
b407e173 51from .vimeo import VimeoIE
756f574e 52from .dailymotion import DailymotionCloudIE
1ac1c4c2 53from .onionstudios import OnionStudiosIE
eedd20ef 54from .snagfilms import SnagFilmsEmbedIE
efd712c6 55from .screenwavemedia import ScreenwaveMediaIE
46fde8a1 56from .mtv import MTVServicesEmbeddedIE
45dad7ba 57from .pladform import PladformIE
ff18735c 58from .videomore import VideomoreIE
5b251628 59from .googledrive import GoogleDriveIE
7cb09524 60from .jwplatform import JWPlatformIE
aecfcd4e 61from .digiteka import DigitekaIE
9b122384 62
0838239e 63
9b122384 64class GenericIE(InfoExtractor):
79649588 65 IE_DESC = 'Generic downloader that works on some sites'
9b122384 66 _VALID_URL = r'.*'
79649588 67 IE_NAME = 'generic'
cfe50f04 68 _TESTS = [
c5fa81fe
S
69 # Direct link to a video
70 {
71 'url': 'http://media.w3.org/2010/05/sintel/trailer.mp4',
72 'md5': '67d406c2bcb6af27fa886f31aa934bbe',
73 'info_dict': {
74 'id': 'trailer',
75 'ext': 'mp4',
76 'title': 'trailer',
77 'upload_date': '20100513',
78 }
79 },
c5138a7c 80 # Direct link to media delivered compressed (until Accept-Encoding is *)
c5fa81fe
S
81 {
82 'url': 'http://calimero.tk/muzik/FictionJunction-Parallel_Hearts.flac',
83 'md5': '128c42e68b13950268b648275386fc74',
84 'info_dict': {
85 'id': 'FictionJunction-Parallel_Hearts',
86 'ext': 'flac',
87 'title': 'FictionJunction-Parallel_Hearts',
88 'upload_date': '20140522',
89 },
90 'expected_warnings': [
91 'URL could be a direct video link, returning it as such.'
92 ]
93 },
94 # Direct download with broken HEAD
95 {
96 'url': 'http://ai-radio.org:8000/radio.opus',
97 'info_dict': {
98 'id': 'radio',
99 'ext': 'opus',
100 'title': 'radio',
101 },
102 'params': {
103 'skip_download': True, # infinite live stream
104 },
105 'expected_warnings': [
106 r'501.*Not Implemented'
107 ],
108 },
109 # Direct link with incorrect MIME type
110 {
111 'url': 'http://ftp.nluug.nl/video/nluug/2014-11-20_nj14/zaal-2/5_Lennart_Poettering_-_Systemd.webm',
112 'md5': '4ccbebe5f36706d85221f204d7eb5913',
113 'info_dict': {
114 'url': 'http://ftp.nluug.nl/video/nluug/2014-11-20_nj14/zaal-2/5_Lennart_Poettering_-_Systemd.webm',
115 'id': '5_Lennart_Poettering_-_Systemd',
116 'ext': 'webm',
117 'title': '5_Lennart_Poettering_-_Systemd',
118 'upload_date': '20141120',
119 },
120 'expected_warnings': [
121 'URL could be a direct video link, returning it as such.'
122 ]
123 },
124 # RSS feed
125 {
126 'url': 'http://phihag.de/2014/youtube-dl/rss2.xml',
127 'info_dict': {
128 'id': 'http://phihag.de/2014/youtube-dl/rss2.xml',
129 'title': 'Zero Punctuation',
130 'description': 're:.*groundbreaking video review series.*'
131 },
132 'playlist_mincount': 11,
133 },
134 # RSS feed with enclosure
135 {
136 'url': 'http://podcastfeeds.nbcnews.com/audio/podcast/MSNBC-MADDOW-NETCAST-M4V.xml',
137 'info_dict': {
138 'id': 'pdv_maddow_netcast_m4v-02-27-2015-201624',
139 'ext': 'm4v',
140 'upload_date': '20150228',
141 'title': 'pdv_maddow_netcast_m4v-02-27-2015-201624',
142 }
143 },
8765222d
S
144 # SMIL from http://videolectures.net/promogram_igor_mekjavic_eng
145 {
146 'url': 'http://videolectures.net/promogram_igor_mekjavic_eng/video/1/smil.xml',
147 'info_dict': {
148 'id': 'smil',
149 'ext': 'mp4',
150 'title': 'Automatics, robotics and biocybernetics',
151 'description': 'md5:815fc1deb6b3a2bff99de2d5325be482',
e327b736 152 'upload_date': '20130627',
8765222d
S
153 'formats': 'mincount:16',
154 'subtitles': 'mincount:1',
155 },
156 'params': {
157 'force_generic_extractor': True,
158 'skip_download': True,
159 },
160 },
161 # SMIL from http://www1.wdr.de/mediathek/video/livestream/index.html
162 {
163 'url': 'http://metafilegenerator.de/WDR/WDR_FS/hds/hds.smil',
164 'info_dict': {
165 'id': 'hds',
166 'ext': 'flv',
167 'title': 'hds',
168 'formats': 'mincount:1',
169 },
170 'params': {
171 'skip_download': True,
172 },
173 },
174 # SMIL from https://www.restudy.dk/video/play/id/1637
175 {
176 'url': 'https://www.restudy.dk/awsmedia/SmilDirectory/video_1637.xml',
177 'info_dict': {
178 'id': 'video_1637',
179 'ext': 'flv',
180 'title': 'video_1637',
181 'formats': 'mincount:3',
182 },
183 'params': {
184 'skip_download': True,
185 },
186 },
187 # SMIL from http://adventure.howstuffworks.com/5266-cool-jobs-iditarod-musher-video.htm
188 {
189 'url': 'http://services.media.howstuffworks.com/videos/450221/smil-service.smil',
190 'info_dict': {
191 'id': 'smil-service',
192 'ext': 'flv',
193 'title': 'smil-service',
194 'formats': 'mincount:1',
195 },
196 'params': {
197 'skip_download': True,
198 },
199 },
200 # SMIL from http://new.livestream.com/CoheedandCambria/WebsterHall/videos/4719370
201 {
202 'url': 'http://api.new.livestream.com/accounts/1570303/events/1585861/videos/4719370.smil',
203 'info_dict': {
204 'id': '4719370',
205 'ext': 'mp4',
206 'title': '571de1fd-47bc-48db-abf9-238872a58d1f',
207 'formats': 'mincount:3',
208 },
209 'params': {
210 'skip_download': True,
211 },
212 },
1de5cd3b
S
213 # XSPF playlist from http://www.telegraaf.nl/tv/nieuws/binnenland/24353229/__Tikibad_ontruimd_wegens_brand__.html
214 {
215 'url': 'http://www.telegraaf.nl/xml/playlist/2015/8/7/mZlp2ctYIUEB.xspf',
216 'info_dict': {
217 'id': 'mZlp2ctYIUEB',
218 'ext': 'mp4',
219 'title': 'Tikibad ontruimd wegens brand',
220 'description': 'md5:05ca046ff47b931f9b04855015e163a4',
221 'thumbnail': 're:^https?://.*\.jpg$',
222 'duration': 33,
223 },
224 'params': {
225 'skip_download': True,
226 },
227 },
9d939cec
S
228 # MPD from http://dash-mse-test.appspot.com/media.html
229 {
230 'url': 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-manifest.mpd',
231 'md5': '4b57baab2e30d6eb3a6a09f0ba57ef53',
232 'info_dict': {
233 'id': 'car-20120827-manifest',
234 'ext': 'mp4',
235 'title': 'car-20120827-manifest',
236 'formats': 'mincount:9',
237 },
238 'params': {
239 'format': 'bestvideo',
240 },
241 },
20938f76
S
242 # m3u8 served with Content-Type: audio/x-mpegURL; charset=utf-8
243 {
244 'url': 'http://once.unicornmedia.com/now/master/playlist/bb0b18ba-64f5-4b1b-a29f-0ac252f06b68/77a785f3-5188-4806-b788-0893a61634ed/93677179-2d99-4ef4-9e17-fe70d49abfbf/content.m3u8',
245 'info_dict': {
246 'id': 'content',
247 'ext': 'mp4',
248 'title': 'content',
249 'formats': 'mincount:8',
250 },
251 'params': {
252 # m3u8 downloads
253 'skip_download': True,
254 }
255 },
edd9b71c
S
256 # m3u8 served with Content-Type: text/plain
257 {
258 'url': 'http://www.nacentapps.com/m3u8/index.m3u8',
259 'info_dict': {
260 'id': 'index',
261 'ext': 'mp4',
262 'title': 'index',
263 'upload_date': '20140720',
264 'formats': 'mincount:11',
265 },
266 'params': {
267 # m3u8 downloads
268 'skip_download': True,
269 }
270 },
c5fa81fe
S
271 # google redirect
272 {
273 'url': 'http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCUQtwIwAA&url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcmQHVoWB5FY&ei=F-sNU-LLCaXk4QT52ICQBQ&usg=AFQjCNEw4hL29zgOohLXvpJ-Bdh2bils1Q&bvm=bv.61965928,d.bGE',
274 'info_dict': {
275 'id': 'cmQHVoWB5FY',
276 'ext': 'mp4',
277 'upload_date': '20130224',
278 'uploader_id': 'TheVerge',
279 'description': 're:^Chris Ziegler takes a look at the\.*',
280 'uploader': 'The Verge',
281 'title': 'First Firefox OS phones side-by-side',
282 },
283 'params': {
284 'skip_download': False,
285 }
286 },
6c91a5a7
S
287 {
288 # redirect in Refresh HTTP header
289 'url': 'https://www.facebook.com/l.php?u=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DpO8h3EaFRdo&h=TAQHsoToz&enc=AZN16h-b6o4Zq9pZkCCdOLNKMN96BbGMNtcFwHSaazus4JHT_MFYkAA-WARTX2kvsCIdlAIyHZjl6d33ILIJU7Jzwk_K3mcenAXoAzBNoZDI_Q7EXGDJnIhrGkLXo_LJ_pAa2Jzbx17UHMd3jAs--6j2zaeto5w9RTn8T_1kKg3fdC5WPX9Dbb18vzH7YFX0eSJmoa6SP114rvlkw6pkS1-T&s=1',
290 'info_dict': {
291 'id': 'pO8h3EaFRdo',
292 'ext': 'mp4',
293 'title': 'Tripeo Boiler Room x Dekmantel Festival DJ Set',
294 'description': 'md5:6294cc1af09c4049e0652b51a2df10d5',
295 'upload_date': '20150917',
296 'uploader_id': 'brtvofficial',
297 'uploader': 'Boiler Room',
298 },
299 'params': {
300 'skip_download': False,
301 },
302 },
cfe50f04 303 {
79649588 304 'url': 'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html',
d360a146 305 'md5': '85b90ccc9d73b4acd9138d3af4c27f89',
79649588 306 'info_dict': {
d360a146
S
307 'id': '13601338388002',
308 'ext': 'mp4',
79649588
PH
309 'uploader': 'www.hodiho.fr',
310 'title': 'R\u00e9gis plante sa Jeep',
cfe50f04
JMF
311 }
312 },
c19f7764
JMF
313 # bandcamp page with custom domain
314 {
79649588
PH
315 'add_ie': ['Bandcamp'],
316 'url': 'http://bronyrock.com/track/the-pony-mash',
79649588 317 'info_dict': {
fd50bf62
S
318 'id': '3235767654',
319 'ext': 'mp3',
79649588
PH
320 'title': 'The Pony Mash',
321 'uploader': 'M_Pallante',
c19f7764 322 },
79649588 323 'skip': 'There is a limit of 200 free downloads / month for the test song',
c19f7764 324 },
eeb165e6 325 # embedded brightcove video
dd5bcdc4
JMF
326 # it also tests brightcove videos that need to set the 'Referer' in the
327 # http requests
eeb165e6 328 {
3b7d9aa4 329 'add_ie': ['BrightcoveLegacy'],
79649588
PH
330 'url': 'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/',
331 'info_dict': {
332 'id': '2765128793001',
333 'ext': 'mp4',
334 'title': 'Le cours de bourse : l’analyse technique',
335 'description': 'md5:7e9ad046e968cb2d1114004aba466fd9',
336 'uploader': 'BFM BUSINESS',
eeb165e6 337 },
79649588
PH
338 'params': {
339 'skip_download': True,
eeb165e6
JMF
340 },
341 },
17ab4d3b
PH
342 {
343 # https://github.com/rg3/youtube-dl/issues/2253
344 'url': 'http://bcove.me/i6nfkrc3',
17ab4d3b
PH
345 'md5': '0ba9446db037002366bab3b3eb30c88c',
346 'info_dict': {
fd50bf62
S
347 'id': '3101154703001',
348 'ext': 'mp4',
17ab4d3b
PH
349 'title': 'Still no power',
350 'uploader': 'thestar.com',
351 'description': 'Mississauga resident David Farmer is still out of power as a result of the ice storm a month ago. To keep the house warm, Farmer cuts wood from his property for a wood burning stove downstairs.',
352 },
3b7d9aa4 353 'add_ie': ['BrightcoveLegacy'],
17ab4d3b 354 },
0479c625
S
355 {
356 'url': 'http://www.championat.com/video/football/v/87/87499.html',
357 'md5': 'fb973ecf6e4a78a67453647444222983',
358 'info_dict': {
359 'id': '3414141473001',
360 'ext': 'mp4',
361 'title': 'Видео. Удаление Дзагоева (ЦСКА)',
362 'description': 'Онлайн-трансляция матча ЦСКА - "Волга"',
363 'uploader': 'Championat',
364 },
365 },
bdf97017 366 {
37aab278 367 # https://github.com/rg3/youtube-dl/issues/3541
3b7d9aa4 368 'add_ie': ['BrightcoveLegacy'],
bdf97017
NJ
369 'url': 'http://www.kijk.nl/sbs6/leermijvrouwenkennen/videos/jqMiXKAYan2S/aflevering-1',
370 'info_dict': {
371 'id': '3866516442001',
37aab278 372 'ext': 'mp4',
bdf97017
NJ
373 'title': 'Leer mij vrouwen kennen: Aflevering 1',
374 'description': 'Leer mij vrouwen kennen: Aflevering 1',
375 'uploader': 'SBS Broadcasting',
376 },
37aab278 377 'skip': 'Restricted to Netherlands',
bdf97017 378 'params': {
37aab278 379 'skip_download': True, # m3u8 download
bdf97017
NJ
380 },
381 },
c0d0b01f
JMF
382 # ooyala video
383 {
79649588 384 'url': 'http://www.rollingstone.com/music/videos/norwegian-dj-cashmere-cat-goes-spartan-on-with-me-premiere-20131219',
87830900 385 'md5': '166dd577b433b4d4ebfee10b0824d8ff',
79649588
PH
386 'info_dict': {
387 'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ',
388 'ext': 'mp4',
3486df38 389 'title': '2cc213299525360.mov', # that's what we get
53e06b25 390 'duration': 238.231,
c0d0b01f 391 },
87830900 392 'add_ie': ['Ooyala'],
c0d0b01f 393 },
bf94d763
S
394 {
395 # ooyala video embedded with http://player.ooyala.com/iframe.js
396 'url': 'http://www.macrumors.com/2015/07/24/steve-jobs-the-man-in-the-machine-first-trailer/',
397 'info_dict': {
398 'id': 'p0MGJndjoG5SOKqO_hZJuZFPB-Tr5VgB',
399 'ext': 'mp4',
400 'title': '"Steve Jobs: Man in the Machine" trailer',
401 'description': 'The first trailer for the Alex Gibney documentary "Steve Jobs: Man in the Machine."',
53e06b25 402 'duration': 135.427,
bf94d763
S
403 },
404 'params': {
405 'skip_download': True,
406 },
407 },
f076b638 408 # multiple ooyala embeds on SBN network websites
409 {
410 'url': 'http://www.sbnation.com/college-football-recruiting/2015/2/3/7970291/national-signing-day-rationalizations-itll-be-ok-itll-be-ok',
411 'info_dict': {
412 'id': 'national-signing-day-rationalizations-itll-be-ok-itll-be-ok',
413 'title': '25 lies you will tell yourself on National Signing Day - SBNation.com',
414 },
415 'playlist_mincount': 3,
416 'params': {
417 'skip_download': True,
418 },
419 'add_ie': ['Ooyala'],
420 },
1b86cc41 421 # embed.ly video
422 {
423 'url': 'http://www.tested.com/science/weird/460206-tested-grinding-coffee-2000-frames-second/',
424 'info_dict': {
425 'id': '9ODmcdjQcHQ',
426 'ext': 'mp4',
0a5bce56
PH
427 'title': 'Tested: Grinding Coffee at 2000 Frames Per Second',
428 'upload_date': '20140225',
429 'description': 'md5:06a40fbf30b220468f1e0957c0f558ff',
430 'uploader': 'Tested',
431 'uploader_id': 'testedcom',
1b86cc41 432 },
433 # No need to test YoutubeIE here
434 'params': {
435 'skip_download': True,
436 },
437 },
60cc4dc4
PH
438 # funnyordie embed
439 {
440 'url': 'http://www.theguardian.com/world/2014/mar/11/obama-zach-galifianakis-between-two-ferns',
60cc4dc4
PH
441 'info_dict': {
442 'id': '18e820ec3f',
443 'ext': 'mp4',
444 'title': 'Between Two Ferns with Zach Galifianakis: President Barack Obama',
445 'description': 'Episode 18: President Barack Obama sits down with Zach Galifianakis for his most memorable interview yet.',
93d020dd 446 },
60cc4dc4 447 },
93d020dd
S
448 # RUTV embed
449 {
450 'url': 'http://www.rg.ru/2014/03/15/reg-dfo/anklav-anons.html',
451 'info_dict': {
452 'id': '776940',
453 'ext': 'mp4',
454 'title': 'Охотское море стало целиком российским',
455 'description': 'md5:5ed62483b14663e2a95ebbe115eb8f43',
456 },
457 'params': {
458 # m3u8 download
459 'skip_download': True,
460 },
aab74fa1 461 },
f37bdbe5
S
462 # TVC embed
463 {
464 'url': 'http://sch1298sz.mskobr.ru/dou_edu/karamel_ki/filial_galleries/video/iframe_src_http_tvc_ru_video_iframe_id_55304_isplay_false_acc_video_id_channel_brand_id_11_show_episodes_episode_id_32307_frameb/',
465 'info_dict': {
466 'id': '55304',
467 'ext': 'mp4',
468 'title': 'Дошкольное воспитание',
469 },
470 },
b827a601
S
471 # SportBox embed
472 {
473 'url': 'http://www.vestifinance.ru/articles/25753',
474 'info_dict': {
475 'id': '25753',
476 'title': 'Вести Экономика ― Прямые трансляции с Форума-выставки "Госзаказ-2013"',
477 },
478 'playlist': [{
479 'info_dict': {
480 'id': '370908',
481 'title': 'Госзаказ. День 3',
482 'ext': 'mp4',
483 }
484 }, {
485 'info_dict': {
486 'id': '370905',
487 'title': 'Госзаказ. День 2',
488 'ext': 'mp4',
489 }
490 }, {
491 'info_dict': {
492 'id': '370902',
493 'title': 'Госзаказ. День 1',
494 'ext': 'mp4',
495 }
496 }],
497 'params': {
498 # m3u8 download
499 'skip_download': True,
500 },
501 },
bf20b9c5
S
502 # Myvi.ru embed
503 {
504 'url': 'http://www.kinomyvi.tv/news/detail/Pervij-dublirovannij-trejler--Uzhastikov-_nOw1',
505 'info_dict': {
506 'id': 'f4dafcad-ff21-423d-89b5-146cfd89fa1e',
507 'ext': 'mp4',
508 'title': 'Ужастики, русский трейлер (2015)',
509 'thumbnail': 're:^https?://.*\.jpg$',
510 'duration': 153,
511 }
512 },
c76799c5
S
513 # XHamster embed
514 {
515 'url': 'http://www.numisc.com/forum/showthread.php?11696-FM15-which-pumiscer-was-this-%28-vid-%29-%28-alfa-as-fuck-srx-%29&s=711f5db534502e22260dec8c5e2d66d8',
516 'info_dict': {
517 'id': 'showthread',
518 'title': '[NSFL] [FM15] which pumiscer was this ( vid ) ( alfa as fuck srx )',
519 },
520 'playlist_mincount': 7,
521 },
aab74fa1
PH
522 # Embedded TED video
523 {
524 'url': 'http://en.support.wordpress.com/videos/ted-talks/',
a8eb5a8e 525 'md5': '65fdff94098e4a607385a60c5177c638',
aab74fa1 526 'info_dict': {
a8eb5a8e 527 'id': '1969',
aab74fa1 528 'ext': 'mp4',
a8eb5a8e
PH
529 'title': 'Hidden miracles of the natural world',
530 'uploader': 'Louie Schwartzberg',
531 'description': 'md5:8145d19d320ff3e52f28401f4c4283b9',
aab74fa1 532 }
60cc4dc4 533 },
dfb1b146 534 # Embedded Ustream video
5c386252 535 {
536 'url': 'http://www.american.edu/spa/pti/nsa-privacy-janus-2014.cfm',
537 'md5': '27b99cdb639c9b12a79bca876a073417',
538 'info_dict': {
ca6aada4 539 'id': '45734260',
540 'ext': 'flv',
541 'uploader': 'AU SPA: The NSA and Privacy',
5c386252 542 'title': 'NSA and Privacy Forum Debate featuring General Hayden and Barton Gellman'
543 }
544 },
d95e35d6
S
545 # nowvideo embed hidden behind percent encoding
546 {
547 'url': 'http://www.waoanime.tv/the-super-dimension-fortress-macross-episode-1/',
548 'md5': '2baf4ddd70f697d94b1c18cf796d5107',
549 'info_dict': {
550 'id': '06e53103ca9aa',
551 'ext': 'flv',
552 'title': 'Macross Episode 001 Watch Macross Episode 001 onl',
553 'description': 'No description',
554 },
0f2a2ba1 555 },
893f8832
PH
556 # arte embed
557 {
558 'url': 'http://www.tv-replay.fr/redirection/20-03-14/x-enius-arte-10753389.html',
559 'md5': '7653032cbb25bf6c80d80f217055fa43',
560 'info_dict': {
561 'id': '048195-004_PLUS7-F',
562 'ext': 'flv',
563 'title': 'X:enius',
564 'description': 'md5:d5fdf32ef6613cdbfd516ae658abf168',
565 'upload_date': '20140320',
566 },
567 'params': {
568 'skip_download': 'Requires rtmpdump'
569 }
570 },
cbd55ade
S
571 # francetv embed
572 {
573 'url': 'http://www.tsprod.com/replay-du-concert-alcaline-de-calogero',
574 'info_dict': {
575 'id': 'EV_30231',
576 'ext': 'mp4',
577 'title': 'Alcaline, le concert avec Calogero',
578 'description': 'md5:61f08036dcc8f47e9cfc33aed08ffaff',
579 'upload_date': '20150226',
580 'timestamp': 1424989860,
581 'duration': 5400,
582 },
583 'params': {
584 # m3u8 downloads
585 'skip_download': True,
586 },
587 'expected_warnings': [
588 'Forbidden'
589 ]
590 },
fa35cdad
PH
591 # Condé Nast embed
592 {
593 'url': 'http://www.wired.com/2014/04/honda-asimo/',
594 'md5': 'ba0dfe966fa007657bd1443ee672db0f',
595 'info_dict': {
596 'id': '53501be369702d3275860000',
597 'ext': 'mp4',
598 'title': 'Honda’s New Asimo Robot Is More Human Than Ever',
599 }
ebd3c7b3
PH
600 },
601 # Dailymotion embed
602 {
603 'url': 'http://www.spi0n.com/zap-spi0n-com-n216/',
604 'md5': '441aeeb82eb72c422c7f14ec533999cd',
605 'info_dict': {
606 'id': 'k2mm4bCdJ6CQ2i7c8o2',
607 'ext': 'mp4',
608 'title': 'Le Zap de Spi0n n°216 - Zapping du Web',
609 'uploader': 'Spi0n',
610 },
611 'add_ie': ['Dailymotion'],
2b88feed
PH
612 },
613 # YouTube embed
614 {
615 'url': 'http://www.badzine.de/ansicht/datum/2014/06/09/so-funktioniert-die-neue-englische-badminton-liga.html',
616 'info_dict': {
617 'id': 'FXRb4ykk4S0',
618 'ext': 'mp4',
619 'title': 'The NBL Auction 2014',
620 'uploader': 'BADMINTON England',
621 'uploader_id': 'BADMINTONEvents',
622 'upload_date': '20140603',
623 'description': 'md5:9ef128a69f1e262a700ed83edb163a73',
624 },
625 'add_ie': ['Youtube'],
626 'params': {
627 'skip_download': True,
628 }
629 },
c5cd249e
JMF
630 # MTVSercices embed
631 {
632 'url': 'http://www.gametrailers.com/news-post/76093/north-america-europe-is-getting-that-mario-kart-8-mercedes-dlc-too',
633 'md5': '35727f82f58c76d996fc188f9755b0d5',
634 'info_dict': {
635 'id': '0306a69b-8adf-4fb5-aace-75f8e8cbfca9',
636 'ext': 'mp4',
637 'title': 'Review',
638 'description': 'Mario\'s life in the fast lane has never looked so good.',
639 },
640 },
61013473 641 # YouTube embed via <data-embed-url="">
642 {
643 'url': 'https://play.google.com/store/apps/details?id=com.gameloft.android.ANMP.GloftA8HM',
61013473 644 'info_dict': {
a8eb5a8e 645 'id': '4vAffPZIT44',
61013473 646 'ext': 'mp4',
a8eb5a8e 647 'title': 'Asphalt 8: Airborne - Update - Welcome to Dubai!',
ed2d6a19
PH
648 'uploader': 'Gameloft',
649 'uploader_id': 'gameloft',
a8eb5a8e
PH
650 'upload_date': '20140828',
651 'description': 'md5:c80da9ed3d83ae6d1876c834de03e1c4',
ed2d6a19
PH
652 },
653 'params': {
654 'skip_download': True,
61013473 655 }
c8e9a235
PH
656 },
657 # Camtasia studio
658 {
659 'url': 'http://www.ll.mit.edu/workshops/education/videocourses/antennas/lecture1/video/',
660 'playlist': [{
661 'md5': '0c5e352edabf715d762b0ad4e6d9ee67',
662 'info_dict': {
663 'id': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final',
664 'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final - video1',
665 'ext': 'flv',
666 'duration': 2235.90,
667 }
668 }, {
669 'md5': '10e4bb3aaca9fd630e273ff92d9f3c63',
670 'info_dict': {
671 'id': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final_PIP',
672 'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final - pip',
673 'ext': 'flv',
674 'duration': 2235.93,
675 }
676 }],
677 'info_dict': {
678 'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final',
679 }
4d805e06
PH
680 },
681 # Flowplayer
682 {
683 'url': 'http://www.handjobhub.com/video/busty-blonde-siri-tit-fuck-while-wank-6313.html',
684 'md5': '9d65602bf31c6e20014319c7d07fba27',
685 'info_dict': {
686 'id': '5123ea6d5e5a7',
687 'ext': 'mp4',
688 'age_limit': 18,
689 'uploader': 'www.handjobhub.com',
d6d9186f 690 'title': 'Busty Blonde Siri Tit Fuck While Wank at HandjobHub.com',
4d805e06 691 }
0990305d 692 },
22a6f150
PH
693 # Multiple brightcove videos
694 # https://github.com/rg3/youtube-dl/issues/2283
695 {
696 'url': 'http://www.newyorker.com/online/blogs/newsdesk/2014/01/always-never-nuclear-command-and-control.html',
697 'info_dict': {
698 'id': 'always-never',
699 'title': 'Always / Never - The New Yorker',
700 },
701 'playlist_count': 3,
702 'params': {
703 'extract_flat': False,
704 'skip_download': True,
705 }
1a94ff68
S
706 },
707 # MLB embed
708 {
709 'url': 'http://umpire-empire.com/index.php/topic/58125-laz-decides-no-thats-low/',
710 'md5': '96f09a37e44da40dd083e12d9a683327',
711 'info_dict': {
712 'id': '33322633',
713 'ext': 'mp4',
714 'title': 'Ump changes call to ball',
715 'description': 'md5:71c11215384298a172a6dcb4c2e20685',
716 'duration': 48,
717 'timestamp': 1401537900,
718 'upload_date': '20140531',
719 'thumbnail': 're:^https?://.*\.jpg$',
720 },
721 },
746c67d7
NJ
722 # Wistia embed
723 {
724 'url': 'http://education-portal.com/academy/lesson/north-american-exploration-failed-colonies-of-spain-france-england.html#lesson',
725 'md5': '8788b683c777a5cf25621eaf286d0c23',
726 'info_dict': {
727 'id': '1cfaf6b7ea',
728 'ext': 'mov',
729 'title': 'md5:51364a8d3d009997ba99656004b5e20d',
730 'duration': 643.0,
731 'filesize': 182808282,
732 'uploader': 'education-portal.com',
733 },
734 },
52cffcb1 735 {
736 'url': 'http://thoughtworks.wistia.com/medias/uxjb0lwrcz',
737 'md5': 'baf49c2baa8a7de5f3fc145a8506dcd4',
738 'info_dict': {
739 'id': 'uxjb0lwrcz',
740 'ext': 'mp4',
85d7b765 741 'title': 'Conversation about Hexagonal Rails Part 1 - ThoughtWorks',
52cffcb1 742 'duration': 1715.0,
85d7b765 743 'uploader': 'thoughtworks.wistia.com',
70b7e3fb 744 },
52cffcb1 745 },
ac645ac7
PH
746 # Soundcloud embed
747 {
748 'url': 'http://nakedsecurity.sophos.com/2014/10/29/sscc-171-are-you-sure-that-1234-is-a-bad-password-podcast/',
749 'info_dict': {
750 'id': '174391317',
751 'ext': 'mp3',
752 'description': 'md5:ff867d6b555488ad3c52572bb33d432c',
753 'uploader': 'Sophos Security',
754 'title': 'Chet Chat 171 - Oct 29, 2014',
755 'upload_date': '20141029',
756 }
af63fed7
PH
757 },
758 # Livestream embed
759 {
760 'url': 'http://www.esa.int/Our_Activities/Space_Science/Rosetta/Philae_comet_touch-down_webcast',
761 'info_dict': {
762 'id': '67864563',
763 'ext': 'flv',
764 'upload_date': '20141112',
765 'title': 'Rosetta #CometLanding webcast HL 10',
766 }
767 },
65f3a228
PH
768 # LazyYT
769 {
770 'url': 'http://discourse.ubuntu.com/t/unity-8-desktop-mode-windows-on-mir/1986',
771 'info_dict': {
11e611a7 772 'id': '1986',
65f3a228
PH
773 'title': 'Unity 8 desktop-mode windows on Mir! - Ubuntu Discourse',
774 },
775 'playlist_mincount': 2,
4e262a88 776 },
42bdd9d0
PH
777 # Cinchcast embed
778 {
779 'url': 'http://undergroundwellness.com/podcasts/306-5-steps-to-permanent-gut-healing/',
780 'info_dict': {
781 'id': '7141703',
782 'ext': 'mp3',
783 'upload_date': '20141126',
784 'title': 'Jack Tips: 5 Steps to Permanent Gut Healing',
785 }
786 },
501f13fb
PH
787 # Cinerama player
788 {
789 'url': 'http://www.abc.net.au/7.30/content/2015/s4164797.htm',
790 'info_dict': {
791 'id': '730m_DandD_1901_512k',
792 'ext': 'mp4',
793 'uploader': 'www.abc.net.au',
794 'title': 'Game of Thrones with dice - Dungeons and Dragons fantasy role-playing game gets new life - 19/01/2015',
795 }
796df3c6
S
796 },
797 # embedded viddler video
798 {
799 'url': 'http://deadspin.com/i-cant-stop-watching-john-wall-chop-the-nuggets-with-th-1681801597',
800 'info_dict': {
801 'id': '4d03aad9',
802 'ext': 'mp4',
803 'uploader': 'deadspin',
804 'title': 'WALL-TO-GORTAT',
805 'timestamp': 1422285291,
806 'upload_date': '20150126',
807 },
808 'add_ie': ['Viddler'],
a0f71985 809 },
2051acde
S
810 # Libsyn embed
811 {
812 'url': 'http://thedailyshow.cc.com/podcast/episodetwelve',
813 'info_dict': {
814 'id': '3377616',
815 'ext': 'mp3',
816 'title': "The Daily Show Podcast without Jon Stewart - Episode 12: Bassem Youssef: Egypt's Jon Stewart",
817 'description': 'md5:601cb790edd05908957dae8aaa866465',
818 'upload_date': '20150220',
819 },
820 },
a0f71985
PH
821 # jwplayer YouTube
822 {
823 'url': 'http://media.nationalarchives.gov.uk/index.php/webinar-using-discovery-national-archives-online-catalogue/',
824 'info_dict': {
825 'id': 'Mrj4DVp2zeA',
826 'ext': 'mp4',
f37e3f99 827 'upload_date': '20150212',
a0f71985
PH
828 'uploader': 'The National Archives UK',
829 'description': 'md5:a236581cd2449dd2df4f93412f3f01c6',
830 'uploader_id': 'NationalArchives08',
831 'title': 'Webinar: Using Discovery, The National Archives’ online catalogue',
832 },
59b8ab58
PH
833 },
834 # rtl.nl embed
835 {
836 'url': 'http://www.rtlnieuws.nl/nieuws/buitenland/aanslagen-kopenhagen',
837 'playlist_mincount': 5,
838 'info_dict': {
839 'id': 'aanslagen-kopenhagen',
840 'title': 'Aanslagen Kopenhagen | RTL Nieuws',
841 }
255fca5e
S
842 },
843 # Zapiks embed
844 {
845 'url': 'http://www.skipass.com/news/116090-bon-appetit-s5ep3-baqueira-mi-cor.html',
846 'info_dict': {
847 'id': '118046',
848 'ext': 'mp4',
849 'title': 'EP3S5 - Bon Appétit - Baqueira Mi Corazon !',
850 }
851 },
e3216b82
NJ
852 # Kaltura embed
853 {
854 'url': 'http://www.monumentalnetwork.com/videos/john-carlson-postgame-2-25-15',
855 'info_dict': {
856 'id': '1_eergr3h1',
857 'ext': 'mp4',
858 'upload_date': '20150226',
859 'uploader_id': 'MonumentalSports-Kaltura@perfectsensedigital.com',
860 'timestamp': int,
861 'title': 'John Carlson Postgame 2/25/15',
862 },
863 },
66e568de
S
864 # Kaltura embed (different embed code)
865 {
866 'url': 'http://www.premierchristianradio.com/Shows/Saturday/Unbelievable/Conference-Videos/Os-Guinness-Is-It-Fools-Talk-Unbelievable-Conference-2014',
867 'info_dict': {
868 'id': '1_a52wc67y',
869 'ext': 'flv',
870 'upload_date': '20150127',
871 'uploader_id': 'PremierMedia',
872 'timestamp': int,
873 'title': 'Os Guinness // Is It Fools Talk? // Unbelievable? Conference 2014',
874 },
875 },
6da620de
S
876 # Kaltura embed protected with referrer
877 {
878 'url': 'http://www.disney.nl/disney-channel/filmpjes/achter-de-schermen#/videoId/violetta-achter-de-schermen-ruggero',
879 'info_dict': {
880 'id': '1_g4fbemnq',
881 'ext': 'mp4',
882 'title': 'Violetta - Achter De Schermen - Ruggero',
883 'description': 'Achter de schermen met Ruggero',
884 'timestamp': 1435133761,
885 'upload_date': '20150624',
886 'uploader_id': 'echojecka',
887 },
888 },
135c9c42
S
889 # Eagle.Platform embed (generic URL)
890 {
891 'url': 'http://lenta.ru/news/2015/03/06/navalny/',
892 'info_dict': {
893 'id': '227304',
894 'ext': 'mp4',
895 'title': 'Навальный вышел на свободу',
896 'description': 'md5:d97861ac9ae77377f3f20eaf9d04b4f5',
897 'thumbnail': 're:^https?://.*\.jpg$',
898 'duration': 87,
899 'view_count': int,
900 'age_limit': 0,
901 },
902 },
d47ae7f6
S
903 # ClipYou (Eagle.Platform) embed (custom URL)
904 {
905 'url': 'http://muz-tv.ru/play/7129/',
906 'info_dict': {
907 'id': '12820',
908 'ext': 'mp4',
909 'title': "'O Sole Mio",
910 'thumbnail': 're:^https?://.*\.jpg$',
911 'duration': 216,
912 'view_count': int,
913 },
914 },
f8388757
S
915 # Pladform embed
916 {
917 'url': 'http://muz-tv.ru/kinozal/view/7400/',
918 'info_dict': {
919 'id': '100183293',
920 'ext': 'mp4',
62259846 921 'title': 'Тайны перевала Дятлова • 1 серия 2 часть',
f8388757
S
922 'description': 'Документальный сериал-расследование одной из самых жутких тайн ХХ века',
923 'thumbnail': 're:^https?://.*\.jpg$',
924 'duration': 694,
925 'age_limit': 0,
926 },
927 },
c798f15b
S
928 # Playwire embed
929 {
930 'url': 'http://www.cinemablend.com/new/First-Joe-Dirt-2-Trailer-Teaser-Stupid-Greatness-70874.html',
931 'info_dict': {
932 'id': '3519514',
933 'ext': 'mp4',
934 'title': 'Joe Dirt 2 Beautiful Loser Teaser Trailer',
935 'thumbnail': 're:^https?://.*\.png$',
936 'duration': 45.115,
937 },
938 },
ad320e9b
NJ
939 # 5min embed
940 {
941 'url': 'http://techcrunch.com/video/facebook-creates-on-this-day-crunch-report/518726732/',
942 'md5': '4c6f127a30736b59b3e2c19234ee2bf7',
943 'info_dict': {
944 'id': '518726732',
945 'ext': 'mp4',
946 'title': 'Facebook Creates "On This Day" | Crunch Report',
947 },
948 },
dc455a5f
S
949 # SVT embed
950 {
951 'url': 'http://www.svt.se/sport/ishockey/jagr-tacklar-giroux-under-intervjun',
952 'info_dict': {
953 'id': '2900353',
954 'ext': 'flv',
955 'title': 'Här trycker Jagr till Giroux (under SVT-intervjun)',
956 'duration': 27,
957 'age_limit': 0,
958 },
959 },
a4257017
S
960 # Crooks and Liars embed
961 {
962 'url': 'http://crooksandliars.com/2015/04/fox-friends-says-protecting-atheists',
963 'info_dict': {
964 'id': '8RUoRhRi',
965 'ext': 'mp4',
966 'title': "Fox & Friends Says Protecting Atheists From Discrimination Is Anti-Christian!",
967 'description': 'md5:e1a46ad1650e3a5ec7196d432799127f',
968 'timestamp': 1428207000,
969 'upload_date': '20150405',
970 'uploader': 'Heather',
971 },
972 },
973 # Crooks and Liars external embed
974 {
975 'url': 'http://theothermccain.com/2010/02/02/video-proves-that-bill-kristol-has-been-watching-glenn-beck/comment-page-1/',
976 'info_dict': {
977 'id': 'MTE3MjUtMzQ2MzA',
978 'ext': 'mp4',
979 'title': 'md5:5e3662a81a4014d24c250d76d41a08d5',
980 'description': 'md5:9b8e9542d6c3c5de42d6451b7d780cec',
981 'timestamp': 1265032391,
982 'upload_date': '20100201',
983 'uploader': 'Heather',
984 },
985 },
facecb84 986 # NBC Sports vplayer embed
a2edf2e7 987 {
facecb84 988 'url': 'http://www.riderfans.com/forum/showthread.php?121827-Freeman&s=e98fa1ea6dc08e886b1678d35212494a',
a2edf2e7 989 'info_dict': {
facecb84
S
990 'id': 'ln7x1qSThw4k',
991 'ext': 'flv',
992 'title': "PFT Live: New leader in the 'new-look' defense",
993 'description': 'md5:65a19b4bbfb3b0c0c5768bed1dfad74e',
a2edf2e7 994 },
418c5cc3
YCH
995 },
996 # UDN embed
997 {
998 'url': 'http://www.udn.com/news/story/7314/822787',
01c58f84 999 'md5': 'fd2060e988c326991037b9aff9df21a6',
418c5cc3 1000 'info_dict': {
01c58f84 1001 'id': '300346',
418c5cc3 1002 'ext': 'mp4',
01c58f84 1003 'title': '中一中男師變性 全校師生力挺',
418c5cc3
YCH
1004 'thumbnail': 're:^https?://.*\.jpg$',
1005 }
edfcf7ab
YCH
1006 },
1007 # Ooyala embed
1008 {
1009 'url': 'http://www.businessinsider.com/excel-index-match-vlookup-video-how-to-2015-2?IR=T',
1010 'info_dict': {
1011 'id': '50YnY4czr4ms1vJ7yz3xzq0excz_pUMs',
1012 'ext': 'mp4',
cce9d15d 1013 'description': 'VIDEO: INDEX/MATCH versus VLOOKUP.',
edfcf7ab 1014 'title': 'This is what separates the Excel masters from the wannabes',
53e06b25 1015 'duration': 191.933,
edfcf7ab
YCH
1016 },
1017 'params': {
1018 # m3u8 downloads
1019 'skip_download': True,
1020 }
d6fd958c
YCH
1021 },
1022 # Contains a SMIL manifest
1023 {
1024 'url': 'http://www.telewebion.com/fa/1263668/%D9%82%D8%B1%D8%B9%D9%87%E2%80%8C%DA%A9%D8%B4%DB%8C-%D9%84%DB%8C%DA%AF-%D9%82%D9%87%D8%B1%D9%85%D8%A7%D9%86%D8%A7%D9%86-%D8%A7%D8%B1%D9%88%D9%BE%D8%A7/%2B-%D9%81%D9%88%D8%AA%D8%A8%D8%A7%D9%84.html',
1025 'info_dict': {
1026 'id': 'file',
1027 'ext': 'flv',
1028 'title': '+ Football: Lottery Champions League Europe',
1029 'uploader': 'www.telewebion.com',
1030 },
1031 'params': {
1032 # rtmpe downloads
1033 'skip_download': True,
1034 }
b26733ba
YCH
1035 },
1036 # Brightcove URL in single quotes
1037 {
1038 'url': 'http://www.sportsnet.ca/baseball/mlb/sn-presents-russell-martin-world-citizen/',
1039 'md5': '4ae374f1f8b91c889c4b9203c8c752af',
1040 'info_dict': {
1041 'id': '4255764656001',
1042 'ext': 'mp4',
1043 'title': 'SN Presents: Russell Martin, World Citizen',
1044 'description': 'To understand why he was the Toronto Blue Jays’ top off-season priority is to appreciate his background and upbringing in Montreal, where he first developed his baseball skills. Written and narrated by Stephen Brunt.',
1045 'uploader': 'Rogers Sportsnet',
1046 },
756f574e
YCH
1047 },
1048 # Dailymotion Cloud video
1049 {
1050 'url': 'http://replay.publicsenat.fr/vod/le-debat/florent-kolandjian,dominique-cena,axel-decourtye,laurence-abeille,bruno-parmentier/175910',
1051 'md5': '49444254273501a64675a7e68c502681',
1052 'info_dict': {
1053 'id': '5585de919473990de4bee11b',
1054 'ext': 'mp4',
1055 'title': 'Le débat',
1056 'thumbnail': 're:^https?://.*\.jpe?g$',
1057 }
a5158f38 1058 },
8084be78
S
1059 # OnionStudios embed
1060 {
1061 'url': 'http://www.clickhole.com/video/dont-understand-bitcoin-man-will-mumble-explanatio-2537',
1062 'info_dict': {
1063 'id': '2855',
1064 'ext': 'mp4',
1065 'title': 'Don’t Understand Bitcoin? This Man Will Mumble An Explanation At You',
1066 'thumbnail': 're:^https?://.*\.jpe?g$',
1067 'uploader': 'ClickHole',
1068 'uploader_id': 'clickhole',
1069 }
1070 },
b8c1cc1a
S
1071 # SnagFilms embed
1072 {
1073 'url': 'http://whilewewatch.blogspot.ru/2012/06/whilewewatch-whilewewatch-gripping.html',
1074 'info_dict': {
1075 'id': '74849a00-85a9-11e1-9660-123139220831',
1076 'ext': 'mp4',
1077 'title': '#whilewewatch',
1078 }
1079 },
a5158f38
YCH
1080 # AdobeTVVideo embed
1081 {
1082 'url': 'https://helpx.adobe.com/acrobat/how-to/new-experience-acrobat-dc.html?set=acrobat--get-started--essential-beginners',
1083 'md5': '43662b577c018ad707a63766462b1e87',
1084 'info_dict': {
1085 'id': '2456',
1086 'ext': 'mp4',
1087 'title': 'New experience with Acrobat DC',
1088 'description': 'New experience with Acrobat DC',
1089 'duration': 248.667,
1090 },
1f812580
S
1091 },
1092 # ScreenwaveMedia embed
1093 {
1094 'url': 'http://www.thecinemasnob.com/the-cinema-snob/a-nightmare-on-elm-street-2-freddys-revenge1',
1095 'md5': '24ace5baba0d35d55c6810b51f34e9e0',
1096 'info_dict': {
1097 'id': 'cinemasnob-55d26273809dd',
1098 'ext': 'mp4',
1099 'title': 'cinemasnob',
1100 },
ed126900 1101 },
1102 # BrightcoveInPageEmbed embed
1103 {
1104 'url': 'http://www.geekandsundry.com/tabletop-bonus-wils-final-thoughts-on-dread/',
1105 'info_dict': {
1106 'id': '4238694884001',
1107 'ext': 'flv',
1108 'title': 'Tabletop: Dread, Last Thoughts',
1109 'description': 'Tabletop: Dread, Last Thoughts',
1110 'duration': 51690,
1111 },
750b9ff0
YCH
1112 },
1113 # JWPlayer with M3U8
1114 {
1115 'url': 'http://ren.tv/novosti/2015-09-25/sluchaynyy-prohozhiy-poymal-avtougonshchika-v-murmanske-video',
1116 'info_dict': {
1117 'id': 'playlist',
1118 'ext': 'mp4',
1119 'title': 'Случайный прохожий поймал автоугонщика в Мурманске. ВИДЕО | РЕН ТВ',
1120 'uploader': 'ren.tv',
1121 },
1122 'params': {
1123 # m3u8 downloads
1124 'skip_download': True,
1125 }
76c73715 1126 }
cfe50f04 1127 ]
9b122384 1128
9b122384
PH
1129 def report_following_redirect(self, new_url):
1130 """Report information extraction."""
79649588 1131 self._downloader.to_screen('[redirect] Following redirect to %s' % new_url)
9b122384 1132
4fc946b5
PH
1133 def _extract_rss(self, url, video_id, doc):
1134 playlist_title = doc.find('./channel/title').text
1135 playlist_desc_el = doc.find('./channel/description')
1136 playlist_desc = None if playlist_desc_el is None else playlist_desc_el.text
1137
76c73715
PH
1138 entries = []
1139 for it in doc.findall('./channel/item'):
1140 next_url = xpath_text(it, 'link', fatal=False)
1141 if not next_url:
1142 enclosure_nodes = it.findall('./enclosure')
1143 for e in enclosure_nodes:
1144 next_url = e.attrib.get('url')
1145 if next_url:
1146 break
1147
1148 if not next_url:
1149 continue
1150
1151 entries.append({
1152 '_type': 'url',
1153 'url': next_url,
1154 'title': it.find('title').text,
1155 })
4fc946b5
PH
1156
1157 return {
1158 '_type': 'playlist',
1159 'id': url,
1160 'title': playlist_title,
1161 'description': playlist_desc,
1162 'entries': entries,
1163 }
1164
c8e9a235
PH
1165 def _extract_camtasia(self, url, video_id, webpage):
1166 """ Returns None if no camtasia video can be found. """
1167
1168 camtasia_cfg = self._search_regex(
1169 r'fo\.addVariable\(\s*"csConfigFile",\s*"([^"]+)"\s*\);',
1170 webpage, 'camtasia configuration file', default=None)
1171 if camtasia_cfg is None:
1172 return None
1173
1174 title = self._html_search_meta('DC.title', webpage, fatal=True)
1175
1176 camtasia_url = compat_urlparse.urljoin(url, camtasia_cfg)
1177 camtasia_cfg = self._download_xml(
1178 camtasia_url, video_id,
1179 note='Downloading camtasia configuration',
1180 errnote='Failed to download camtasia configuration')
1181 fileset_node = camtasia_cfg.find('./playlist/array/fileset')
1182
1183 entries = []
1184 for n in fileset_node.getchildren():
1185 url_n = n.find('./uri')
1186 if url_n is None:
1187 continue
1188
1189 entries.append({
1190 'id': os.path.splitext(url_n.text.rpartition('/')[2])[0],
1191 'title': '%s - %s' % (title, n.tag),
1192 'url': compat_urlparse.urljoin(url, url_n.text),
1193 'duration': float_or_none(n.find('./duration').text),
1194 })
1195
1196 return {
1197 '_type': 'playlist',
1198 'entries': entries,
1199 'title': title,
1200 }
1201
9b122384 1202 def _real_extract(self, url):
ebd3c7b3
PH
1203 if url.startswith('//'):
1204 return {
1205 '_type': 'url',
20991253 1206 'url': self.http_scheme() + url,
ebd3c7b3
PH
1207 }
1208
a7130543
JMF
1209 parsed_url = compat_urlparse.urlparse(url)
1210 if not parsed_url.scheme:
04b4d394
PH
1211 default_search = self._downloader.params.get('default_search')
1212 if default_search is None:
1f7ccb90 1213 default_search = 'fixup_error'
04b4d394 1214
1f7ccb90 1215 if default_search in ('auto', 'auto_warning', 'fixup_error'):
04b4d394
PH
1216 if '/' in url:
1217 self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
1218 return self.url_result('http://' + url)
1f7ccb90 1219 elif default_search != 'fixup_error':
9c1fc022 1220 if default_search == 'auto_warning':
0e67ab0d
PH
1221 if re.match(r'^(?:url|URL)$', url):
1222 raise ExtractorError(
1223 'Invalid URL: %r . Call youtube-dl like this: youtube-dl -v "https://www.youtube.com/watch?v=BaW_jenozKc" ' % url,
1224 expected=True)
1225 else:
1226 self._downloader.report_warning(
7571c02c 1227 'Falling back to youtube search for %s . Set --default-search "auto" to suppress this warning.' % url)
04b4d394 1228 return self.url_result('ytsearch:' + url)
1f7ccb90
PH
1229
1230 if default_search in ('error', 'fixup_error'):
7571c02c 1231 raise ExtractorError(
b74e86f4
PH
1232 '%r is not a valid URL. '
1233 'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
1234 % (url, url), expected=True)
04b4d394 1235 else:
f2f2c0c2
PH
1236 if ':' not in default_search:
1237 default_search += ':'
04b4d394 1238 return self.url_result(default_search + url)
4d54ef20
PH
1239
1240 url, smuggled_data = unsmuggle_url(url)
1241 force_videoid = None
d6e6a422 1242 is_intentional = smuggled_data and smuggled_data.get('to_generic')
4d54ef20
PH
1243 if smuggled_data and 'force_videoid' in smuggled_data:
1244 force_videoid = smuggled_data['force_videoid']
1245 video_id = force_videoid
1246 else:
1ddb9456 1247 video_id = compat_urllib_parse_unquote(os.path.splitext(url.rstrip('/').split('/')[-1])[0])
a7130543 1248
79649588 1249 self.to_screen('%s: Requesting header' % video_id)
c1d1facd 1250
ebab4520 1251 head_req = HEADRequest(url)
23be51d8 1252 head_response = self._request_webpage(
ebab4520
PH
1253 head_req, video_id,
1254 note=False, errnote='Could not send HEAD request to %s' % url,
1255 fatal=False)
42393ce2 1256
23be51d8 1257 if head_response is not False:
42393ce2 1258 # Check for redirect
23be51d8 1259 new_url = head_response.geturl()
42393ce2
PH
1260 if url != new_url:
1261 self.report_following_redirect(new_url)
4d54ef20
PH
1262 if force_videoid:
1263 new_url = smuggle_url(
1264 new_url, {'force_videoid': force_videoid})
cecaaf3f 1265 return self.url_result(new_url)
42393ce2 1266
23be51d8
PH
1267 full_response = None
1268 if head_response is False:
5c2266df 1269 request = sanitized_Request(url)
58bde34a
S
1270 request.add_header('Accept-Encoding', '*')
1271 full_response = self._request_webpage(request, video_id)
23be51d8
PH
1272 head_response = full_response
1273
f930e0c7
S
1274 info_dict = {
1275 'id': video_id,
1276 'title': compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0]),
303dcdb9 1277 'upload_date': unified_strdate(head_response.headers.get('Last-Modified'))
f930e0c7
S
1278 }
1279
23be51d8 1280 # Check for direct link to a video
955737b2 1281 content_type = head_response.headers.get('Content-Type', '').lower()
263eff95 1282 m = re.match(r'^(?P<type>audio|video|application(?=/(?:ogg$|(?:vnd\.apple\.|x-)?mpegurl)))/(?P<format_id>[^;\s]+)', content_type)
23be51d8 1283 if m:
f930e0c7
S
1284 format_id = m.group('format_id')
1285 if format_id.endswith('mpegurl'):
eadc3ccd 1286 formats = self._extract_m3u8_formats(url, video_id, 'mp4')
f930e0c7
S
1287 elif format_id == 'f4m':
1288 formats = self._extract_f4m_formats(url, video_id)
eadc3ccd 1289 else:
1290 formats = [{
1291 'format_id': m.group('format_id'),
1292 'url': url,
1293 'vcodec': 'none' if m.group('type') == 'audio' else None
1294 }]
de6c51e8
S
1295 info_dict['direct'] = True
1296 info_dict['formats'] = formats
f930e0c7 1297 return info_dict
42393ce2 1298
d6e6a422 1299 if not self._downloader.params.get('test', False) and not is_intentional:
2fece970
S
1300 force = self._downloader.params.get('force_generic_extractor', False)
1301 self._downloader.report_warning(
1302 '%s on generic information extractor.' % ('Forcing' if force else 'Falling back'))
d6e6a422 1303
4e262a88 1304 if not full_response:
5c2266df 1305 request = sanitized_Request(url)
58bde34a
S
1306 # Some webservers may serve compressed content of rather big size (e.g. gzipped flac)
1307 # making it impossible to download only chunk of the file (yet we need only 512kB to
1308 # test whether it's HTML or not). According to youtube-dl default Accept-Encoding
1309 # that will always result in downloading the whole file that is not desirable.
1310 # Therefore for extraction pass we have to override Accept-Encoding to any in order
1311 # to accept raw bytes and being able to download only a chunk.
1312 # It may probably better to solve this by checking Content-Type for application/octet-stream
1313 # after HEAD request finishes, but not sure if we can rely on this.
1314 request.add_header('Accept-Encoding', '*')
1315 full_response = self._request_webpage(request, video_id)
4e262a88 1316
5940862d
S
1317 first_bytes = full_response.read(512)
1318
1319 # Is it an M3U playlist?
1320 if first_bytes.startswith('#EXTM3U'):
1321 info_dict['formats'] = self._extract_m3u8_formats(url, video_id, 'mp4')
1322 return info_dict
1323
4e262a88
PH
1324 # Maybe it's a direct link to a video?
1325 # Be careful not to download the whole thing!
61ca9a80 1326 if not is_html(first_bytes):
4e262a88
PH
1327 self._downloader.report_warning(
1328 'URL could be a direct video link, returning it as such.')
f930e0c7 1329 info_dict.update({
4e262a88
PH
1330 'direct': True,
1331 'url': url,
f930e0c7
S
1332 })
1333 return info_dict
4e262a88
PH
1334
1335 webpage = self._webpage_read_content(
1336 full_response, url, video_id, prefix=first_bytes)
1337
9b122384 1338 self.report_extraction(video_id)
887c6acd 1339
1b840245 1340 # Is it an RSS feed, a SMIL file, an XSPF playlist or a MPD manifest?
4fc946b5 1341 try:
f7854627 1342 doc = compat_etree_fromstring(webpage.encode('utf-8'))
4fc946b5
PH
1343 if doc.tag == 'rss':
1344 return self._extract_rss(url, video_id, doc)
e5e8d20a
S
1345 elif re.match(r'^(?:{[^}]+})?smil$', doc.tag):
1346 return self._parse_smil(doc, url, video_id)
729accb4
S
1347 elif doc.tag == '{http://xspf.org/ns/0/}playlist':
1348 return self.playlist_result(self._parse_xspf(doc, video_id), video_id)
1b840245 1349 elif re.match(r'(?i)^(?:{[^}]+})?MPD$', doc.tag):
f930e0c7
S
1350 info_dict['formats'] = self._parse_mpd_formats(
1351 doc, video_id, mpd_base_url=url.rpartition('/')[0])
1352 return info_dict
1353 elif re.match(r'^{http://ns\.adobe\.com/f4m/[12]\.0}manifest$', doc.tag):
1354 info_dict['formats'] = self._parse_f4m_formats(doc, url, video_id)
1355 return info_dict
f7300c5c 1356 except compat_xml_parse_error:
4fc946b5
PH
1357 pass
1358
c8e9a235
PH
1359 # Is it a Camtasia project?
1360 camtasia_res = self._extract_camtasia(url, video_id, webpage)
1361 if camtasia_res is not None:
1362 return camtasia_res
1363
14390730
S
1364 # Sometimes embedded video player is hidden behind percent encoding
1365 # (e.g. https://github.com/rg3/youtube-dl/issues/2448)
1366 # Unescaping the whole page allows to handle those cases in a generic way
45eedbe5 1367 webpage = compat_urllib_parse_unquote(webpage)
1f7659db 1368
887c6acd
PH
1369 # it's tempting to parse this further, but you would
1370 # have to take into account all the variations like
1371 # Video Title - Site Name
1372 # Site Name | Video Title
1373 # Video Title - Tagline | Site Name
1374 # and so on and so forth; it's just not practical
ef4fd848 1375 video_title = self._html_search_regex(
79649588
PH
1376 r'(?s)<title>(.*?)</title>', webpage, 'video title',
1377 default='video')
ef4fd848 1378
4d805e06
PH
1379 # Try to detect age limit automatically
1380 age_limit = self._rta_search(webpage)
1381 # And then there are the jokers who advertise that they use RTA,
1382 # but actually don't.
1383 AGE_LIMIT_MARKERS = [
1384 r'Proudly Labeled <a href="http://www.rtalabel.org/" title="Restricted to Adults">RTA</a>',
1385 ]
1386 if any(re.search(marker, webpage) for marker in AGE_LIMIT_MARKERS):
1387 age_limit = 18
1388
ef4fd848
PH
1389 # video uploader is domain name
1390 video_uploader = self._search_regex(
79649588 1391 r'^(?:https?://)?([^/]*)/.*', url, 'video uploader')
887c6acd 1392
ed2d6a19 1393 # Helper method
83992676 1394 def _playlist_from_matches(matches, getter=None, ie=None):
3b2f933b 1395 urlrs = orderedSet(
83992676 1396 self.url_result(self._proto_relative_url(getter(m) if getter else m), ie)
3b2f933b 1397 for m in matches)
ed2d6a19
PH
1398 return self.playlist_result(
1399 urlrs, playlist_id=video_id, playlist_title=video_title)
1400
1f4b722b 1401 # Look for Brightcove Legacy Studio embeds
4fcaa4f4 1402 bc_urls = BrightcoveLegacyIE._extract_brightcove_urls(webpage)
99877772 1403 if bc_urls:
79649588 1404 self.to_screen('Brightcove video detected.')
99877772
PH
1405 entries = [{
1406 '_type': 'url',
1407 'url': smuggle_url(bc_url, {'Referer': url}),
3b7d9aa4 1408 'ie_key': 'BrightcoveLegacy'
99877772
PH
1409 } for bc_url in bc_urls]
1410
1411 return {
1412 '_type': 'playlist',
1413 'title': video_title,
1414 'id': video_id,
1415 'entries': entries,
1416 }
cfe50f04 1417
f6519f89
S
1418 # Look for Brightcove New Studio embeds
1419 bc_urls = BrightcoveNewIE._extract_urls(webpage)
1420 if bc_urls:
1421 return _playlist_from_matches(bc_urls, ie='BrightcoveNew')
ed126900 1422
59b8ab58
PH
1423 # Look for embedded rtl.nl player
1424 matches = re.findall(
97b570a9 1425 r'<iframe[^>]+?src="((?:https?:)?//(?:www\.)?rtl\.nl/system/videoplayer/[^"]+(?:video_)?embed[^"]+)"',
59b8ab58
PH
1426 webpage)
1427 if matches:
1428 return _playlist_from_matches(matches, ie='RtlNl')
1429
b407e173
YCH
1430 vimeo_url = VimeoIE._extract_vimeo_url(url, webpage)
1431 if vimeo_url is not None:
1432 return self.url_result(vimeo_url)
7115ca84 1433
a1b85269
YCH
1434 vid_me_embed_url = self._search_regex(
1435 r'src=[\'"](https?://vid\.me/[^\'"]+)[\'"]',
1436 webpage, 'vid.me embed', default=None)
1437 if vid_me_embed_url is not None:
1438 return self.url_result(vid_me_embed_url, 'Vidme')
1439
53c1d3ef 1440 # Look for embedded YouTube player
1f9da904 1441 matches = re.findall(r'''(?x)
2b88feed
PH
1442 (?:
1443 <iframe[^>]+?src=|
c71dfccc 1444 data-video-url=|
2b88feed 1445 <embed[^>]+?src=|
a7e97f6d
PH
1446 embedSWF\(?:\s*|
1447 new\s+SWFObject\(
2b88feed
PH
1448 )
1449 (["\'])
1bf5423e 1450 (?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
6b08cdf6 1451 (?:embed|v|p)/.+?)
1f9da904 1452 \1''', webpage)
887c6acd 1453 if matches:
ed2d6a19 1454 return _playlist_from_matches(
3b2f933b 1455 matches, lambda m: unescapeHTML(m[1]))
53c1d3ef 1456
65f3a228
PH
1457 # Look for lazyYT YouTube embed
1458 matches = re.findall(
1459 r'class="lazyYT" data-youtube-id="([^"]+)"', webpage)
1460 if matches:
1461 return _playlist_from_matches(matches, lambda m: unescapeHTML(m))
1462
355e4fd0
PH
1463 # Look for embedded Dailymotion player
1464 matches = re.findall(
5a490592 1465 r'<(?:(?:embed|iframe)[^>]+?src=|input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=)(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/(?:embed|swf)/video/.+?)\1', webpage)
355e4fd0 1466 if matches:
ed2d6a19
PH
1467 return _playlist_from_matches(
1468 matches, lambda m: unescapeHTML(m[1]))
355e4fd0 1469
8489578d
NJ
1470 # Look for embedded Dailymotion playlist player (#3822)
1471 m = re.search(
1472 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.[a-z]{2,3}/widget/jukebox\?.+?)\1', webpage)
1473 if m:
1474 playlists = re.findall(
1475 r'list\[\]=/playlist/([^/]+)/', unescapeHTML(m.group('url')))
1476 if playlists:
1477 return _playlist_from_matches(
1478 playlists, lambda p: '//dailymotion.com/playlist/%s' % p)
1479
ef4fd848
PH
1480 # Look for embedded Wistia player
1481 match = re.search(
281d3f1d 1482 r'<(?:meta[^>]+?content|iframe[^>]+?src)=(["\'])(?P<url>(?:https?:)?//(?:fast\.)?wistia\.net/embed/iframe/.+?)\1', webpage)
ef4fd848 1483 if match:
9471c444
NJ
1484 embed_url = self._proto_relative_url(
1485 unescapeHTML(match.group('url')))
ef4fd848
PH
1486 return {
1487 '_type': 'url_transparent',
9471c444 1488 'url': embed_url,
ef4fd848
PH
1489 'ie_key': 'Wistia',
1490 'uploader': video_uploader,
1491 'title': video_title,
1492 'id': video_id,
1493 }
5f6a1245 1494
9471c444 1495 match = re.search(r'(?:id=["\']wistia_|data-wistia-?id=["\']|Wistia\.embed\(["\'])(?P<id>[^"\']+)', webpage)
746c67d7
NJ
1496 if match:
1497 return {
1498 '_type': 'url_transparent',
1499 'url': 'http://fast.wistia.net/embed/iframe/{0:}'.format(match.group('id')),
1500 'ie_key': 'Wistia',
1501 'uploader': video_uploader,
1502 'title': video_title,
1503 'id': match.group('id')
1504 }
ef4fd848 1505
bab19a8e
S
1506 # Look for SVT player
1507 svt_url = SVTIE._extract_url(webpage)
1508 if svt_url:
1509 return self.url_result(svt_url, 'SVT')
1510
fa35cdad
PH
1511 # Look for embedded condenast player
1512 matches = re.findall(
1513 r'<iframe\s+(?:[a-zA-Z-]+="[^"]+"\s+)*?src="(https?://player\.cnevids\.com/embed/[^"]+")',
1514 webpage)
1515 if matches:
1516 return {
1517 '_type': 'playlist',
1518 'entries': [{
1519 '_type': 'url',
1520 'ie_key': 'CondeNast',
1521 'url': ma,
1522 } for ma in matches],
1523 'title': video_title,
1524 'id': video_id,
1525 }
1526
c19f7764
JMF
1527 # Look for Bandcamp pages with custom domain
1528 mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage)
1529 if mobj is not None:
1530 burl = unescapeHTML(mobj.group(1))
09804265
JMF
1531 # Don't set the extractor because it can be a track url or an album
1532 return self.url_result(burl)
c19f7764 1533
f25571ff
PH
1534 # Look for embedded Vevo player
1535 mobj = re.search(
1536 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:cache\.)?vevo\.com/.+?)\1', webpage)
1537 if mobj is not None:
1538 return self.url_result(mobj.group('url'))
796df3c6
S
1539
1540 # Look for embedded Viddler player
cb454b33
S
1541 mobj = re.search(
1542 r'<(?:iframe[^>]+?src|param[^>]+?value)=(["\'])(?P<url>(?:https?:)?//(?:www\.)?viddler\.com/(?:embed|player)/.+?)\1',
1543 webpage)
796df3c6
S
1544 if mobj is not None:
1545 return self.url_result(mobj.group('url'))
f25571ff 1546
3378d67a
S
1547 # Look for NYTimes player
1548 mobj = re.search(
1549 r'<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//graphics8\.nytimes\.com/bcvideo/[^/]+/iframe/embed\.html.+?)\1>',
1550 webpage)
1551 if mobj is not None:
1552 return self.url_result(mobj.group('url'))
1553
cefdf970
S
1554 # Look for Libsyn player
1555 mobj = re.search(
1556 r'<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//html5-player\.libsyn\.com/embed/.+?)\1', webpage)
1557 if mobj is not None:
1558 return self.url_result(mobj.group('url'))
1559
c0d0b01f 1560 # Look for Ooyala videos
8a37aa15 1561 mobj = (re.search(r'player\.ooyala\.com/[^"?]+[?#][^"]*?(?:embedCode|ec)=(?P<ec>[^"&]+)', webpage) or
f076b638 1562 re.search(r'OO\.Player\.create\([\'"].*?[\'"],\s*[\'"](?P<ec>.{32})[\'"]', webpage) or
edfcf7ab
YCH
1563 re.search(r'SBN\.VideoLinkset\.ooyala\([\'"](?P<ec>.{32})[\'"]\)', webpage) or
1564 re.search(r'data-ooyala-video-id\s*=\s*[\'"](?P<ec>.{32})[\'"]', webpage))
c0d0b01f 1565 if mobj is not None:
cce9d15d 1566 return OoyalaIE._build_url_result(smuggle_url(mobj.group('ec'), {'domain': url}))
c0d0b01f 1567
f076b638 1568 # Look for multiple Ooyala embeds on SBN network websites
1569 mobj = re.search(r'SBN\.VideoLinkset\.entryGroup\((\[.*?\])', webpage)
1570 if mobj is not None:
1571 embeds = self._parse_json(mobj.group(1), video_id, fatal=False)
1572 if embeds:
1573 return _playlist_from_matches(
cce9d15d 1574 embeds, getter=lambda v: OoyalaIE._url_for_embed_code(smuggle_url(v['provider_video_id'], {'domain': url})), ie='Ooyala')
f076b638 1575
aa94a6d3 1576 # Look for Aparat videos
48099643 1577 mobj = re.search(r'<iframe .*?src="(http://www\.aparat\.com/video/[^"]+)"', webpage)
aa94a6d3
PH
1578 if mobj is not None:
1579 return self.url_result(mobj.group(1), 'Aparat')
1580
c93c2ab1 1581 # Look for MPORA videos
c3f51436 1582 mobj = re.search(r'<iframe .*?src="(http://mpora\.(?:com|de)/videos/[^"]+)"', webpage)
c93c2ab1
PH
1583 if mobj is not None:
1584 return self.url_result(mobj.group(1), 'Mpora')
5f59ee79 1585
15c0e8e7 1586 # Look for embedded NovaMov-based player
8f89e687 1587 mobj = re.search(
8dfa187b 1588 r'''(?x)<(?:pagespeed_)?iframe[^>]+?src=(["\'])
15c0e8e7
S
1589 (?P<url>http://(?:(?:embed|www)\.)?
1590 (?:novamov\.com|
1591 nowvideo\.(?:ch|sx|eu|at|ag|co)|
1592 videoweed\.(?:es|com)|
1593 movshare\.(?:net|sx|ag)|
1594 divxstage\.(?:eu|net|ch|co|at|ag))
1595 /embed\.php.+?)\1''', webpage)
8f89e687 1596 if mobj is not None:
15c0e8e7 1597 return self.url_result(mobj.group('url'))
50f56607 1598
9834872b
PH
1599 # Look for embedded Facebook player
1600 mobj = re.search(
db1f3888 1601 r'<iframe[^>]+?src=(["\'])(?P<url>https://www\.facebook\.com/video/embed.+?)\1', webpage)
9834872b
PH
1602 if mobj is not None:
1603 return self.url_result(mobj.group('url'), 'Facebook')
1604
ca97a56e
S
1605 # Look for embedded VK player
1606 mobj = re.search(r'<iframe[^>]+?src=(["\'])(?P<url>https?://vk\.com/video_ext\.php.+?)\1', webpage)
1607 if mobj is not None:
1608 return self.url_result(mobj.group('url'), 'VK')
1609
33d4fdab
S
1610 # Look for embedded Odnoklassniki player
1611 mobj = re.search(r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:odnoklassniki|ok)\.ru/videoembed/.+?)\1', webpage)
1612 if mobj is not None:
1613 return self.url_result(mobj.group('url'), 'Odnoklassniki')
1614
0364fa8b
S
1615 # Look for embedded ivi player
1616 mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
1617 if mobj is not None:
1618 return self.url_result(mobj.group('url'), 'Ivi')
1619
db1f3888
PH
1620 # Look for embedded Huffington Post player
1621 mobj = re.search(
c3f51436 1622 r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
db1f3888
PH
1623 if mobj is not None:
1624 return self.url_result(mobj.group('url'), 'HuffPost')
1625
1b86cc41 1626 # Look for embed.ly
1627 mobj = re.search(r'class=["\']embedly-card["\'][^>]href=["\'](?P<url>[^"\']+)', webpage)
1628 if mobj is not None:
1629 return self.url_result(mobj.group('url'))
1630 mobj = re.search(r'class=["\']embedly-embed["\'][^>]src=["\'][^"\']*url=(?P<url>[^&]+)', webpage)
1631 if mobj is not None:
f7e6f7fa 1632 return self.url_result(compat_urllib_parse_unquote(mobj.group('url')))
1b86cc41 1633
60cc4dc4
PH
1634 # Look for funnyordie embed
1635 matches = re.findall(r'<iframe[^>]+?src="(https?://(?:www\.)?funnyordie\.com/embed/[^"]+)"', webpage)
1636 if matches:
ed2d6a19
PH
1637 return _playlist_from_matches(
1638 matches, getter=unescapeHTML, ie='FunnyOrDie')
60cc4dc4 1639
db546cf8
S
1640 # Look for BBC iPlayer embed
1641 matches = re.findall(r'setPlaylist\("(https?://www\.bbc\.co\.uk/iplayer/[^/]+/[\da-z]{8})"\)', webpage)
1642 if matches:
476eae0c 1643 return _playlist_from_matches(matches, ie='BBCCoUk')
db546cf8 1644
93d020dd
S
1645 # Look for embedded RUTV player
1646 rutv_url = RUTVIE._extract_url(webpage)
1647 if rutv_url:
1648 return self.url_result(rutv_url, 'RUTV')
1649
494f20cb 1650 # Look for embedded TVC player
b8599718
S
1651 tvc_url = TVCIE._extract_url(webpage)
1652 if tvc_url:
1653 return self.url_result(tvc_url, 'TVC')
494f20cb 1654
d40a3b5b
S
1655 # Look for embedded SportBox player
1656 sportbox_urls = SportBoxEmbedIE._extract_urls(webpage)
1657 if sportbox_urls:
1658 return _playlist_from_matches(sportbox_urls, ie='SportBoxEmbed')
1659
78e2b74b 1660 # Look for embedded PornHub player
65d161c4
S
1661 pornhub_url = PornHubIE._extract_url(webpage)
1662 if pornhub_url:
1663 return self.url_result(pornhub_url, 'PornHub')
1664
2bb5b6d0
S
1665 # Look for embedded XHamster player
1666 xhamster_urls = XHamsterEmbedIE._extract_urls(webpage)
1667 if xhamster_urls:
1668 return _playlist_from_matches(xhamster_urls, ie='XHamsterEmbed')
1669
2c9ca782
S
1670 # Look for embedded TNAFlixNetwork player
1671 tnaflix_urls = TNAFlixNetworkEmbedIE._extract_urls(webpage)
1672 if tnaflix_urls:
1673 return _playlist_from_matches(tnaflix_urls, ie=TNAFlixNetworkEmbedIE.ie_key())
1674
9872d311
S
1675 # Look for embedded Tvigle player
1676 mobj = re.search(
1677 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//cloud\.tvigle\.ru/video/.+?)\1', webpage)
1678 if mobj is not None:
1679 return self.url_result(mobj.group('url'), 'Tvigle')
1680
7e2ede98
JMF
1681 # Look for embedded TED player
1682 mobj = re.search(
d7cc31b6 1683 r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed(?:-ssl)?\.ted\.com/.+?)\1', webpage)
7e2ede98
JMF
1684 if mobj is not None:
1685 return self.url_result(mobj.group('url'), 'TED')
1686
5c386252 1687 # Look for embedded Ustream videos
1688 mobj = re.search(
1689 r'<iframe[^>]+?src=(["\'])(?P<url>http://www\.ustream\.tv/embed/.+?)\1', webpage)
1690 if mobj is not None:
1691 return self.url_result(mobj.group('url'), 'Ustream')
1692
893f8832
PH
1693 # Look for embedded arte.tv player
1694 mobj = re.search(
1695 r'<script [^>]*?src="(?P<url>http://www\.arte\.tv/playerv2/embed[^"]+)"',
1696 webpage)
1697 if mobj is not None:
1698 return self.url_result(mobj.group('url'), 'ArteTVEmbed')
1699
cbd55ade
S
1700 # Look for embedded francetv player
1701 mobj = re.search(
1702 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?://)?embed\.francetv\.fr/\?ue=.+?)\1',
1703 webpage)
1704 if mobj is not None:
1705 return self.url_result(mobj.group('url'))
1706
cb3ac1c6
S
1707 # Look for embedded smotri.com player
1708 smotri_url = SmotriIE._extract_url(webpage)
1709 if smotri_url:
1710 return self.url_result(smotri_url, 'Smotri')
1711
e6c2d9ad 1712 # Look for embedded Myvi.ru player
6dd94d3a 1713 myvi_url = MyviIE._extract_url(webpage)
e6c2d9ad
S
1714 if myvi_url:
1715 return self.url_result(myvi_url)
1716
dfb1b146 1717 # Look for embedded soundcloud player
20991253 1718 mobj = re.search(
ac645ac7 1719 r'<iframe\s+(?:[a-zA-Z0-9_-]+="[^"]+"\s+)*src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
20991253
PH
1720 webpage)
1721 if mobj is not None:
1722 url = unescapeHTML(mobj.group('url'))
1723 return self.url_result(url)
1724
826ec77f
PH
1725 # Look for embedded vulture.com player
1726 mobj = re.search(
1727 r'<iframe src="(?P<url>https?://video\.vulture\.com/[^"]+)"',
1728 webpage)
1729 if mobj is not None:
1730 url = unescapeHTML(mobj.group('url'))
1731 return self.url_result(url, ie='Vulture')
1732
c5cd249e 1733 # Look for embedded mtvservices player
46fde8a1
S
1734 mtvservices_url = MTVServicesEmbeddedIE._extract_url(webpage)
1735 if mtvservices_url:
1736 return self.url_result(mtvservices_url, ie='MTVServicesEmbedded')
c5cd249e 1737
49807b4a
S
1738 # Look for embedded yahoo player
1739 mobj = re.search(
1740 r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:screen|movies)\.yahoo\.com/.+?\.html\?format=embed)\1',
1741 webpage)
1742 if mobj is not None:
1743 return self.url_result(mobj.group('url'), 'Yahoo')
1744
2ef6fcb5
PH
1745 # Look for embedded sbs.com.au player
1746 mobj = re.search(
e98b8e79
PH
1747 r'''(?x)
1748 (?:
1749 <meta\s+property="og:video"\s+content=|
1750 <iframe[^>]+?src=
1751 )
1752 (["\'])(?P<url>https?://(?:www\.)?sbs\.com\.au/ondemand/video/.+?)\1''',
2ef6fcb5
PH
1753 webpage)
1754 if mobj is not None:
1755 return self.url_result(mobj.group('url'), 'SBS')
1756
42bdd9d0
PH
1757 # Look for embedded Cinchcast player
1758 mobj = re.search(
1759 r'<iframe[^>]+?src=(["\'])(?P<url>https?://player\.cinchcast\.com/.+?)\1',
1760 webpage)
1761 if mobj is not None:
1762 return self.url_result(mobj.group('url'), 'Cinchcast')
1763
1a94ff68 1764 mobj = re.search(
5263cdfc 1765 r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
1a94ff68 1766 webpage)
8001607e
YCH
1767 if not mobj:
1768 mobj = re.search(
1769 r'data-video-link=["\'](?P<url>http://m.mlb.com/video/[^"\']+)',
1770 webpage)
1a94ff68
S
1771 if mobj is not None:
1772 return self.url_result(mobj.group('url'), 'MLB')
1773
1419fafd 1774 mobj = re.search(
dd467d33 1775 r'<(?:iframe|script)[^>]+?src=(["\'])(?P<url>%s)\1' % CondeNastIE.EMBED_URL,
1419fafd
S
1776 webpage)
1777 if mobj is not None:
1778 return self.url_result(self._proto_relative_url(mobj.group('url'), scheme='http:'), 'CondeNast')
1779
af63fed7
PH
1780 mobj = re.search(
1781 r'<iframe[^>]+src="(?P<url>https?://new\.livestream\.com/[^"]+/player[^"]+)"',
1782 webpage)
1783 if mobj is not None:
1784 return self.url_result(mobj.group('url'), 'Livestream')
1785
255fca5e
S
1786 # Look for Zapiks embed
1787 mobj = re.search(
1788 r'<iframe[^>]+src="(?P<url>https?://(?:www\.)?zapiks\.fr/index\.php\?.+?)"', webpage)
1789 if mobj is not None:
1790 return self.url_result(mobj.group('url'), 'Zapiks')
1791
e3216b82 1792 # Look for Kaltura embeds
6a5d6de1 1793 mobj = (re.search(r"(?s)kWidget\.(?:thumb)?[Ee]mbed\(\{.*?'wid'\s*:\s*'_?(?P<partner_id>[^']+)',.*?'entry_?[Ii]d'\s*:\s*'(?P<id>[^']+)',", webpage) or
1c31a5b0 1794 re.search(r'(?s)(?P<q1>["\'])(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com/.*?(?:p|partner_id)/(?P<partner_id>\d+).*?(?P=q1).*?entry_?[Ii]d\s*:\s*(?P<q2>["\'])(?P<id>.+?)(?P=q2)', webpage))
e3216b82 1795 if mobj is not None:
5b5fae5f
S
1796 return self.url_result(smuggle_url(
1797 'kaltura:%(partner_id)s:%(id)s' % mobj.groupdict(),
1798 {'source_url': url}), 'Kaltura')
e3216b82 1799
135c9c42
S
1800 # Look for Eagle.Platform embeds
1801 mobj = re.search(
1802 r'<iframe[^>]+src="(?P<url>https?://.+?\.media\.eagleplatform\.com/index/player\?.+?)"', webpage)
1803 if mobj is not None:
1804 return self.url_result(mobj.group('url'), 'EaglePlatform')
1805
d47ae7f6
S
1806 # Look for ClipYou (uses Eagle.Platform) embeds
1807 mobj = re.search(
1808 r'<iframe[^>]+src="https?://(?P<host>media\.clipyou\.ru)/index/player\?.*\brecord_id=(?P<id>\d+).*"', webpage)
1809 if mobj is not None:
1810 return self.url_result('eagleplatform:%(host)s:%(id)s' % mobj.groupdict(), 'EaglePlatform')
1811
f8388757 1812 # Look for Pladform embeds
45dad7ba
S
1813 pladform_url = PladformIE._extract_url(webpage)
1814 if pladform_url:
1815 return self.url_result(pladform_url)
f8388757 1816
ff18735c
S
1817 # Look for Videomore embeds
1818 videomore_url = VideomoreIE._extract_url(webpage)
1819 if videomore_url:
1820 return self.url_result(videomore_url)
1821
2dcc114f
S
1822 # Look for Playwire embeds
1823 mobj = re.search(
1824 r'<script[^>]+data-config=(["\'])(?P<url>(?:https?:)?//config\.playwire\.com/.+?)\1', webpage)
1825 if mobj is not None:
1826 return self.url_result(mobj.group('url'))
1827
ad320e9b
NJ
1828 # Look for 5min embeds
1829 mobj = re.search(
1830 r'<meta[^>]+property="og:video"[^>]+content="https?://embed\.5min\.com/(?P<id>[0-9]+)/?', webpage)
1831 if mobj is not None:
1832 return self.url_result('5min:%s' % mobj.group('id'), 'FiveMin')
1833
18153f1b
S
1834 # Look for Crooks and Liars embeds
1835 mobj = re.search(
1836 r'<(?:iframe[^>]+src|param[^>]+value)=(["\'])(?P<url>(?:https?:)?//embed\.crooksandliars\.com/(?:embed|v)/.+?)\1', webpage)
1837 if mobj is not None:
1838 return self.url_result(mobj.group('url'))
1839
a2edf2e7
YCH
1840 # Look for NBC Sports VPlayer embeds
1841 nbc_sports_url = NBCSportsVPlayerIE._extract_url(webpage)
1842 if nbc_sports_url:
1843 return self.url_result(nbc_sports_url, 'NBCSportsVPlayer')
1844
653789af 1845 # Look for Google Drive embeds
5b251628 1846 google_drive_url = GoogleDriveIE._extract_url(webpage)
653789af 1847 if google_drive_url:
1848 return self.url_result(google_drive_url, 'GoogleDrive')
1849
418c5cc3
YCH
1850 # Look for UDN embeds
1851 mobj = re.search(
c39fd7b1 1852 r'<iframe[^>]+src="(?P<url>%s)"' % UDNEmbedIE._PROTOCOL_RELATIVE_VALID_URL, webpage)
418c5cc3
YCH
1853 if mobj is not None:
1854 return self.url_result(
0a160363 1855 compat_urlparse.urljoin(url, mobj.group('url')), 'UDNEmbed')
418c5cc3 1856
2fe1b5bd
YCH
1857 # Look for Senate ISVP iframe
1858 senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
1859 if senate_isvp_url:
25c3a734 1860 return self.url_result(senate_isvp_url, 'SenateISVP')
2fe1b5bd 1861
756f574e
YCH
1862 # Look for Dailymotion Cloud videos
1863 dmcloud_url = DailymotionCloudIE._extract_dmcloud_url(webpage)
1864 if dmcloud_url:
1865 return self.url_result(dmcloud_url, 'DailymotionCloud')
1866
1ac1c4c2
S
1867 # Look for OnionStudios embeds
1868 onionstudios_url = OnionStudiosIE._extract_url(webpage)
1869 if onionstudios_url:
1870 return self.url_result(onionstudios_url)
1871
eedd20ef
S
1872 # Look for SnagFilms embeds
1873 snagfilms_url = SnagFilmsEmbedIE._extract_url(webpage)
1874 if snagfilms_url:
1875 return self.url_result(snagfilms_url)
1876
7cb09524 1877 # Look for JWPlatform embeds
1878 jwplatform_url = JWPlatformIE._extract_url(webpage)
1879 if jwplatform_url:
1880 return self.url_result(jwplatform_url, 'JWPlatform')
1881
8ca31a0e 1882 # Look for ScreenwaveMedia embeds
efd712c6 1883 mobj = re.search(ScreenwaveMediaIE.EMBED_PATTERN, webpage)
8ca31a0e 1884 if mobj is not None:
efd712c6 1885 return self.url_result(unescapeHTML(mobj.group('url')), 'ScreenwaveMedia')
8ca31a0e 1886
aecfcd4e
S
1887 # Look for Digiteka embeds
1888 digiteka_url = DigitekaIE._extract_url(webpage)
1889 if digiteka_url:
1890 return self.url_result(self._proto_relative_url(digiteka_url), DigitekaIE.ie_key())
6aeba407 1891
1bf996fa 1892 # Look for Limelight embeds
1893 mobj = re.search(r'LimelightPlayer\.doLoad(Media|Channel|ChannelList)\(["\'](?P<id>[a-z0-9]{32})', webpage)
1894 if mobj:
1895 lm = {
1896 'Media': 'media',
1897 'Channel': 'channel',
1898 'ChannelList': 'channel_list',
1899 }
1900 return self.url_result('limelight:%s:%s' % (
1901 lm[mobj.group(1)], mobj.group(2)), 'Limelight%s' % mobj.group(1), mobj.group(2))
1902
a5158f38
YCH
1903 # Look for AdobeTVVideo embeds
1904 mobj = re.search(
1905 r'<iframe[^>]+src=[\'"]((?:https?:)?//video\.tv\.adobe\.com/v/\d+[^"]+)[\'"]',
1906 webpage)
1907 if mobj is not None:
1908 return self.url_result(
1909 self._proto_relative_url(unescapeHTML(mobj.group(1))),
1910 'AdobeTVVideo')
1911
ced659bb 1912 def check_video(vurl):
a0f71985
PH
1913 if YoutubeIE.suitable(vurl):
1914 return True
ced659bb
S
1915 vpath = compat_urlparse.urlparse(vurl).path
1916 vext = determine_ext(vpath)
1917 return '.' in vpath and vext not in ('swf', 'png', 'jpg', 'srt', 'sbv', 'sub', 'vtt', 'ttml')
1918
1919 def filter_video(urls):
1920 return list(filter(check_video, urls))
1921
9b122384 1922 # Start with something easy: JW Player in SWFObject
ced659bb 1923 found = filter_video(re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage))
b30b8698 1924 if not found:
d981cef6 1925 # Look for gorilla-vid style embedding
ced659bb 1926 found = filter_video(re.findall(r'''(?sx)
c0292e8a
PH
1927 (?:
1928 jw_plugins|
1929 JWPlayerOptions|
1930 jwplayer\s*\(\s*["'][^'"]+["']\s*\)\s*\.setup
1931 )
a0f71985
PH
1932 .*?
1933 ['"]?file['"]?\s*:\s*["\'](.*?)["\']''', webpage))
b30b8698 1934 if not found:
9b122384 1935 # Broaden the search a little bit
ced659bb 1936 found = filter_video(re.findall(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage))
b30b8698
PH
1937 if not found:
1938 # Broaden the findall a little bit: JWPlayer JS loader
ced659bb 1939 found = filter_video(re.findall(
54a9328b 1940 r'[^A-Za-z0-9]?(?:file|video_url)["\']?:\s*["\'](http(?![^\'"]+\.[0-9]+[\'"])[^\'"]+)["\']', webpage))
4d805e06
PH
1941 if not found:
1942 # Flow player
ced659bb 1943 found = filter_video(re.findall(r'''(?xs)
4d805e06
PH
1944 flowplayer\("[^"]+",\s*
1945 \{[^}]+?\}\s*,
52585fd6 1946 \s*\{[^}]+? ["']?clip["']?\s*:\s*\{\s*
4d805e06 1947 ["']?url["']?\s*:\s*["']([^"']+)["']
ced659bb 1948 ''', webpage))
501f13fb
PH
1949 if not found:
1950 # Cinerama player
1951 found = re.findall(
1952 r"cinerama\.embedPlayer\(\s*\'[^']+\',\s*'([^']+)'", webpage)
b30b8698 1953 if not found:
9b122384 1954 # Try to find twitter cards info
ced659bb
S
1955 found = filter_video(re.findall(
1956 r'<meta (?:property|name)="twitter:player:stream" (?:content|value)="(.+?)"', webpage))
b30b8698 1957 if not found:
9b122384
PH
1958 # We look for Open Graph info:
1959 # We have to match any number spaces between elements, some sites try to align them (eg.: statigr.am)
b30b8698 1960 m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
9b122384
PH
1961 # We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
1962 if m_video_type is not None:
ced659bb 1963 found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
b30b8698 1964 if not found:
7fea7156 1965 # HTML5 video
12439dd5 1966 found = re.findall(r'(?s)<(?:video|audio)[^<]*(?:>.*?<source[^>]*)?\s+src=["\'](.*?)["\']', webpage)
b30b8698 1967 if not found:
ed9a25dd 1968 REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
a5a45015 1969 found = re.search(
89ef304b 1970 r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
ed9a25dd 1971 r'(?:[a-z-]+="[^"]+"\s+)*?content="%s' % REDIRECT_REGEX,
89ef304b 1972 webpage)
84f81016
S
1973 if not found:
1974 # Look also in Refresh HTTP header
1975 refresh_header = head_response.headers.get('Refresh')
1976 if refresh_header:
6c91a5a7
S
1977 # In python 2 response HTTP headers are bytestrings
1978 if sys.version_info < (3, 0) and isinstance(refresh_header, str):
1979 refresh_header = refresh_header.decode('iso-8859-1')
ed9a25dd 1980 found = re.search(REDIRECT_REGEX, refresh_header)
b30b8698 1981 if found:
b37317d8 1982 new_url = compat_urlparse.urljoin(url, unescapeHTML(found.group(1)))
89ef304b
PH
1983 self.report_following_redirect(new_url)
1984 return {
1985 '_type': 'url',
1986 'url': new_url,
1987 }
b30b8698 1988 if not found:
416c7fcb 1989 raise UnsupportedError(url)
9b122384 1990
b30b8698
PH
1991 entries = []
1992 for video_url in found:
6cc37c69 1993 video_url = video_url.replace('\\/', '/')
b30b8698 1994 video_url = compat_urlparse.urljoin(url, video_url)
f7e6f7fa 1995 video_id = compat_urllib_parse_unquote(os.path.basename(video_url))
9b122384 1996
b30b8698
PH
1997 # Sometimes, jwplayer extraction will result in a YouTube URL
1998 if YoutubeIE.suitable(video_url):
1999 entries.append(self.url_result(video_url, 'Youtube'))
2000 continue
9b122384 2001
b30b8698
PH
2002 # here's a fun little line of code for you:
2003 video_id = os.path.splitext(video_id)[0]
fc9713a1 2004
28602e74
YCH
2005 entry_info_dict = {
2006 'id': video_id,
2007 'uploader': video_uploader,
2008 'title': video_title,
2009 'age_limit': age_limit,
2010 }
2011
729accb4
S
2012 ext = determine_ext(video_url)
2013 if ext == 'smil':
28602e74 2014 entry_info_dict['formats'] = self._extract_smil_formats(video_url, video_id)
729accb4
S
2015 elif ext == 'xspf':
2016 return self.playlist_result(self._extract_xspf_playlist(video_url, video_id), video_id)
750b9ff0
YCH
2017 elif ext == 'm3u8':
2018 entry_info_dict['formats'] = self._extract_m3u8_formats(video_url, video_id, ext='mp4')
79a35085
S
2019 elif ext == 'mpd':
2020 entry_info_dict['formats'] = self._extract_mpd_formats(video_url, video_id)
3f2f4a94
S
2021 elif ext == 'f4m':
2022 entry_info_dict['formats'] = self._extract_f4m_formats(video_url, video_id)
d6fd958c 2023 else:
28602e74
YCH
2024 entry_info_dict['url'] = video_url
2025
2026 entries.append(entry_info_dict)
b30b8698
PH
2027
2028 if len(entries) == 1:
669f0e7c 2029 return entries[0]
b30b8698
PH
2030 else:
2031 for num, e in enumerate(entries, start=1):
13d8fbef
JMF
2032 # 'url' results don't have a title
2033 if e.get('title') is not None:
2034 e['title'] = '%s (%d)' % (e['title'], num)
b30b8698
PH
2035 return {
2036 '_type': 'playlist',
2037 'entries': entries,
2038 }