2 from __future__
import unicode_literals
5 import xml
.etree
.ElementTree
7 from .common
import InfoExtractor
16 from ..compat
import compat_HTTPError
19 class BBCCoUkIE(InfoExtractor
):
21 IE_DESC
= 'BBC iPlayer'
22 _VALID_URL
= r
'https?://(?:www\.)?bbc\.co\.uk/(?:(?:(?:programmes|iplayer(?:/[^/]+)?/(?:episode|playlist))/)|music/clips[/#])(?P<id>[\da-z]{8})'
24 _MEDIASELECTOR_URLS
= [
25 # Provides HQ HLS streams with even better quality that pc mediaset but fails
26 # with geolocation in some cases when it's even not geo restricted at all (e.g.
27 # http://www.bbc.co.uk/programmes/b06bp7lf)
28 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/%s',
29 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/%s',
34 'url': 'http://www.bbc.co.uk/programmes/b039g8p7',
38 'title': 'Kaleidoscope, Leonard Cohen',
39 'description': 'The Canadian poet and songwriter reflects on his musical career.',
44 'skip_download': True,
48 'url': 'http://www.bbc.co.uk/iplayer/episode/b00yng5w/The_Man_in_Black_Series_3_The_Printed_Name/',
52 'title': 'The Man in Black: Series 3: The Printed Name',
53 'description': "Mark Gatiss introduces Nicholas Pierpan's chilling tale of a writer's devilish pact with a mysterious man. Stars Ewan Bailey.",
58 'skip_download': True,
60 'skip': 'Episode is no longer available on BBC iPlayer Radio',
63 'url': 'http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/',
67 'title': 'The Voice UK: Series 3: Blind Auditions 5',
68 'description': "Emma Willis and Marvin Humes present the fifth set of blind auditions in the singing competition, as the coaches continue to build their teams based on voice alone.",
73 'skip_download': True,
75 'skip': 'Currently BBC iPlayer TV programmes are available to play in the UK only',
78 'url': 'http://www.bbc.co.uk/iplayer/episode/p026c7jt/tomorrows-worlds-the-unearthly-history-of-science-fiction-2-invasion',
82 'title': "Tomorrow's Worlds: The Unearthly History of Science Fiction",
83 'description': '2. Invasion',
88 'skip_download': True,
90 'skip': 'Currently BBC iPlayer TV programmes are available to play in the UK only',
92 'url': 'http://www.bbc.co.uk/programmes/b04v20dw',
96 'title': 'Pete Tong, The Essential New Tune Special',
97 'description': "Pete has a very special mix - all of 2014's Essential New Tunes!",
102 'skip_download': True,
105 'url': 'http://www.bbc.co.uk/music/clips/p02frcc3',
110 'title': 'Pete Tong, Past, Present and Future Special, Madeon - After Hours mix',
111 'description': 'French house superstar Madeon takes us out of the club and onto the after party.',
116 'skip_download': True,
119 'url': 'http://www.bbc.co.uk/music/clips/p025c0zz',
124 'title': 'Reading and Leeds Festival, 2014, Rae Morris - Closer (Live on BBC Three)',
125 'description': 'Rae Morris performs Closer for BBC Three at Reading 2014',
130 'skip_download': True,
133 'url': 'http://www.bbc.co.uk/iplayer/episode/b054fn09/ad/natural-world-20152016-2-super-powered-owls',
137 'title': 'Natural World, 2015-2016: 2. Super Powered Owls',
138 'description': 'md5:e4db5c937d0e95a7c6b5e654d429183d',
143 'skip_download': True,
145 'skip': 'geolocation',
147 'url': 'http://www.bbc.co.uk/iplayer/episode/b05zmgwn/royal-academy-summer-exhibition',
151 'description': 'Kirsty Wark and Morgan Quaintance visit the Royal Academy as it prepares for its annual artistic extravaganza, meeting people who have come together to make the show unique.',
152 'title': 'Royal Academy Summer Exhibition',
157 'skip_download': True,
159 'skip': 'geolocation',
161 # iptv-all mediaset fails with geolocation however there is no geo restriction
162 # for this programme at all
163 'url': 'http://www.bbc.co.uk/programmes/b06bp7lf',
167 'title': "Annie Mac's Friday Night, B.Traits sits in for Annie",
168 'description': 'B.Traits sits in for Annie Mac with a Mini-Mix from Disclosure.',
173 'skip_download': True,
176 'url': 'http://www.bbc.co.uk/iplayer/playlist/p01dvks4',
177 'only_matching': True,
179 'url': 'http://www.bbc.co.uk/music/clips#p02frcc3',
180 'only_matching': True,
182 'url': 'http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo',
183 'only_matching': True,
187 class MediaSelectionError(Exception):
188 def __init__(self
, id):
191 def _extract_asx_playlist(self
, connection
, programme_id
):
192 asx
= self
._download
_xml
(connection
.get('href'), programme_id
, 'Downloading ASX playlist')
193 return [ref
.get('href') for ref
in asx
.findall('./Entry/ref')]
195 def _extract_connection(self
, connection
, programme_id
):
197 protocol
= connection
.get('protocol')
198 supplier
= connection
.get('supplier')
199 if protocol
== 'http':
200 href
= connection
.get('href')
201 transfer_format
= connection
.get('transferFormat')
203 if supplier
== 'asx':
204 for i
, ref
in enumerate(self
._extract
_asx
_playlist
(connection
, programme_id
)):
207 'format_id': 'ref%s_%s' % (i
, supplier
),
209 # Skip DASH until supported
210 elif transfer_format
== 'dash':
212 elif transfer_format
== 'hls':
213 m3u8_formats
= self
._extract
_m
3u8_formats
(
214 href
, programme_id
, ext
='mp4', entry_protocol
='m3u8_native',
215 m3u8_id
=supplier
, fatal
=False)
217 formats
.extend(m3u8_formats
)
222 'format_id': supplier
,
224 elif protocol
== 'rtmp':
225 application
= connection
.get('application', 'ondemand')
226 auth_string
= connection
.get('authString')
227 identifier
= connection
.get('identifier')
228 server
= connection
.get('server')
230 'url': '%s://%s/%s?%s' % (protocol
, server
, application
, auth_string
),
231 'play_path': identifier
,
232 'app': '%s?%s' % (application
, auth_string
),
233 'page_url': 'http://www.bbc.co.uk',
234 'player_url': 'http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf',
237 'format_id': supplier
,
241 def _extract_items(self
, playlist
):
242 return playlist
.findall('./{http://bbc.co.uk/2008/emp/playlist}item')
244 def _extract_medias(self
, media_selection
):
245 error
= media_selection
.find('./{http://bbc.co.uk/2008/mp/mediaselection}error')
246 if error
is not None:
247 raise BBCCoUkIE
.MediaSelectionError(error
.get('id'))
248 return media_selection
.findall('./{http://bbc.co.uk/2008/mp/mediaselection}media')
250 def _extract_connections(self
, media
):
251 return media
.findall('./{http://bbc.co.uk/2008/mp/mediaselection}connection')
253 def _extract_video(self
, media
, programme_id
):
255 vbr
= int_or_none(media
.get('bitrate'))
256 vcodec
= media
.get('encoding')
257 service
= media
.get('service')
258 width
= int_or_none(media
.get('width'))
259 height
= int_or_none(media
.get('height'))
260 file_size
= int_or_none(media
.get('media_file_size'))
261 for connection
in self
._extract
_connections
(media
):
262 conn_formats
= self
._extract
_connection
(connection
, programme_id
)
263 for format
in conn_formats
:
265 'format_id': '%s_%s' % (service
, format
['format_id']),
270 'filesize': file_size
,
272 formats
.extend(conn_formats
)
275 def _extract_audio(self
, media
, programme_id
):
277 abr
= int_or_none(media
.get('bitrate'))
278 acodec
= media
.get('encoding')
279 service
= media
.get('service')
280 for connection
in self
._extract
_connections
(media
):
281 conn_formats
= self
._extract
_connection
(connection
, programme_id
)
282 for format
in conn_formats
:
284 'format_id': '%s_%s' % (service
, format
['format_id']),
288 formats
.extend(conn_formats
)
291 def _get_subtitles(self
, media
, programme_id
):
293 for connection
in self
._extract
_connections
(media
):
294 captions
= self
._download
_xml
(connection
.get('href'), programme_id
, 'Downloading captions')
295 lang
= captions
.get('{http://www.w3.org/XML/1998/namespace}lang', 'en')
298 'url': connection
.get('href'),
304 def _raise_extractor_error(self
, media_selection_error
):
305 raise ExtractorError(
306 '%s returned error: %s' % (self
.IE_NAME
, media_selection_error
.id),
309 def _download_media_selector(self
, programme_id
):
310 last_exception
= None
311 for mediaselector_url
in self
._MEDIASELECTOR
_URLS
:
313 return self
._download
_media
_selector
_url
(
314 mediaselector_url
% programme_id
, programme_id
)
315 except BBCCoUkIE
.MediaSelectionError
as e
:
316 if e
.id in ('notukerror', 'geolocation'):
319 self
._raise
_extractor
_error
(e
)
320 self
._raise
_extractor
_error
(last_exception
)
322 def _download_media_selector_url(self
, url
, programme_id
=None):
324 media_selection
= self
._download
_xml
(
325 url
, programme_id
, 'Downloading media selection XML')
326 except ExtractorError
as ee
:
327 if isinstance(ee
.cause
, compat_HTTPError
) and ee
.cause
.code
== 403:
328 media_selection
= xml
.etree
.ElementTree
.fromstring(ee
.cause
.read().decode('utf-8'))
331 return self
._process
_media
_selector
(media_selection
, programme_id
)
333 def _process_media_selector(self
, media_selection
, programme_id
):
337 for media
in self
._extract
_medias
(media_selection
):
338 kind
= media
.get('kind')
340 formats
.extend(self
._extract
_audio
(media
, programme_id
))
341 elif kind
== 'video':
342 formats
.extend(self
._extract
_video
(media
, programme_id
))
343 elif kind
== 'captions':
344 subtitles
= self
.extract_subtitles(media
, programme_id
)
345 return formats
, subtitles
347 def _download_playlist(self
, playlist_id
):
349 playlist
= self
._download
_json
(
350 'http://www.bbc.co.uk/programmes/%s/playlist.json' % playlist_id
,
351 playlist_id
, 'Downloading playlist JSON')
353 version
= playlist
.get('defaultAvailableVersion')
355 smp_config
= version
['smpConfig']
356 title
= smp_config
['title']
357 description
= smp_config
['summary']
358 for item
in smp_config
['items']:
360 if kind
!= 'programme' and kind
!= 'radioProgramme':
362 programme_id
= item
.get('vpid')
363 duration
= int_or_none(item
.get('duration'))
364 formats
, subtitles
= self
._download
_media
_selector
(programme_id
)
365 return programme_id
, title
, description
, duration
, formats
, subtitles
366 except ExtractorError
as ee
:
367 if not (isinstance(ee
.cause
, compat_HTTPError
) and ee
.cause
.code
== 404):
370 # fallback to legacy playlist
371 return self
._process
_legacy
_playlist
(playlist_id
)
373 def _process_legacy_playlist_url(self
, url
, display_id
):
374 playlist
= self
._download
_legacy
_playlist
_url
(url
, display_id
)
375 return self
._extract
_from
_legacy
_playlist
(playlist
, display_id
)
377 def _process_legacy_playlist(self
, playlist_id
):
378 return self
._process
_legacy
_playlist
_url
(
379 'http://www.bbc.co.uk/iplayer/playlist/%s' % playlist_id
, playlist_id
)
381 def _download_legacy_playlist_url(self
, url
, playlist_id
=None):
382 return self
._download
_xml
(
383 url
, playlist_id
, 'Downloading legacy playlist XML')
385 def _extract_from_legacy_playlist(self
, playlist
, playlist_id
):
386 no_items
= playlist
.find('./{http://bbc.co.uk/2008/emp/playlist}noItems')
387 if no_items
is not None:
388 reason
= no_items
.get('reason')
389 if reason
== 'preAvailability':
390 msg
= 'Episode %s is not yet available' % playlist_id
391 elif reason
== 'postAvailability':
392 msg
= 'Episode %s is no longer available' % playlist_id
393 elif reason
== 'noMedia':
394 msg
= 'Episode %s is not currently available' % playlist_id
396 msg
= 'Episode %s is not available: %s' % (playlist_id
, reason
)
397 raise ExtractorError(msg
, expected
=True)
399 for item
in self
._extract
_items
(playlist
):
400 kind
= item
.get('kind')
401 if kind
!= 'programme' and kind
!= 'radioProgramme':
403 title
= playlist
.find('./{http://bbc.co.uk/2008/emp/playlist}title').text
404 description
= playlist
.find('./{http://bbc.co.uk/2008/emp/playlist}summary').text
406 def get_programme_id(item
):
407 def get_from_attributes(item
):
408 for p
in('identifier', 'group'):
410 if value
and re
.match(r
'^[pb][\da-z]{7}$', value
):
412 get_from_attributes(item
)
413 mediator
= item
.find('./{http://bbc.co.uk/2008/emp/playlist}mediator')
414 if mediator
is not None:
415 return get_from_attributes(mediator
)
417 programme_id
= get_programme_id(item
)
418 duration
= int_or_none(item
.get('duration'))
419 # TODO: programme_id can be None and media items can be incorporated right inside
420 # playlist's item (e.g. http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
422 formats
, subtitles
= self
._download
_media
_selector
(programme_id
)
424 return programme_id
, title
, description
, duration
, formats
, subtitles
426 def _real_extract(self
, url
):
427 group_id
= self
._match
_id
(url
)
429 webpage
= self
._download
_webpage
(url
, group_id
, 'Downloading video page')
433 tviplayer
= self
._search
_regex
(
434 r
'mediator\.bind\(({.+?})\s*,\s*document\.getElementById',
435 webpage
, 'player', default
=None)
438 player
= self
._parse
_json
(tviplayer
, group_id
).get('player', {})
439 duration
= int_or_none(player
.get('duration'))
440 programme_id
= player
.get('vpid')
443 programme_id
= self
._search
_regex
(
444 r
'"vpid"\s*:\s*"([\da-z]{8})"', webpage
, 'vpid', fatal
=False, default
=None)
447 formats
, subtitles
= self
._download
_media
_selector
(programme_id
)
448 title
= self
._og
_search
_title
(webpage
)
449 description
= self
._search
_regex
(
450 r
'<p class="[^"]*medium-description[^"]*">([^<]+)</p>',
451 webpage
, 'description', fatal
=False)
453 programme_id
, title
, description
, duration
, formats
, subtitles
= self
._download
_playlist
(group_id
)
455 self
._sort
_formats
(formats
)
460 'description': description
,
461 'thumbnail': self
._og
_search
_thumbnail
(webpage
, default
=None),
462 'duration': duration
,
464 'subtitles': subtitles
,
468 class BBCIE(BBCCoUkIE
):
471 _VALID_URL
= r
'https?://(?:www\.)?bbc\.(?:com|co\.uk)/(?:[^/]+/)+(?P<id>[^/#?]+)'
473 _MEDIASELECTOR_URLS
= [
474 # Provides more formats, namely direct mp4 links, but fails on some videos with
475 # notukerror for non UK (?) users (e.g.
476 # http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
477 'http://open.live.bbc.co.uk/mediaselector/4/mtis/stream/%s',
478 # Provides fewer formats, but works everywhere for everybody (hopefully)
479 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/journalism-pc/vpid/%s',
483 # article with multiple videos embedded with data-media-meta containing
484 # playlist.sxml, externalId and no direct video links
485 'url': 'http://www.bbc.com/news/world-europe-32668511',
487 'id': 'world-europe-32668511',
488 'title': 'Russia stages massive WW2 parade despite Western boycott',
489 'description': 'md5:00ff61976f6081841f759a08bf78cc9c',
493 # article with multiple videos embedded with data-media-meta (more videos)
494 'url': 'http://www.bbc.com/news/business-28299555',
496 'id': 'business-28299555',
497 'title': 'Farnborough Airshow: Video highlights',
498 'description': 'BBC reports and video highlights at the Farnborough Airshow.',
503 # article with multiple videos embedded with `new SMP()`
504 'url': 'http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460',
506 'id': '3662a707-0af9-3149-963f-47bea720b460',
507 'title': 'BBC Blogs - Adam Curtis - BUGGER',
509 'playlist_count': 18,
511 # single video embedded with mediaAssetPage.init()
512 'url': 'http://www.bbc.com/news/world-europe-32041533',
516 'title': 'Aerial footage showed the site of the crash in the Alps - courtesy BFM TV',
518 'timestamp': 1427219242,
519 'upload_date': '20150324',
523 'skip_download': True,
526 # article with single video embedded with data-media-meta containing
527 # direct video links (for now these are extracted) and playlist.xml (with
528 # media items as f4m and m3u8 - currently unsupported)
529 'url': 'http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu',
531 'id': '150615_telabyad_kentin_cogu',
533 'title': "YPG: Tel Abyad'ın tamamı kontrolümüzde",
535 'timestamp': 1434397334,
536 'upload_date': '20150615',
539 'skip_download': True,
542 # single video embedded with mediaAssetPage.init() (regional section)
543 'url': 'http://www.bbc.com/mundo/video_fotos/2015/06/150619_video_honduras_militares_hospitales_corrupcion_aw',
545 'id': '150619_video_honduras_militares_hospitales_corrupcion_aw',
547 'title': 'Honduras militariza sus hospitales por nuevo escándalo de corrupción',
549 'timestamp': 1434713142,
550 'upload_date': '20150619',
553 'skip_download': True,
556 # single video from video playlist embedded with vxp-playlist-data JSON
557 'url': 'http://www.bbc.com/news/video_and_audio/must_see/33376376',
561 'title': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
565 'skip_download': True,
568 # single video story with digitalData
569 'url': 'http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret',
573 'title': 'Sri Lanka’s spicy secret',
574 'description': 'As a new train line to Jaffna opens up the country’s north, travellers can experience a truly distinct slice of Tamil culture.',
575 'timestamp': 1437674293,
576 'upload_date': '20150723',
580 'skip_download': True,
583 # single video story without digitalData
584 'url': 'http://www.bbc.com/autos/story/20130513-hyundais-rock-star',
588 'title': 'Hyundai Santa Fe Sport: Rock star',
589 'description': 'md5:b042a26142c4154a6e472933cf20793d',
590 'timestamp': 1368473503,
591 'upload_date': '20130513',
595 'skip_download': True,
598 # single video with playlist.sxml URL
599 'url': 'http://www.bbc.com/sport/0/football/33653409',
603 'title': 'Transfers: Cristiano Ronaldo to Man Utd, Arsenal to spend?',
604 'description': 'md5:398fca0e2e701c609d726e034fa1fc89',
609 'skip_download': True,
612 # single video with playlist URL from weather section
613 'url': 'http://www.bbc.com/weather/features/33601775',
614 'only_matching': True,
616 # custom redirection to www.bbc.com
617 'url': 'http://www.bbc.co.uk/news/science-environment-33661876',
618 'only_matching': True,
622 def suitable(cls
, url
):
623 return False if BBCCoUkIE
.suitable(url
) else super(BBCIE
, cls
).suitable(url
)
625 def _extract_from_media_meta(self
, media_meta
, video_id
):
626 # Direct links to media in media metadata (e.g.
627 # http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
628 # TODO: there are also f4m and m3u8 streams incorporated in playlist.sxml
629 source_files
= media_meta
.get('sourceFiles')
633 'format_id': format_id
,
634 'ext': f
.get('encoding'),
635 'tbr': float_or_none(f
.get('bitrate'), 1000),
636 'filesize': int_or_none(f
.get('filesize')),
637 } for format_id
, f
in source_files
.items() if f
.get('url')], []
639 programme_id
= media_meta
.get('externalId')
641 return self
._download
_media
_selector
(programme_id
)
643 # Process playlist.sxml as legacy playlist
644 href
= media_meta
.get('href')
646 playlist
= self
._download
_legacy
_playlist
_url
(href
)
647 _
, _
, _
, _
, formats
, subtitles
= self
._extract
_from
_legacy
_playlist
(playlist
, video_id
)
648 return formats
, subtitles
652 def _real_extract(self
, url
):
653 playlist_id
= self
._match
_id
(url
)
655 webpage
= self
._download
_webpage
(url
, playlist_id
)
657 timestamp
= parse_iso8601(self
._search
_regex
(
658 [r
'"datePublished":\s*"([^"]+)',
659 r
'<meta[^>]+property="article:published_time"[^>]+content="([^"]+)"',
660 r
'itemprop="datePublished"[^>]+datetime="([^"]+)"'],
661 webpage
, 'date', default
=None))
663 # article with multiple videos embedded with playlist.sxml (e.g.
664 # http://www.bbc.com/sport/0/football/34475836)
665 playlists
= re
.findall(r
'<param[^>]+name="playlist"[^>]+value="([^"]+)"', webpage
)
668 for playlist
in playlists
:
669 programme_id
, title
, description
, duration
, formats
, subtitles
= \
670 self
._process
_legacy
_playlist
_url
(playlist
, playlist_id
)
671 self
._sort
_formats
(formats
)
675 'description': description
,
676 'duration': duration
,
677 'timestamp': timestamp
,
679 'subtitles': subtitles
,
681 playlist_title
= self
._og
_search
_title
(webpage
)
682 playlist_description
= self
._og
_search
_description
(webpage
)
683 return self
.playlist_result(entries
, playlist_id
, playlist_title
, playlist_description
)
685 # single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
686 programme_id
= self
._search
_regex
(
687 [r
'data-video-player-vpid="([\da-z]{8})"',
688 r
'<param[^>]+name="externalIdentifier"[^>]+value="([\da-z]{8})"'],
689 webpage
, 'vpid', default
=None)
693 # single video in news article embedded with data-playable (e.g.
694 # http://www.bbc.com/news/world-us-canada-34473351)
695 data_playable
= self
._parse
_json
(
696 unescapeHTML(self
._search
_regex
(
697 r
'data-playable="({.+?})"', webpage
, 'data playable', default
='{}')),
698 programme_id
, fatal
=False)
700 items
= data_playable
.get('settings', {}).get('playlistObject', {}
).get('items')
701 if items
and isinstance(items
, list):
702 duration
= int_or_none(items
[0].get('duration'))
703 programme_id
= items
[0].get('vpid')
706 formats
, subtitles
= self
._download
_media
_selector
(programme_id
)
707 self
._sort
_formats
(formats
)
708 # digitalData may be missing (e.g. http://www.bbc.com/autos/story/20130513-hyundais-rock-star)
709 digital_data
= self
._parse
_json
(
711 r
'var\s+digitalData\s*=\s*({.+?});?\n', webpage
, 'digital data', default
='{}'),
712 programme_id
, fatal
=False)
713 page_info
= digital_data
.get('page', {}).get('pageInfo', {}
)
714 title
= page_info
.get('pageName') or self
._og
_search
_title
(webpage
)
715 description
= page_info
.get('description') or self
._og
_search
_description
(webpage
)
716 timestamp
= parse_iso8601(page_info
.get('publicationDate')) or timestamp
720 'description': description
,
721 'timestamp': timestamp
,
722 'duration': duration
,
724 'subtitles': subtitles
,
727 playlist_title
= self
._html
_search
_regex
(
728 r
'<title>(.*?)(?:\s*-\s*BBC [^ ]+)?</title>', webpage
, 'playlist title')
729 playlist_description
= self
._og
_search
_description
(webpage
, default
=None)
731 def extract_all(pattern
):
732 return list(filter(None, map(
733 lambda s
: self
._parse
_json
(s
, playlist_id
, fatal
=False),
734 re
.findall(pattern
, webpage
))))
736 # Multiple video article (e.g.
737 # http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
738 EMBED_URL
= r
'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+[\da-z]{8}(?:\b[^"]+)?'
740 for match
in extract_all(r
'new\s+SMP\(({.+?})\)'):
741 embed_url
= match
.get('playerSettings', {}).get('externalEmbedUrl')
742 if embed_url
and re
.match(EMBED_URL
, embed_url
):
743 entries
.append(embed_url
)
744 entries
.extend(re
.findall(
745 r
'setPlaylist\("(%s)"\)' % EMBED_URL
, webpage
))
747 return self
.playlist_result(
748 [self
.url_result(entry
, 'BBCCoUk') for entry
in entries
],
749 playlist_id
, playlist_title
, playlist_description
)
751 # Multiple video article (e.g. http://www.bbc.com/news/world-europe-32668511)
752 medias
= extract_all(r
"data-media-meta='({[^']+})'")
755 # Single video article (e.g. http://www.bbc.com/news/video_and_audio/international)
756 media_asset
= self
._search
_regex
(
757 r
'mediaAssetPage\.init\(\s*({.+?}), "/',
758 webpage
, 'media asset', default
=None)
760 media_asset_page
= self
._parse
_json
(media_asset
, playlist_id
, fatal
=False)
762 for video
in media_asset_page
.get('videos', {}).values():
763 medias
.extend(video
.values())
766 # Multiple video playlist with single `now playing` entry (e.g.
767 # http://www.bbc.com/news/video_and_audio/must_see/33767813)
768 vxp_playlist
= self
._parse
_json
(
770 r
'<script[^>]+class="vxp-playlist-data"[^>]+type="application/json"[^>]*>([^<]+)</script>',
771 webpage
, 'playlist data'),
774 for item
in vxp_playlist
:
775 media
= item
.get('media')
778 playlist_medias
.append(media
)
779 # Download single video if found media with asset id matching the video id from URL
780 if item
.get('advert', {}).get('assetId') == playlist_id
:
783 # Fallback to the whole playlist
785 medias
= playlist_medias
788 for num
, media_meta
in enumerate(medias
, start
=1):
789 formats
, subtitles
= self
._extract
_from
_media
_meta
(media_meta
, playlist_id
)
792 self
._sort
_formats
(formats
)
794 video_id
= media_meta
.get('externalId')
796 video_id
= playlist_id
if len(medias
) == 1 else '%s-%s' % (playlist_id
, num
)
798 title
= media_meta
.get('caption')
800 title
= playlist_title
if len(medias
) == 1 else '%s - Video %s' % (playlist_title
, num
)
802 duration
= int_or_none(media_meta
.get('durationInSeconds')) or parse_duration(media_meta
.get('duration'))
805 for image
in media_meta
.get('images', {}).values():
806 images
.extend(image
.values())
807 if 'image' in media_meta
:
808 images
.append(media_meta
['image'])
811 'url': image
.get('href'),
812 'width': int_or_none(image
.get('width')),
813 'height': int_or_none(image
.get('height')),
814 } for image
in images
]
819 'thumbnails': thumbnails
,
820 'duration': duration
,
821 'timestamp': timestamp
,
823 'subtitles': subtitles
,
826 return self
.playlist_result(entries
, playlist_id
, playlist_title
, playlist_description
)