]> jfr.im git - yt-dlp.git/blob - youtube_dl/extractor/generic.py
[ssa] Add extractor (Closes #5169)
[yt-dlp.git] / youtube_dl / extractor / generic.py
1 # encoding: utf-8
2
3 from __future__ import unicode_literals
4
5 import os
6 import re
7
8 from .common import InfoExtractor
9 from .youtube import YoutubeIE
10 from ..compat import (
11 compat_urllib_parse,
12 compat_urlparse,
13 compat_xml_parse_error,
14 )
15 from ..utils import (
16 determine_ext,
17 ExtractorError,
18 float_or_none,
19 HEADRequest,
20 is_html,
21 orderedSet,
22 parse_xml,
23 smuggle_url,
24 unescapeHTML,
25 unified_strdate,
26 unsmuggle_url,
27 UnsupportedError,
28 url_basename,
29 xpath_text,
30 )
31 from .brightcove import BrightcoveIE
32 from .ooyala import OoyalaIE
33 from .rutv import RUTVIE
34 from .smotri import SmotriIE
35 from .condenast import CondeNastIE
36
37
38 class GenericIE(InfoExtractor):
39 IE_DESC = 'Generic downloader that works on some sites'
40 _VALID_URL = r'.*'
41 IE_NAME = 'generic'
42 _TESTS = [
43 {
44 'url': 'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html',
45 'md5': '85b90ccc9d73b4acd9138d3af4c27f89',
46 'info_dict': {
47 'id': '13601338388002',
48 'ext': 'mp4',
49 'uploader': 'www.hodiho.fr',
50 'title': 'R\u00e9gis plante sa Jeep',
51 }
52 },
53 # bandcamp page with custom domain
54 {
55 'add_ie': ['Bandcamp'],
56 'url': 'http://bronyrock.com/track/the-pony-mash',
57 'info_dict': {
58 'id': '3235767654',
59 'ext': 'mp3',
60 'title': 'The Pony Mash',
61 'uploader': 'M_Pallante',
62 },
63 'skip': 'There is a limit of 200 free downloads / month for the test song',
64 },
65 # embedded brightcove video
66 # it also tests brightcove videos that need to set the 'Referer' in the
67 # http requests
68 {
69 'add_ie': ['Brightcove'],
70 'url': 'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/',
71 'info_dict': {
72 'id': '2765128793001',
73 'ext': 'mp4',
74 'title': 'Le cours de bourse : l’analyse technique',
75 'description': 'md5:7e9ad046e968cb2d1114004aba466fd9',
76 'uploader': 'BFM BUSINESS',
77 },
78 'params': {
79 'skip_download': True,
80 },
81 },
82 {
83 # https://github.com/rg3/youtube-dl/issues/2253
84 'url': 'http://bcove.me/i6nfkrc3',
85 'md5': '0ba9446db037002366bab3b3eb30c88c',
86 'info_dict': {
87 'id': '3101154703001',
88 'ext': 'mp4',
89 'title': 'Still no power',
90 'uploader': 'thestar.com',
91 '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.',
92 },
93 'add_ie': ['Brightcove'],
94 },
95 {
96 'url': 'http://www.championat.com/video/football/v/87/87499.html',
97 'md5': 'fb973ecf6e4a78a67453647444222983',
98 'info_dict': {
99 'id': '3414141473001',
100 'ext': 'mp4',
101 'title': 'Видео. Удаление Дзагоева (ЦСКА)',
102 'description': 'Онлайн-трансляция матча ЦСКА - "Волга"',
103 'uploader': 'Championat',
104 },
105 },
106 {
107 # https://github.com/rg3/youtube-dl/issues/3541
108 'add_ie': ['Brightcove'],
109 'url': 'http://www.kijk.nl/sbs6/leermijvrouwenkennen/videos/jqMiXKAYan2S/aflevering-1',
110 'info_dict': {
111 'id': '3866516442001',
112 'ext': 'mp4',
113 'title': 'Leer mij vrouwen kennen: Aflevering 1',
114 'description': 'Leer mij vrouwen kennen: Aflevering 1',
115 'uploader': 'SBS Broadcasting',
116 },
117 'skip': 'Restricted to Netherlands',
118 'params': {
119 'skip_download': True, # m3u8 download
120 },
121 },
122 # Direct link to a video
123 {
124 'url': 'http://media.w3.org/2010/05/sintel/trailer.mp4',
125 'md5': '67d406c2bcb6af27fa886f31aa934bbe',
126 'info_dict': {
127 'id': 'trailer',
128 'ext': 'mp4',
129 'title': 'trailer',
130 'upload_date': '20100513',
131 }
132 },
133 # ooyala video
134 {
135 'url': 'http://www.rollingstone.com/music/videos/norwegian-dj-cashmere-cat-goes-spartan-on-with-me-premiere-20131219',
136 'md5': '166dd577b433b4d4ebfee10b0824d8ff',
137 'info_dict': {
138 'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ',
139 'ext': 'mp4',
140 'title': '2cc213299525360.mov', # that's what we get
141 },
142 'add_ie': ['Ooyala'],
143 },
144 # multiple ooyala embeds on SBN network websites
145 {
146 'url': 'http://www.sbnation.com/college-football-recruiting/2015/2/3/7970291/national-signing-day-rationalizations-itll-be-ok-itll-be-ok',
147 'info_dict': {
148 'id': 'national-signing-day-rationalizations-itll-be-ok-itll-be-ok',
149 'title': '25 lies you will tell yourself on National Signing Day - SBNation.com',
150 },
151 'playlist_mincount': 3,
152 'params': {
153 'skip_download': True,
154 },
155 'add_ie': ['Ooyala'],
156 },
157 # google redirect
158 {
159 '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',
160 'info_dict': {
161 'id': 'cmQHVoWB5FY',
162 'ext': 'mp4',
163 'upload_date': '20130224',
164 'uploader_id': 'TheVerge',
165 'description': 're:^Chris Ziegler takes a look at the\.*',
166 'uploader': 'The Verge',
167 'title': 'First Firefox OS phones side-by-side',
168 },
169 'params': {
170 'skip_download': False,
171 }
172 },
173 # embed.ly video
174 {
175 'url': 'http://www.tested.com/science/weird/460206-tested-grinding-coffee-2000-frames-second/',
176 'info_dict': {
177 'id': '9ODmcdjQcHQ',
178 'ext': 'mp4',
179 'title': 'Tested: Grinding Coffee at 2000 Frames Per Second',
180 'upload_date': '20140225',
181 'description': 'md5:06a40fbf30b220468f1e0957c0f558ff',
182 'uploader': 'Tested',
183 'uploader_id': 'testedcom',
184 },
185 # No need to test YoutubeIE here
186 'params': {
187 'skip_download': True,
188 },
189 },
190 # funnyordie embed
191 {
192 'url': 'http://www.theguardian.com/world/2014/mar/11/obama-zach-galifianakis-between-two-ferns',
193 'info_dict': {
194 'id': '18e820ec3f',
195 'ext': 'mp4',
196 'title': 'Between Two Ferns with Zach Galifianakis: President Barack Obama',
197 'description': 'Episode 18: President Barack Obama sits down with Zach Galifianakis for his most memorable interview yet.',
198 },
199 },
200 # BBC iPlayer embeds
201 {
202 'url': 'http://www.bbc.co.uk/blogs/adamcurtis/posts/BUGGER',
203 'info_dict': {
204 'title': 'BBC - Blogs - Adam Curtis - BUGGER',
205 },
206 'playlist_mincount': 18,
207 },
208 # RUTV embed
209 {
210 'url': 'http://www.rg.ru/2014/03/15/reg-dfo/anklav-anons.html',
211 'info_dict': {
212 'id': '776940',
213 'ext': 'mp4',
214 'title': 'Охотское море стало целиком российским',
215 'description': 'md5:5ed62483b14663e2a95ebbe115eb8f43',
216 },
217 'params': {
218 # m3u8 download
219 'skip_download': True,
220 },
221 },
222 # Embedded TED video
223 {
224 'url': 'http://en.support.wordpress.com/videos/ted-talks/',
225 'md5': '65fdff94098e4a607385a60c5177c638',
226 'info_dict': {
227 'id': '1969',
228 'ext': 'mp4',
229 'title': 'Hidden miracles of the natural world',
230 'uploader': 'Louie Schwartzberg',
231 'description': 'md5:8145d19d320ff3e52f28401f4c4283b9',
232 }
233 },
234 # Embeded Ustream video
235 {
236 'url': 'http://www.american.edu/spa/pti/nsa-privacy-janus-2014.cfm',
237 'md5': '27b99cdb639c9b12a79bca876a073417',
238 'info_dict': {
239 'id': '45734260',
240 'ext': 'flv',
241 'uploader': 'AU SPA: The NSA and Privacy',
242 'title': 'NSA and Privacy Forum Debate featuring General Hayden and Barton Gellman'
243 }
244 },
245 # nowvideo embed hidden behind percent encoding
246 {
247 'url': 'http://www.waoanime.tv/the-super-dimension-fortress-macross-episode-1/',
248 'md5': '2baf4ddd70f697d94b1c18cf796d5107',
249 'info_dict': {
250 'id': '06e53103ca9aa',
251 'ext': 'flv',
252 'title': 'Macross Episode 001 Watch Macross Episode 001 onl',
253 'description': 'No description',
254 },
255 },
256 # arte embed
257 {
258 'url': 'http://www.tv-replay.fr/redirection/20-03-14/x-enius-arte-10753389.html',
259 'md5': '7653032cbb25bf6c80d80f217055fa43',
260 'info_dict': {
261 'id': '048195-004_PLUS7-F',
262 'ext': 'flv',
263 'title': 'X:enius',
264 'description': 'md5:d5fdf32ef6613cdbfd516ae658abf168',
265 'upload_date': '20140320',
266 },
267 'params': {
268 'skip_download': 'Requires rtmpdump'
269 }
270 },
271 # Condé Nast embed
272 {
273 'url': 'http://www.wired.com/2014/04/honda-asimo/',
274 'md5': 'ba0dfe966fa007657bd1443ee672db0f',
275 'info_dict': {
276 'id': '53501be369702d3275860000',
277 'ext': 'mp4',
278 'title': 'Honda’s New Asimo Robot Is More Human Than Ever',
279 }
280 },
281 # Dailymotion embed
282 {
283 'url': 'http://www.spi0n.com/zap-spi0n-com-n216/',
284 'md5': '441aeeb82eb72c422c7f14ec533999cd',
285 'info_dict': {
286 'id': 'k2mm4bCdJ6CQ2i7c8o2',
287 'ext': 'mp4',
288 'title': 'Le Zap de Spi0n n°216 - Zapping du Web',
289 'uploader': 'Spi0n',
290 },
291 'add_ie': ['Dailymotion'],
292 },
293 # YouTube embed
294 {
295 'url': 'http://www.badzine.de/ansicht/datum/2014/06/09/so-funktioniert-die-neue-englische-badminton-liga.html',
296 'info_dict': {
297 'id': 'FXRb4ykk4S0',
298 'ext': 'mp4',
299 'title': 'The NBL Auction 2014',
300 'uploader': 'BADMINTON England',
301 'uploader_id': 'BADMINTONEvents',
302 'upload_date': '20140603',
303 'description': 'md5:9ef128a69f1e262a700ed83edb163a73',
304 },
305 'add_ie': ['Youtube'],
306 'params': {
307 'skip_download': True,
308 }
309 },
310 # MTVSercices embed
311 {
312 'url': 'http://www.gametrailers.com/news-post/76093/north-america-europe-is-getting-that-mario-kart-8-mercedes-dlc-too',
313 'md5': '35727f82f58c76d996fc188f9755b0d5',
314 'info_dict': {
315 'id': '0306a69b-8adf-4fb5-aace-75f8e8cbfca9',
316 'ext': 'mp4',
317 'title': 'Review',
318 'description': 'Mario\'s life in the fast lane has never looked so good.',
319 },
320 },
321 # YouTube embed via <data-embed-url="">
322 {
323 'url': 'https://play.google.com/store/apps/details?id=com.gameloft.android.ANMP.GloftA8HM',
324 'info_dict': {
325 'id': '4vAffPZIT44',
326 'ext': 'mp4',
327 'title': 'Asphalt 8: Airborne - Update - Welcome to Dubai!',
328 'uploader': 'Gameloft',
329 'uploader_id': 'gameloft',
330 'upload_date': '20140828',
331 'description': 'md5:c80da9ed3d83ae6d1876c834de03e1c4',
332 },
333 'params': {
334 'skip_download': True,
335 }
336 },
337 # Camtasia studio
338 {
339 'url': 'http://www.ll.mit.edu/workshops/education/videocourses/antennas/lecture1/video/',
340 'playlist': [{
341 'md5': '0c5e352edabf715d762b0ad4e6d9ee67',
342 'info_dict': {
343 'id': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final',
344 'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final - video1',
345 'ext': 'flv',
346 'duration': 2235.90,
347 }
348 }, {
349 'md5': '10e4bb3aaca9fd630e273ff92d9f3c63',
350 'info_dict': {
351 'id': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final_PIP',
352 'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final - pip',
353 'ext': 'flv',
354 'duration': 2235.93,
355 }
356 }],
357 'info_dict': {
358 'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final',
359 }
360 },
361 # Flowplayer
362 {
363 'url': 'http://www.handjobhub.com/video/busty-blonde-siri-tit-fuck-while-wank-6313.html',
364 'md5': '9d65602bf31c6e20014319c7d07fba27',
365 'info_dict': {
366 'id': '5123ea6d5e5a7',
367 'ext': 'mp4',
368 'age_limit': 18,
369 'uploader': 'www.handjobhub.com',
370 'title': 'Busty Blonde Siri Tit Fuck While Wank at HandjobHub.com',
371 }
372 },
373 # RSS feed
374 {
375 'url': 'http://phihag.de/2014/youtube-dl/rss2.xml',
376 'info_dict': {
377 'id': 'http://phihag.de/2014/youtube-dl/rss2.xml',
378 'title': 'Zero Punctuation',
379 'description': 're:.*groundbreaking video review series.*'
380 },
381 'playlist_mincount': 11,
382 },
383 # Multiple brightcove videos
384 # https://github.com/rg3/youtube-dl/issues/2283
385 {
386 'url': 'http://www.newyorker.com/online/blogs/newsdesk/2014/01/always-never-nuclear-command-and-control.html',
387 'info_dict': {
388 'id': 'always-never',
389 'title': 'Always / Never - The New Yorker',
390 },
391 'playlist_count': 3,
392 'params': {
393 'extract_flat': False,
394 'skip_download': True,
395 }
396 },
397 # MLB embed
398 {
399 'url': 'http://umpire-empire.com/index.php/topic/58125-laz-decides-no-thats-low/',
400 'md5': '96f09a37e44da40dd083e12d9a683327',
401 'info_dict': {
402 'id': '33322633',
403 'ext': 'mp4',
404 'title': 'Ump changes call to ball',
405 'description': 'md5:71c11215384298a172a6dcb4c2e20685',
406 'duration': 48,
407 'timestamp': 1401537900,
408 'upload_date': '20140531',
409 'thumbnail': 're:^https?://.*\.jpg$',
410 },
411 },
412 # Wistia embed
413 {
414 'url': 'http://education-portal.com/academy/lesson/north-american-exploration-failed-colonies-of-spain-france-england.html#lesson',
415 'md5': '8788b683c777a5cf25621eaf286d0c23',
416 'info_dict': {
417 'id': '1cfaf6b7ea',
418 'ext': 'mov',
419 'title': 'md5:51364a8d3d009997ba99656004b5e20d',
420 'duration': 643.0,
421 'filesize': 182808282,
422 'uploader': 'education-portal.com',
423 },
424 },
425 {
426 'url': 'http://thoughtworks.wistia.com/medias/uxjb0lwrcz',
427 'md5': 'baf49c2baa8a7de5f3fc145a8506dcd4',
428 'info_dict': {
429 'id': 'uxjb0lwrcz',
430 'ext': 'mp4',
431 'title': 'Conversation about Hexagonal Rails Part 1 - ThoughtWorks',
432 'duration': 1715.0,
433 'uploader': 'thoughtworks.wistia.com',
434 },
435 },
436 # Direct download with broken HEAD
437 {
438 'url': 'http://ai-radio.org:8000/radio.opus',
439 'info_dict': {
440 'id': 'radio',
441 'ext': 'opus',
442 'title': 'radio',
443 },
444 'params': {
445 'skip_download': True, # infinite live stream
446 },
447 'expected_warnings': [
448 r'501.*Not Implemented'
449 ],
450 },
451 # Soundcloud embed
452 {
453 'url': 'http://nakedsecurity.sophos.com/2014/10/29/sscc-171-are-you-sure-that-1234-is-a-bad-password-podcast/',
454 'info_dict': {
455 'id': '174391317',
456 'ext': 'mp3',
457 'description': 'md5:ff867d6b555488ad3c52572bb33d432c',
458 'uploader': 'Sophos Security',
459 'title': 'Chet Chat 171 - Oct 29, 2014',
460 'upload_date': '20141029',
461 }
462 },
463 # Livestream embed
464 {
465 'url': 'http://www.esa.int/Our_Activities/Space_Science/Rosetta/Philae_comet_touch-down_webcast',
466 'info_dict': {
467 'id': '67864563',
468 'ext': 'flv',
469 'upload_date': '20141112',
470 'title': 'Rosetta #CometLanding webcast HL 10',
471 }
472 },
473 # LazyYT
474 {
475 'url': 'http://discourse.ubuntu.com/t/unity-8-desktop-mode-windows-on-mir/1986',
476 'info_dict': {
477 'id': '1986',
478 'title': 'Unity 8 desktop-mode windows on Mir! - Ubuntu Discourse',
479 },
480 'playlist_mincount': 2,
481 },
482 # Direct link with incorrect MIME type
483 {
484 'url': 'http://ftp.nluug.nl/video/nluug/2014-11-20_nj14/zaal-2/5_Lennart_Poettering_-_Systemd.webm',
485 'md5': '4ccbebe5f36706d85221f204d7eb5913',
486 'info_dict': {
487 'url': 'http://ftp.nluug.nl/video/nluug/2014-11-20_nj14/zaal-2/5_Lennart_Poettering_-_Systemd.webm',
488 'id': '5_Lennart_Poettering_-_Systemd',
489 'ext': 'webm',
490 'title': '5_Lennart_Poettering_-_Systemd',
491 'upload_date': '20141120',
492 },
493 'expected_warnings': [
494 'URL could be a direct video link, returning it as such.'
495 ]
496 },
497 # Cinchcast embed
498 {
499 'url': 'http://undergroundwellness.com/podcasts/306-5-steps-to-permanent-gut-healing/',
500 'info_dict': {
501 'id': '7141703',
502 'ext': 'mp3',
503 'upload_date': '20141126',
504 'title': 'Jack Tips: 5 Steps to Permanent Gut Healing',
505 }
506 },
507 # Cinerama player
508 {
509 'url': 'http://www.abc.net.au/7.30/content/2015/s4164797.htm',
510 'info_dict': {
511 'id': '730m_DandD_1901_512k',
512 'ext': 'mp4',
513 'uploader': 'www.abc.net.au',
514 'title': 'Game of Thrones with dice - Dungeons and Dragons fantasy role-playing game gets new life - 19/01/2015',
515 }
516 },
517 # embedded viddler video
518 {
519 'url': 'http://deadspin.com/i-cant-stop-watching-john-wall-chop-the-nuggets-with-th-1681801597',
520 'info_dict': {
521 'id': '4d03aad9',
522 'ext': 'mp4',
523 'uploader': 'deadspin',
524 'title': 'WALL-TO-GORTAT',
525 'timestamp': 1422285291,
526 'upload_date': '20150126',
527 },
528 'add_ie': ['Viddler'],
529 },
530 # jwplayer YouTube
531 {
532 'url': 'http://media.nationalarchives.gov.uk/index.php/webinar-using-discovery-national-archives-online-catalogue/',
533 'info_dict': {
534 'id': 'Mrj4DVp2zeA',
535 'ext': 'mp4',
536 'upload_date': '20150212',
537 'uploader': 'The National Archives UK',
538 'description': 'md5:a236581cd2449dd2df4f93412f3f01c6',
539 'uploader_id': 'NationalArchives08',
540 'title': 'Webinar: Using Discovery, The National Archives’ online catalogue',
541 },
542 },
543 # rtl.nl embed
544 {
545 'url': 'http://www.rtlnieuws.nl/nieuws/buitenland/aanslagen-kopenhagen',
546 'playlist_mincount': 5,
547 'info_dict': {
548 'id': 'aanslagen-kopenhagen',
549 'title': 'Aanslagen Kopenhagen | RTL Nieuws',
550 }
551 },
552 # Zapiks embed
553 {
554 'url': 'http://www.skipass.com/news/116090-bon-appetit-s5ep3-baqueira-mi-cor.html',
555 'info_dict': {
556 'id': '118046',
557 'ext': 'mp4',
558 'title': 'EP3S5 - Bon Appétit - Baqueira Mi Corazon !',
559 }
560 },
561 # Kaltura embed
562 {
563 'url': 'http://www.monumentalnetwork.com/videos/john-carlson-postgame-2-25-15',
564 'info_dict': {
565 'id': '1_eergr3h1',
566 'ext': 'mp4',
567 'upload_date': '20150226',
568 'uploader_id': 'MonumentalSports-Kaltura@perfectsensedigital.com',
569 'timestamp': int,
570 'title': 'John Carlson Postgame 2/25/15',
571 },
572 },
573 # Eagle.Platform embed (generic URL)
574 {
575 'url': 'http://lenta.ru/news/2015/03/06/navalny/',
576 'info_dict': {
577 'id': '227304',
578 'ext': 'mp4',
579 'title': 'Навальный вышел на свободу',
580 'description': 'md5:d97861ac9ae77377f3f20eaf9d04b4f5',
581 'thumbnail': 're:^https?://.*\.jpg$',
582 'duration': 87,
583 'view_count': int,
584 'age_limit': 0,
585 },
586 },
587 # ClipYou (Eagle.Platform) embed (custom URL)
588 {
589 'url': 'http://muz-tv.ru/play/7129/',
590 'info_dict': {
591 'id': '12820',
592 'ext': 'mp4',
593 'title': "'O Sole Mio",
594 'thumbnail': 're:^https?://.*\.jpg$',
595 'duration': 216,
596 'view_count': int,
597 },
598 },
599 # Pladform embed
600 {
601 'url': 'http://muz-tv.ru/kinozal/view/7400/',
602 'info_dict': {
603 'id': '100183293',
604 'ext': 'mp4',
605 'title': 'Тайны перевала Дятлова • Тайна перевала Дятлова 1 серия 2 часть',
606 'description': 'Документальный сериал-расследование одной из самых жутких тайн ХХ века',
607 'thumbnail': 're:^https?://.*\.jpg$',
608 'duration': 694,
609 'age_limit': 0,
610 },
611 },
612 # RSS feed with enclosure
613 {
614 'url': 'http://podcastfeeds.nbcnews.com/audio/podcast/MSNBC-MADDOW-NETCAST-M4V.xml',
615 'info_dict': {
616 'id': 'pdv_maddow_netcast_m4v-02-27-2015-201624',
617 'ext': 'm4v',
618 'upload_date': '20150228',
619 'title': 'pdv_maddow_netcast_m4v-02-27-2015-201624',
620 }
621 }
622 ]
623
624 def report_following_redirect(self, new_url):
625 """Report information extraction."""
626 self._downloader.to_screen('[redirect] Following redirect to %s' % new_url)
627
628 def _extract_rss(self, url, video_id, doc):
629 playlist_title = doc.find('./channel/title').text
630 playlist_desc_el = doc.find('./channel/description')
631 playlist_desc = None if playlist_desc_el is None else playlist_desc_el.text
632
633 entries = []
634 for it in doc.findall('./channel/item'):
635 next_url = xpath_text(it, 'link', fatal=False)
636 if not next_url:
637 enclosure_nodes = it.findall('./enclosure')
638 for e in enclosure_nodes:
639 next_url = e.attrib.get('url')
640 if next_url:
641 break
642
643 if not next_url:
644 continue
645
646 entries.append({
647 '_type': 'url',
648 'url': next_url,
649 'title': it.find('title').text,
650 })
651
652 return {
653 '_type': 'playlist',
654 'id': url,
655 'title': playlist_title,
656 'description': playlist_desc,
657 'entries': entries,
658 }
659
660 def _extract_camtasia(self, url, video_id, webpage):
661 """ Returns None if no camtasia video can be found. """
662
663 camtasia_cfg = self._search_regex(
664 r'fo\.addVariable\(\s*"csConfigFile",\s*"([^"]+)"\s*\);',
665 webpage, 'camtasia configuration file', default=None)
666 if camtasia_cfg is None:
667 return None
668
669 title = self._html_search_meta('DC.title', webpage, fatal=True)
670
671 camtasia_url = compat_urlparse.urljoin(url, camtasia_cfg)
672 camtasia_cfg = self._download_xml(
673 camtasia_url, video_id,
674 note='Downloading camtasia configuration',
675 errnote='Failed to download camtasia configuration')
676 fileset_node = camtasia_cfg.find('./playlist/array/fileset')
677
678 entries = []
679 for n in fileset_node.getchildren():
680 url_n = n.find('./uri')
681 if url_n is None:
682 continue
683
684 entries.append({
685 'id': os.path.splitext(url_n.text.rpartition('/')[2])[0],
686 'title': '%s - %s' % (title, n.tag),
687 'url': compat_urlparse.urljoin(url, url_n.text),
688 'duration': float_or_none(n.find('./duration').text),
689 })
690
691 return {
692 '_type': 'playlist',
693 'entries': entries,
694 'title': title,
695 }
696
697 def _real_extract(self, url):
698 if url.startswith('//'):
699 return {
700 '_type': 'url',
701 'url': self.http_scheme() + url,
702 }
703
704 parsed_url = compat_urlparse.urlparse(url)
705 if not parsed_url.scheme:
706 default_search = self._downloader.params.get('default_search')
707 if default_search is None:
708 default_search = 'fixup_error'
709
710 if default_search in ('auto', 'auto_warning', 'fixup_error'):
711 if '/' in url:
712 self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
713 return self.url_result('http://' + url)
714 elif default_search != 'fixup_error':
715 if default_search == 'auto_warning':
716 if re.match(r'^(?:url|URL)$', url):
717 raise ExtractorError(
718 'Invalid URL: %r . Call youtube-dl like this: youtube-dl -v "https://www.youtube.com/watch?v=BaW_jenozKc" ' % url,
719 expected=True)
720 else:
721 self._downloader.report_warning(
722 'Falling back to youtube search for %s . Set --default-search "auto" to suppress this warning.' % url)
723 return self.url_result('ytsearch:' + url)
724
725 if default_search in ('error', 'fixup_error'):
726 raise ExtractorError(
727 '%r is not a valid URL. '
728 'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
729 % (url, url), expected=True)
730 else:
731 if ':' not in default_search:
732 default_search += ':'
733 return self.url_result(default_search + url)
734
735 url, smuggled_data = unsmuggle_url(url)
736 force_videoid = None
737 is_intentional = smuggled_data and smuggled_data.get('to_generic')
738 if smuggled_data and 'force_videoid' in smuggled_data:
739 force_videoid = smuggled_data['force_videoid']
740 video_id = force_videoid
741 else:
742 video_id = os.path.splitext(url.rstrip('/').split('/')[-1])[0]
743
744 self.to_screen('%s: Requesting header' % video_id)
745
746 head_req = HEADRequest(url)
747 head_response = self._request_webpage(
748 head_req, video_id,
749 note=False, errnote='Could not send HEAD request to %s' % url,
750 fatal=False)
751
752 if head_response is not False:
753 # Check for redirect
754 new_url = head_response.geturl()
755 if url != new_url:
756 self.report_following_redirect(new_url)
757 if force_videoid:
758 new_url = smuggle_url(
759 new_url, {'force_videoid': force_videoid})
760 return self.url_result(new_url)
761
762 full_response = None
763 if head_response is False:
764 full_response = self._request_webpage(url, video_id)
765 head_response = full_response
766
767 # Check for direct link to a video
768 content_type = head_response.headers.get('Content-Type', '')
769 m = re.match(r'^(?P<type>audio|video|application(?=/ogg$))/(?P<format_id>.+)$', content_type)
770 if m:
771 upload_date = unified_strdate(
772 head_response.headers.get('Last-Modified'))
773 return {
774 'id': video_id,
775 'title': os.path.splitext(url_basename(url))[0],
776 'direct': True,
777 'formats': [{
778 'format_id': m.group('format_id'),
779 'url': url,
780 'vcodec': 'none' if m.group('type') == 'audio' else None
781 }],
782 'upload_date': upload_date,
783 }
784
785 if not self._downloader.params.get('test', False) and not is_intentional:
786 self._downloader.report_warning('Falling back on generic information extractor.')
787
788 if not full_response:
789 full_response = self._request_webpage(url, video_id)
790
791 # Maybe it's a direct link to a video?
792 # Be careful not to download the whole thing!
793 first_bytes = full_response.read(512)
794 if not is_html(first_bytes):
795 self._downloader.report_warning(
796 'URL could be a direct video link, returning it as such.')
797 upload_date = unified_strdate(
798 head_response.headers.get('Last-Modified'))
799 return {
800 'id': video_id,
801 'title': os.path.splitext(url_basename(url))[0],
802 'direct': True,
803 'url': url,
804 'upload_date': upload_date,
805 }
806
807 webpage = self._webpage_read_content(
808 full_response, url, video_id, prefix=first_bytes)
809
810 self.report_extraction(video_id)
811
812 # Is it an RSS feed?
813 try:
814 doc = parse_xml(webpage)
815 if doc.tag == 'rss':
816 return self._extract_rss(url, video_id, doc)
817 except compat_xml_parse_error:
818 pass
819
820 # Is it a Camtasia project?
821 camtasia_res = self._extract_camtasia(url, video_id, webpage)
822 if camtasia_res is not None:
823 return camtasia_res
824
825 # Sometimes embedded video player is hidden behind percent encoding
826 # (e.g. https://github.com/rg3/youtube-dl/issues/2448)
827 # Unescaping the whole page allows to handle those cases in a generic way
828 webpage = compat_urllib_parse.unquote(webpage)
829
830 # it's tempting to parse this further, but you would
831 # have to take into account all the variations like
832 # Video Title - Site Name
833 # Site Name | Video Title
834 # Video Title - Tagline | Site Name
835 # and so on and so forth; it's just not practical
836 video_title = self._html_search_regex(
837 r'(?s)<title>(.*?)</title>', webpage, 'video title',
838 default='video')
839
840 # Try to detect age limit automatically
841 age_limit = self._rta_search(webpage)
842 # And then there are the jokers who advertise that they use RTA,
843 # but actually don't.
844 AGE_LIMIT_MARKERS = [
845 r'Proudly Labeled <a href="http://www.rtalabel.org/" title="Restricted to Adults">RTA</a>',
846 ]
847 if any(re.search(marker, webpage) for marker in AGE_LIMIT_MARKERS):
848 age_limit = 18
849
850 # video uploader is domain name
851 video_uploader = self._search_regex(
852 r'^(?:https?://)?([^/]*)/.*', url, 'video uploader')
853
854 # Helper method
855 def _playlist_from_matches(matches, getter=None, ie=None):
856 urlrs = orderedSet(
857 self.url_result(self._proto_relative_url(getter(m) if getter else m), ie)
858 for m in matches)
859 return self.playlist_result(
860 urlrs, playlist_id=video_id, playlist_title=video_title)
861
862 # Look for BrightCove:
863 bc_urls = BrightcoveIE._extract_brightcove_urls(webpage)
864 if bc_urls:
865 self.to_screen('Brightcove video detected.')
866 entries = [{
867 '_type': 'url',
868 'url': smuggle_url(bc_url, {'Referer': url}),
869 'ie_key': 'Brightcove'
870 } for bc_url in bc_urls]
871
872 return {
873 '_type': 'playlist',
874 'title': video_title,
875 'id': video_id,
876 'entries': entries,
877 }
878
879 # Look for embedded rtl.nl player
880 matches = re.findall(
881 r'<iframe\s+(?:[a-zA-Z-]+="[^"]+"\s+)*?src="((?:https?:)?//(?:www\.)?rtl\.nl/system/videoplayer/[^"]+video_embed[^"]+)"',
882 webpage)
883 if matches:
884 return _playlist_from_matches(matches, ie='RtlNl')
885
886 # Look for embedded (iframe) Vimeo player
887 mobj = re.search(
888 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//player\.vimeo\.com/video/.+?)\1', webpage)
889 if mobj:
890 player_url = unescapeHTML(mobj.group('url'))
891 surl = smuggle_url(player_url, {'Referer': url})
892 return self.url_result(surl)
893 # Look for embedded (swf embed) Vimeo player
894 mobj = re.search(
895 r'<embed[^>]+?src="((?:https?:)?//(?:www\.)?vimeo\.com/moogaloop\.swf.+?)"', webpage)
896 if mobj:
897 return self.url_result(mobj.group(1))
898
899 # Look for embedded YouTube player
900 matches = re.findall(r'''(?x)
901 (?:
902 <iframe[^>]+?src=|
903 data-video-url=|
904 <embed[^>]+?src=|
905 embedSWF\(?:\s*|
906 new\s+SWFObject\(
907 )
908 (["\'])
909 (?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
910 (?:embed|v|p)/.+?)
911 \1''', webpage)
912 if matches:
913 return _playlist_from_matches(
914 matches, lambda m: unescapeHTML(m[1]))
915
916 # Look for lazyYT YouTube embed
917 matches = re.findall(
918 r'class="lazyYT" data-youtube-id="([^"]+)"', webpage)
919 if matches:
920 return _playlist_from_matches(matches, lambda m: unescapeHTML(m))
921
922 # Look for embedded Dailymotion player
923 matches = re.findall(
924 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
925 if matches:
926 return _playlist_from_matches(
927 matches, lambda m: unescapeHTML(m[1]))
928
929 # Look for embedded Dailymotion playlist player (#3822)
930 m = re.search(
931 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.[a-z]{2,3}/widget/jukebox\?.+?)\1', webpage)
932 if m:
933 playlists = re.findall(
934 r'list\[\]=/playlist/([^/]+)/', unescapeHTML(m.group('url')))
935 if playlists:
936 return _playlist_from_matches(
937 playlists, lambda p: '//dailymotion.com/playlist/%s' % p)
938
939 # Look for embedded Wistia player
940 match = re.search(
941 r'<(?:meta[^>]+?content|iframe[^>]+?src)=(["\'])(?P<url>(?:https?:)?//(?:fast\.)?wistia\.net/embed/iframe/.+?)\1', webpage)
942 if match:
943 embed_url = self._proto_relative_url(
944 unescapeHTML(match.group('url')))
945 return {
946 '_type': 'url_transparent',
947 'url': embed_url,
948 'ie_key': 'Wistia',
949 'uploader': video_uploader,
950 'title': video_title,
951 'id': video_id,
952 }
953
954 match = re.search(r'(?:id=["\']wistia_|data-wistia-?id=["\']|Wistia\.embed\(["\'])(?P<id>[^"\']+)', webpage)
955 if match:
956 return {
957 '_type': 'url_transparent',
958 'url': 'http://fast.wistia.net/embed/iframe/{0:}'.format(match.group('id')),
959 'ie_key': 'Wistia',
960 'uploader': video_uploader,
961 'title': video_title,
962 'id': match.group('id')
963 }
964
965 # Look for embedded blip.tv player
966 mobj = re.search(r'<meta\s[^>]*https?://api\.blip\.tv/\w+/redirect/\w+/(\d+)', webpage)
967 if mobj:
968 return self.url_result('http://blip.tv/a/a-' + mobj.group(1), 'BlipTV')
969 mobj = re.search(r'<(?:iframe|embed|object)\s[^>]*(https?://(?:\w+\.)?blip\.tv/(?:play/|api\.swf#)[a-zA-Z0-9_]+)', webpage)
970 if mobj:
971 return self.url_result(mobj.group(1), 'BlipTV')
972
973 # Look for embedded condenast player
974 matches = re.findall(
975 r'<iframe\s+(?:[a-zA-Z-]+="[^"]+"\s+)*?src="(https?://player\.cnevids\.com/embed/[^"]+")',
976 webpage)
977 if matches:
978 return {
979 '_type': 'playlist',
980 'entries': [{
981 '_type': 'url',
982 'ie_key': 'CondeNast',
983 'url': ma,
984 } for ma in matches],
985 'title': video_title,
986 'id': video_id,
987 }
988
989 # Look for Bandcamp pages with custom domain
990 mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage)
991 if mobj is not None:
992 burl = unescapeHTML(mobj.group(1))
993 # Don't set the extractor because it can be a track url or an album
994 return self.url_result(burl)
995
996 # Look for embedded Vevo player
997 mobj = re.search(
998 r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:cache\.)?vevo\.com/.+?)\1', webpage)
999 if mobj is not None:
1000 return self.url_result(mobj.group('url'))
1001
1002 # Look for embedded Viddler player
1003 mobj = re.search(
1004 r'<(?:iframe[^>]+?src|param[^>]+?value)=(["\'])(?P<url>(?:https?:)?//(?:www\.)?viddler\.com/(?:embed|player)/.+?)\1',
1005 webpage)
1006 if mobj is not None:
1007 return self.url_result(mobj.group('url'))
1008
1009 # Look for Ooyala videos
1010 mobj = (re.search(r'player\.ooyala\.com/[^"?]+\?[^"]*?(?:embedCode|ec)=(?P<ec>[^"&]+)', webpage) or
1011 re.search(r'OO\.Player\.create\([\'"].*?[\'"],\s*[\'"](?P<ec>.{32})[\'"]', webpage) or
1012 re.search(r'SBN\.VideoLinkset\.ooyala\([\'"](?P<ec>.{32})[\'"]\)', webpage))
1013 if mobj is not None:
1014 return OoyalaIE._build_url_result(mobj.group('ec'))
1015
1016 # Look for multiple Ooyala embeds on SBN network websites
1017 mobj = re.search(r'SBN\.VideoLinkset\.entryGroup\((\[.*?\])', webpage)
1018 if mobj is not None:
1019 embeds = self._parse_json(mobj.group(1), video_id, fatal=False)
1020 if embeds:
1021 return _playlist_from_matches(
1022 embeds, getter=lambda v: OoyalaIE._url_for_embed_code(v['provider_video_id']), ie='Ooyala')
1023
1024 # Look for Aparat videos
1025 mobj = re.search(r'<iframe .*?src="(http://www\.aparat\.com/video/[^"]+)"', webpage)
1026 if mobj is not None:
1027 return self.url_result(mobj.group(1), 'Aparat')
1028
1029 # Look for MPORA videos
1030 mobj = re.search(r'<iframe .*?src="(http://mpora\.(?:com|de)/videos/[^"]+)"', webpage)
1031 if mobj is not None:
1032 return self.url_result(mobj.group(1), 'Mpora')
1033
1034 # Look for embedded NovaMov-based player
1035 mobj = re.search(
1036 r'''(?x)<(?:pagespeed_)?iframe[^>]+?src=(["\'])
1037 (?P<url>http://(?:(?:embed|www)\.)?
1038 (?:novamov\.com|
1039 nowvideo\.(?:ch|sx|eu|at|ag|co)|
1040 videoweed\.(?:es|com)|
1041 movshare\.(?:net|sx|ag)|
1042 divxstage\.(?:eu|net|ch|co|at|ag))
1043 /embed\.php.+?)\1''', webpage)
1044 if mobj is not None:
1045 return self.url_result(mobj.group('url'))
1046
1047 # Look for embedded Facebook player
1048 mobj = re.search(
1049 r'<iframe[^>]+?src=(["\'])(?P<url>https://www\.facebook\.com/video/embed.+?)\1', webpage)
1050 if mobj is not None:
1051 return self.url_result(mobj.group('url'), 'Facebook')
1052
1053 # Look for embedded VK player
1054 mobj = re.search(r'<iframe[^>]+?src=(["\'])(?P<url>https?://vk\.com/video_ext\.php.+?)\1', webpage)
1055 if mobj is not None:
1056 return self.url_result(mobj.group('url'), 'VK')
1057
1058 # Look for embedded ivi player
1059 mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
1060 if mobj is not None:
1061 return self.url_result(mobj.group('url'), 'Ivi')
1062
1063 # Look for embedded Huffington Post player
1064 mobj = re.search(
1065 r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
1066 if mobj is not None:
1067 return self.url_result(mobj.group('url'), 'HuffPost')
1068
1069 # Look for embed.ly
1070 mobj = re.search(r'class=["\']embedly-card["\'][^>]href=["\'](?P<url>[^"\']+)', webpage)
1071 if mobj is not None:
1072 return self.url_result(mobj.group('url'))
1073 mobj = re.search(r'class=["\']embedly-embed["\'][^>]src=["\'][^"\']*url=(?P<url>[^&]+)', webpage)
1074 if mobj is not None:
1075 return self.url_result(compat_urllib_parse.unquote(mobj.group('url')))
1076
1077 # Look for funnyordie embed
1078 matches = re.findall(r'<iframe[^>]+?src="(https?://(?:www\.)?funnyordie\.com/embed/[^"]+)"', webpage)
1079 if matches:
1080 return _playlist_from_matches(
1081 matches, getter=unescapeHTML, ie='FunnyOrDie')
1082
1083 # Look for BBC iPlayer embed
1084 matches = re.findall(r'setPlaylist\("(https?://www\.bbc\.co\.uk/iplayer/[^/]+/[\da-z]{8})"\)', webpage)
1085 if matches:
1086 return _playlist_from_matches(matches, ie='BBCCoUk')
1087
1088 # Look for embedded RUTV player
1089 rutv_url = RUTVIE._extract_url(webpage)
1090 if rutv_url:
1091 return self.url_result(rutv_url, 'RUTV')
1092
1093 # Look for embedded TED player
1094 mobj = re.search(
1095 r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed(?:-ssl)?\.ted\.com/.+?)\1', webpage)
1096 if mobj is not None:
1097 return self.url_result(mobj.group('url'), 'TED')
1098
1099 # Look for embedded Ustream videos
1100 mobj = re.search(
1101 r'<iframe[^>]+?src=(["\'])(?P<url>http://www\.ustream\.tv/embed/.+?)\1', webpage)
1102 if mobj is not None:
1103 return self.url_result(mobj.group('url'), 'Ustream')
1104
1105 # Look for embedded arte.tv player
1106 mobj = re.search(
1107 r'<script [^>]*?src="(?P<url>http://www\.arte\.tv/playerv2/embed[^"]+)"',
1108 webpage)
1109 if mobj is not None:
1110 return self.url_result(mobj.group('url'), 'ArteTVEmbed')
1111
1112 # Look for embedded smotri.com player
1113 smotri_url = SmotriIE._extract_url(webpage)
1114 if smotri_url:
1115 return self.url_result(smotri_url, 'Smotri')
1116
1117 # Look for embeded soundcloud player
1118 mobj = re.search(
1119 r'<iframe\s+(?:[a-zA-Z0-9_-]+="[^"]+"\s+)*src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
1120 webpage)
1121 if mobj is not None:
1122 url = unescapeHTML(mobj.group('url'))
1123 return self.url_result(url)
1124
1125 # Look for embedded vulture.com player
1126 mobj = re.search(
1127 r'<iframe src="(?P<url>https?://video\.vulture\.com/[^"]+)"',
1128 webpage)
1129 if mobj is not None:
1130 url = unescapeHTML(mobj.group('url'))
1131 return self.url_result(url, ie='Vulture')
1132
1133 # Look for embedded mtvservices player
1134 mobj = re.search(
1135 r'<iframe src="(?P<url>https?://media\.mtvnservices\.com/embed/[^"]+)"',
1136 webpage)
1137 if mobj is not None:
1138 url = unescapeHTML(mobj.group('url'))
1139 return self.url_result(url, ie='MTVServicesEmbedded')
1140
1141 # Look for embedded yahoo player
1142 mobj = re.search(
1143 r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:screen|movies)\.yahoo\.com/.+?\.html\?format=embed)\1',
1144 webpage)
1145 if mobj is not None:
1146 return self.url_result(mobj.group('url'), 'Yahoo')
1147
1148 # Look for embedded sbs.com.au player
1149 mobj = re.search(
1150 r'''(?x)
1151 (?:
1152 <meta\s+property="og:video"\s+content=|
1153 <iframe[^>]+?src=
1154 )
1155 (["\'])(?P<url>https?://(?:www\.)?sbs\.com\.au/ondemand/video/.+?)\1''',
1156 webpage)
1157 if mobj is not None:
1158 return self.url_result(mobj.group('url'), 'SBS')
1159
1160 # Look for embedded Cinchcast player
1161 mobj = re.search(
1162 r'<iframe[^>]+?src=(["\'])(?P<url>https?://player\.cinchcast\.com/.+?)\1',
1163 webpage)
1164 if mobj is not None:
1165 return self.url_result(mobj.group('url'), 'Cinchcast')
1166
1167 mobj = re.search(
1168 r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
1169 webpage)
1170 if mobj is not None:
1171 return self.url_result(mobj.group('url'), 'MLB')
1172
1173 mobj = re.search(
1174 r'<iframe[^>]+?src=(["\'])(?P<url>%s)\1' % CondeNastIE.EMBED_URL,
1175 webpage)
1176 if mobj is not None:
1177 return self.url_result(self._proto_relative_url(mobj.group('url'), scheme='http:'), 'CondeNast')
1178
1179 mobj = re.search(
1180 r'<iframe[^>]+src="(?P<url>https?://new\.livestream\.com/[^"]+/player[^"]+)"',
1181 webpage)
1182 if mobj is not None:
1183 return self.url_result(mobj.group('url'), 'Livestream')
1184
1185 # Look for Zapiks embed
1186 mobj = re.search(
1187 r'<iframe[^>]+src="(?P<url>https?://(?:www\.)?zapiks\.fr/index\.php\?.+?)"', webpage)
1188 if mobj is not None:
1189 return self.url_result(mobj.group('url'), 'Zapiks')
1190
1191 # Look for Kaltura embeds
1192 mobj = re.search(
1193 r"(?s)kWidget\.(?:thumb)?[Ee]mbed\(\{.*?'wid'\s*:\s*'_?(?P<partner_id>[^']+)',.*?'entry_id'\s*:\s*'(?P<id>[^']+)',", webpage)
1194 if mobj is not None:
1195 return self.url_result('kaltura:%(partner_id)s:%(id)s' % mobj.groupdict(), 'Kaltura')
1196
1197 # Look for Eagle.Platform embeds
1198 mobj = re.search(
1199 r'<iframe[^>]+src="(?P<url>https?://.+?\.media\.eagleplatform\.com/index/player\?.+?)"', webpage)
1200 if mobj is not None:
1201 return self.url_result(mobj.group('url'), 'EaglePlatform')
1202
1203 # Look for ClipYou (uses Eagle.Platform) embeds
1204 mobj = re.search(
1205 r'<iframe[^>]+src="https?://(?P<host>media\.clipyou\.ru)/index/player\?.*\brecord_id=(?P<id>\d+).*"', webpage)
1206 if mobj is not None:
1207 return self.url_result('eagleplatform:%(host)s:%(id)s' % mobj.groupdict(), 'EaglePlatform')
1208
1209 # Look for Pladform embeds
1210 mobj = re.search(
1211 r'<iframe[^>]+src="(?P<url>https?://out\.pladform\.ru/player\?.+?)"', webpage)
1212 if mobj is not None:
1213 return self.url_result(mobj.group('url'), 'Pladform')
1214
1215 def check_video(vurl):
1216 if YoutubeIE.suitable(vurl):
1217 return True
1218 vpath = compat_urlparse.urlparse(vurl).path
1219 vext = determine_ext(vpath)
1220 return '.' in vpath and vext not in ('swf', 'png', 'jpg', 'srt', 'sbv', 'sub', 'vtt', 'ttml')
1221
1222 def filter_video(urls):
1223 return list(filter(check_video, urls))
1224
1225 # Start with something easy: JW Player in SWFObject
1226 found = filter_video(re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage))
1227 if not found:
1228 # Look for gorilla-vid style embedding
1229 found = filter_video(re.findall(r'''(?sx)
1230 (?:
1231 jw_plugins|
1232 JWPlayerOptions|
1233 jwplayer\s*\(\s*["'][^'"]+["']\s*\)\s*\.setup
1234 )
1235 .*?
1236 ['"]?file['"]?\s*:\s*["\'](.*?)["\']''', webpage))
1237 if not found:
1238 # Broaden the search a little bit
1239 found = filter_video(re.findall(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage))
1240 if not found:
1241 # Broaden the findall a little bit: JWPlayer JS loader
1242 found = filter_video(re.findall(
1243 r'[^A-Za-z0-9]?file["\']?:\s*["\'](http(?![^\'"]+\.[0-9]+[\'"])[^\'"]+)["\']', webpage))
1244 if not found:
1245 # Flow player
1246 found = filter_video(re.findall(r'''(?xs)
1247 flowplayer\("[^"]+",\s*
1248 \{[^}]+?\}\s*,
1249 \s*\{[^}]+? ["']?clip["']?\s*:\s*\{\s*
1250 ["']?url["']?\s*:\s*["']([^"']+)["']
1251 ''', webpage))
1252 if not found:
1253 # Cinerama player
1254 found = re.findall(
1255 r"cinerama\.embedPlayer\(\s*\'[^']+\',\s*'([^']+)'", webpage)
1256 if not found:
1257 # Try to find twitter cards info
1258 found = filter_video(re.findall(
1259 r'<meta (?:property|name)="twitter:player:stream" (?:content|value)="(.+?)"', webpage))
1260 if not found:
1261 # We look for Open Graph info:
1262 # We have to match any number spaces between elements, some sites try to align them (eg.: statigr.am)
1263 m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
1264 # We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
1265 if m_video_type is not None:
1266 found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
1267 if not found:
1268 # HTML5 video
1269 found = re.findall(r'(?s)<video[^<]*(?:>.*?<source[^>]*)?\s+src=["\'](.*?)["\']', webpage)
1270 if not found:
1271 found = re.search(
1272 r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
1273 r'(?:[a-z-]+="[^"]+"\s+)*?content="[0-9]{,2};url=\'?([^\'"]+)',
1274 webpage)
1275 if found:
1276 new_url = found.group(1)
1277 self.report_following_redirect(new_url)
1278 return {
1279 '_type': 'url',
1280 'url': new_url,
1281 }
1282 if not found:
1283 raise UnsupportedError(url)
1284
1285 entries = []
1286 for video_url in found:
1287 video_url = compat_urlparse.urljoin(url, video_url)
1288 video_id = compat_urllib_parse.unquote(os.path.basename(video_url))
1289
1290 # Sometimes, jwplayer extraction will result in a YouTube URL
1291 if YoutubeIE.suitable(video_url):
1292 entries.append(self.url_result(video_url, 'Youtube'))
1293 continue
1294
1295 # here's a fun little line of code for you:
1296 video_id = os.path.splitext(video_id)[0]
1297
1298 entries.append({
1299 'id': video_id,
1300 'url': video_url,
1301 'uploader': video_uploader,
1302 'title': video_title,
1303 'age_limit': age_limit,
1304 })
1305
1306 if len(entries) == 1:
1307 return entries[0]
1308 else:
1309 for num, e in enumerate(entries, start=1):
1310 # 'url' results don't have a title
1311 if e.get('title') is not None:
1312 e['title'] = '%s (%d)' % (e['title'], num)
1313 return {
1314 '_type': 'playlist',
1315 'entries': entries,
1316 }