]>
jfr.im git - yt-dlp.git/blob - youtube_dl/extractor/bbc.py
2 from __future__
import unicode_literals
5 import xml
. etree
. ElementTree
7 from . common
import InfoExtractor
15 from .. compat
import compat_HTTPError
18 class BBCCoUkIE ( InfoExtractor
):
20 IE_DESC
= 'BBC iPlayer'
21 _VALID_URL
= r
'https?://(?:www\.)?bbc\.co\.uk/(?:(?:(?:programmes|iplayer(?:/[^/]+)?/(?:episode|playlist))/)|music/clips[/#])(?P<id>[\da-z] {8} )'
23 _MEDIASELECTOR_URLS
= [
24 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/ %s ' ,
25 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/ %s ' ,
30 'url' : 'http://www.bbc.co.uk/programmes/b039g8p7' ,
34 'title' : 'Kaleidoscope, Leonard Cohen' ,
35 'description' : 'The Canadian poet and songwriter reflects on his musical career.' ,
40 'skip_download' : True ,
44 'url' : 'http://www.bbc.co.uk/iplayer/episode/b00yng5w/The_Man_in_Black_Series_3_The_Printed_Name/' ,
48 'title' : 'The Man in Black: Series 3: The Printed Name' ,
49 'description' : "Mark Gatiss introduces Nicholas Pierpan's chilling tale of a writer's devilish pact with a mysterious man. Stars Ewan Bailey." ,
54 'skip_download' : True ,
56 'skip' : 'Episode is no longer available on BBC iPlayer Radio' ,
59 'url' : 'http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/' ,
63 'title' : 'The Voice UK: Series 3: Blind Auditions 5' ,
64 '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." ,
69 'skip_download' : True ,
71 'skip' : 'Currently BBC iPlayer TV programmes are available to play in the UK only' ,
74 'url' : 'http://www.bbc.co.uk/iplayer/episode/p026c7jt/tomorrows-worlds-the-unearthly-history-of-science-fiction-2-invasion' ,
78 'title' : "Tomorrow's Worlds: The Unearthly History of Science Fiction" ,
79 'description' : '2. Invasion' ,
84 'skip_download' : True ,
86 'skip' : 'Currently BBC iPlayer TV programmes are available to play in the UK only' ,
88 'url' : 'http://www.bbc.co.uk/programmes/b04v20dw' ,
92 'title' : 'Pete Tong, The Essential New Tune Special' ,
93 'description' : "Pete has a very special mix - all of 2014's Essential New Tunes!" ,
98 'skip_download' : True ,
101 'url' : 'http://www.bbc.co.uk/music/clips/p02frcc3' ,
106 'title' : 'Pete Tong, Past, Present and Future Special, Madeon - After Hours mix' ,
107 'description' : 'French house superstar Madeon takes us out of the club and onto the after party.' ,
112 'skip_download' : True ,
115 'url' : 'http://www.bbc.co.uk/music/clips/p025c0zz' ,
120 'title' : 'Reading and Leeds Festival, 2014, Rae Morris - Closer (Live on BBC Three)' ,
121 'description' : 'Rae Morris performs Closer for BBC Three at Reading 2014' ,
126 'skip_download' : True ,
129 'url' : 'http://www.bbc.co.uk/iplayer/episode/b054fn09/ad/natural-world-20152016-2-super-powered-owls' ,
133 'title' : 'Natural World, 2015-2016: 2. Super Powered Owls' ,
134 'description' : 'md5:e4db5c937d0e95a7c6b5e654d429183d' ,
139 'skip_download' : True ,
141 'skip' : 'geolocation' ,
143 'url' : 'http://www.bbc.co.uk/iplayer/episode/b05zmgwn/royal-academy-summer-exhibition' ,
147 '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.' ,
148 'title' : 'Royal Academy Summer Exhibition' ,
153 'skip_download' : True ,
155 'skip' : 'geolocation' ,
157 'url' : 'http://www.bbc.co.uk/iplayer/playlist/p01dvks4' ,
158 'only_matching' : True ,
160 'url' : 'http://www.bbc.co.uk/music/clips#p02frcc3' ,
161 'only_matching' : True ,
163 'url' : 'http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo' ,
164 'only_matching' : True ,
168 class MediaSelectionError ( Exception ):
169 def __init__ ( self
, id ):
172 def _extract_asx_playlist ( self
, connection
, programme_id
):
173 asx
= self
._ download
_ xml
( connection
. get ( 'href' ), programme_id
, 'Downloading ASX playlist' )
174 return [ ref
. get ( 'href' ) for ref
in asx
. findall ( './Entry/ref' )]
176 def _extract_connection ( self
, connection
, programme_id
):
178 protocol
= connection
. get ( 'protocol' )
179 supplier
= connection
. get ( 'supplier' )
180 if protocol
== 'http' :
181 href
= connection
. get ( 'href' )
182 transfer_format
= connection
. get ( 'transferFormat' )
184 if supplier
== 'asx' :
185 for i
, ref
in enumerate ( self
._ extract
_ asx
_ playlist
( connection
, programme_id
)):
188 'format_id' : 'ref %s _ %s ' % ( i
, supplier
),
190 # Skip DASH until supported
191 elif transfer_format
== 'dash' :
193 elif transfer_format
== 'hls' :
194 m3u8_formats
= self
._ extract
_ m
3u8_ formats
(
195 href
, programme_id
, ext
= 'mp4' , entry_protocol
= 'm3u8_native' ,
196 m3u8_id
= supplier
, fatal
= False )
198 formats
. extend ( m3u8_formats
)
203 'format_id' : supplier
,
205 elif protocol
== 'rtmp' :
206 application
= connection
. get ( 'application' , 'ondemand' )
207 auth_string
= connection
. get ( 'authString' )
208 identifier
= connection
. get ( 'identifier' )
209 server
= connection
. get ( 'server' )
211 'url' : ' %s :// %s / %s ? %s ' % ( protocol
, server
, application
, auth_string
),
212 'play_path' : identifier
,
213 'app' : ' %s ? %s ' % ( application
, auth_string
),
214 'page_url' : 'http://www.bbc.co.uk' ,
215 'player_url' : 'http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf' ,
218 'format_id' : supplier
,
222 def _extract_items ( self
, playlist
):
223 return playlist
. findall ( './ {http://bbc.co.uk/2008/emp/playlist} item' )
225 def _extract_medias ( self
, media_selection
):
226 error
= media_selection
. find ( './ {http://bbc.co.uk/2008/mp/mediaselection} error' )
227 if error
is not None :
228 raise BBCCoUkIE
. MediaSelectionError ( error
. get ( 'id' ))
229 return media_selection
. findall ( './ {http://bbc.co.uk/2008/mp/mediaselection} media' )
231 def _extract_connections ( self
, media
):
232 return media
. findall ( './ {http://bbc.co.uk/2008/mp/mediaselection} connection' )
234 def _extract_video ( self
, media
, programme_id
):
236 vbr
= int_or_none ( media
. get ( 'bitrate' ))
237 vcodec
= media
. get ( 'encoding' )
238 service
= media
. get ( 'service' )
239 width
= int_or_none ( media
. get ( 'width' ))
240 height
= int_or_none ( media
. get ( 'height' ))
241 file_size
= int_or_none ( media
. get ( 'media_file_size' ))
242 for connection
in self
._ extract
_ connections
( media
):
243 conn_formats
= self
._ extract
_ connection
( connection
, programme_id
)
244 for format
in conn_formats
:
246 'format_id' : ' %s _ %s ' % ( service
, format
[ 'format_id' ]),
251 'filesize' : file_size
,
253 formats
. extend ( conn_formats
)
256 def _extract_audio ( self
, media
, programme_id
):
258 abr
= int_or_none ( media
. get ( 'bitrate' ))
259 acodec
= media
. get ( 'encoding' )
260 service
= media
. get ( 'service' )
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' ]),
269 formats
. extend ( conn_formats
)
272 def _get_subtitles ( self
, media
, programme_id
):
274 for connection
in self
._ extract
_ connections
( media
):
275 captions
= self
._ download
_ xml
( connection
. get ( 'href' ), programme_id
, 'Downloading captions' )
276 lang
= captions
. get ( ' {http://www.w3.org/XML/1998/namespace} lang' , 'en' )
279 'url' : connection
. get ( 'href' ),
285 def _raise_extractor_error ( self
, media_selection_error
):
286 raise ExtractorError (
287 ' %s returned error: %s ' % ( self
. IE_NAME
, media_selection_error
. id ),
290 def _download_media_selector ( self
, programme_id
):
291 last_exception
= None
292 for mediaselector_url
in self
._ MEDIASELECTOR
_U RLS
:
294 return self
._ download
_ media
_ selector
_u rl
(
295 mediaselector_url
% programme_id
, programme_id
)
296 except BBCCoUkIE
. MediaSelectionError
as e
:
297 if e
. id == 'notukerror' :
300 self
._ raise
_ extractor
_ error
( e
)
301 self
._ raise
_ extractor
_ error
( last_exception
)
303 def _download_media_selector_url ( self
, url
, programme_id
= None ):
305 media_selection
= self
._ download
_ xml
(
306 url
, programme_id
, 'Downloading media selection XML' )
307 except ExtractorError
as ee
:
308 if isinstance ( ee
. cause
, compat_HTTPError
) and ee
. cause
. code
== 403 :
309 media_selection
= xml
. etree
. ElementTree
. fromstring ( ee
. cause
. read (). decode ( 'utf-8' ))
312 return self
._ process
_ media
_ selector
( media_selection
, programme_id
)
314 def _process_media_selector ( self
, media_selection
, programme_id
):
318 for media
in self
._ extract
_ medias
( media_selection
):
319 kind
= media
. get ( 'kind' )
321 formats
. extend ( self
._ extract
_ audio
( media
, programme_id
))
322 elif kind
== 'video' :
323 formats
. extend ( self
._ extract
_ video
( media
, programme_id
))
324 elif kind
== 'captions' :
325 subtitles
= self
. extract_subtitles ( media
, programme_id
)
326 return formats
, subtitles
328 def _download_playlist ( self
, playlist_id
):
330 playlist
= self
._ download
_ json
(
331 'http://www.bbc.co.uk/programmes/ %s /playlist.json' % playlist_id
,
332 playlist_id
, 'Downloading playlist JSON' )
334 version
= playlist
. get ( 'defaultAvailableVersion' )
336 smp_config
= version
[ 'smpConfig' ]
337 title
= smp_config
[ 'title' ]
338 description
= smp_config
[ 'summary' ]
339 for item
in smp_config
[ 'items' ]:
341 if kind
!= 'programme' and kind
!= 'radioProgramme' :
343 programme_id
= item
. get ( 'vpid' )
344 duration
= int_or_none ( item
. get ( 'duration' ))
345 formats
, subtitles
= self
._ download
_ media
_ selector
( programme_id
)
346 return programme_id
, title
, description
, duration
, formats
, subtitles
347 except ExtractorError
as ee
:
348 if not ( isinstance ( ee
. cause
, compat_HTTPError
) and ee
. cause
. code
== 404 ):
351 # fallback to legacy playlist
352 return self
._ process
_l egacy
_ playlist
( playlist_id
)
354 def _process_legacy_playlist_url ( self
, url
, display_id
):
355 playlist
= self
._ download
_l egacy
_ playlist
_u rl
( url
, display_id
)
356 return self
._ extract
_ from
_l egacy
_ playlist
( playlist
, display_id
)
358 def _process_legacy_playlist ( self
, playlist_id
):
359 return self
._ process
_l egacy
_ playlist
_u rl
(
360 'http://www.bbc.co.uk/iplayer/playlist/ %s ' % playlist_id
, playlist_id
)
362 def _download_legacy_playlist_url ( self
, url
, playlist_id
= None ):
363 return self
._ download
_ xml
(
364 url
, playlist_id
, 'Downloading legacy playlist XML' )
366 def _extract_from_legacy_playlist ( self
, playlist
, playlist_id
):
367 no_items
= playlist
. find ( './ {http://bbc.co.uk/2008/emp/playlist} noItems' )
368 if no_items
is not None :
369 reason
= no_items
. get ( 'reason' )
370 if reason
== 'preAvailability' :
371 msg
= 'Episode %s is not yet available' % playlist_id
372 elif reason
== 'postAvailability' :
373 msg
= 'Episode %s is no longer available' % playlist_id
374 elif reason
== 'noMedia' :
375 msg
= 'Episode %s is not currently available' % playlist_id
377 msg
= 'Episode %s is not available: %s ' % ( playlist_id
, reason
)
378 raise ExtractorError ( msg
, expected
= True )
380 for item
in self
._ extract
_ items
( playlist
):
381 kind
= item
. get ( 'kind' )
382 if kind
!= 'programme' and kind
!= 'radioProgramme' :
384 title
= playlist
. find ( './ {http://bbc.co.uk/2008/emp/playlist} title' ). text
385 description
= playlist
. find ( './ {http://bbc.co.uk/2008/emp/playlist} summary' ). text
387 def get_programme_id ( item
):
388 def get_from_attributes ( item
):
389 for p
in ( 'identifier' , 'group' ):
391 if value
and re
. match ( r
'^[pb][\da-z] {7} $' , value
):
393 get_from_attributes ( item
)
394 mediator
= item
. find ( './ {http://bbc.co.uk/2008/emp/playlist} mediator' )
395 if mediator
is not None :
396 return get_from_attributes ( mediator
)
398 programme_id
= get_programme_id ( item
)
399 duration
= int_or_none ( item
. get ( 'duration' ))
400 # TODO: programme_id can be None and media items can be incorporated right inside
401 # playlist's item (e.g. http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
403 formats
, subtitles
= self
._ download
_ media
_ selector
( programme_id
)
405 return programme_id
, title
, description
, duration
, formats
, subtitles
407 def _real_extract ( self
, url
):
408 group_id
= self
._ match
_ id
( url
)
410 webpage
= self
._ download
_ webpage
( url
, group_id
, 'Downloading video page' )
414 tviplayer
= self
._ search
_ regex
(
415 r
'mediator\.bind\(( {.+?} )\s*,\s*document\.getElementById' ,
416 webpage
, 'player' , default
= None )
419 player
= self
._ parse
_ json
( tviplayer
, group_id
). get ( 'player' , {})
420 duration
= int_or_none ( player
. get ( 'duration' ))
421 programme_id
= player
. get ( 'vpid' )
424 programme_id
= self
._ search
_ regex
(
425 r
'"vpid"\s*:\s*"([\da-z] {8} )"' , webpage
, 'vpid' , fatal
= False , default
= None )
428 formats
, subtitles
= self
._ download
_ media
_ selector
( programme_id
)
429 title
= self
._ og
_ search
_ title
( webpage
)
430 description
= self
._ search
_ regex
(
431 r
'<p class="[^"]*medium-description[^"]*">([^<]+)</p>' ,
432 webpage
, 'description' , fatal
= False )
434 programme_id
, title
, description
, duration
, formats
, subtitles
= self
._ download
_ playlist
( group_id
)
436 self
._ sort
_ formats
( formats
)
441 'description' : description
,
442 'thumbnail' : self
._ og
_ search
_ thumbnail
( webpage
, default
= None ),
443 'duration' : duration
,
445 'subtitles' : subtitles
,
449 class BBCIE ( BBCCoUkIE
):
452 _VALID_URL
= r
'https?://(?:www\.)?bbc\.(?:com|co\.uk)/(?:[^/]+/)+(?P<id>[^/#?]+)'
454 _MEDIASELECTOR_URLS
= [
455 # Provides more formats, namely direct mp4 links, but fails on some videos with
456 # notukerror for non UK (?) users (e.g.
457 # http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
458 'http://open.live.bbc.co.uk/mediaselector/4/mtis/stream/ %s ' ,
459 # Provides fewer formats, but works everywhere for everybody (hopefully)
460 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/journalism-pc/vpid/ %s ' ,
464 # article with multiple videos embedded with data-media-meta containing
465 # playlist.sxml, externalId and no direct video links
466 'url' : 'http://www.bbc.com/news/world-europe-32668511' ,
468 'id' : 'world-europe-32668511' ,
469 'title' : 'Russia stages massive WW2 parade despite Western boycott' ,
470 'description' : 'md5:00ff61976f6081841f759a08bf78cc9c' ,
474 # article with multiple videos embedded with data-media-meta (more videos)
475 'url' : 'http://www.bbc.com/news/business-28299555' ,
477 'id' : 'business-28299555' ,
478 'title' : 'Farnborough Airshow: Video highlights' ,
479 'description' : 'BBC reports and video highlights at the Farnborough Airshow.' ,
484 # article with multiple videos embedded with `new SMP()`
485 'url' : 'http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460' ,
487 'id' : '3662a707-0af9-3149-963f-47bea720b460' ,
488 'title' : 'BBC Blogs - Adam Curtis - BUGGER' ,
490 'playlist_count' : 18 ,
492 # single video embedded with mediaAssetPage.init()
493 'url' : 'http://www.bbc.com/news/world-europe-32041533' ,
497 'title' : 'Aerial footage showed the site of the crash in the Alps - courtesy BFM TV' ,
499 'timestamp' : 1427219242 ,
500 'upload_date' : '20150324' ,
504 'skip_download' : True ,
507 # article with single video embedded with data-media-meta containing
508 # direct video links (for now these are extracted) and playlist.xml (with
509 # media items as f4m and m3u8 - currently unsupported)
510 'url' : 'http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu' ,
512 'id' : '150615_telabyad_kentin_cogu' ,
514 'title' : "YPG: Tel Abyad'ın tamamı kontrolümüzde" ,
516 'timestamp' : 1434397334 ,
517 'upload_date' : '20150615' ,
520 'skip_download' : True ,
523 # single video embedded with mediaAssetPage.init() (regional section)
524 'url' : 'http://www.bbc.com/mundo/video_fotos/2015/06/150619_video_honduras_militares_hospitales_corrupcion_aw' ,
526 'id' : '150619_video_honduras_militares_hospitales_corrupcion_aw' ,
528 'title' : 'Honduras militariza sus hospitales por nuevo escándalo de corrupción' ,
530 'timestamp' : 1434713142 ,
531 'upload_date' : '20150619' ,
534 'skip_download' : True ,
537 # single video from video playlist embedded with vxp-playlist-data JSON
538 'url' : 'http://www.bbc.com/news/video_and_audio/must_see/33376376' ,
542 'title' : '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''' ,
546 'skip_download' : True ,
549 # single video story with digitalData
550 'url' : 'http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret' ,
554 'title' : 'Sri Lanka’s spicy secret' ,
555 'description' : 'As a new train line to Jaffna opens up the country’s north, travellers can experience a truly distinct slice of Tamil culture.' ,
556 'timestamp' : 1437674293 ,
557 'upload_date' : '20150723' ,
561 'skip_download' : True ,
564 # single video story without digitalData
565 'url' : 'http://www.bbc.com/autos/story/20130513-hyundais-rock-star' ,
569 'title' : 'Hyundai Santa Fe Sport: Rock star' ,
570 'description' : 'md5:b042a26142c4154a6e472933cf20793d' ,
571 'timestamp' : 1368473503 ,
572 'upload_date' : '20130513' ,
576 'skip_download' : True ,
579 # single video with playlist.sxml URL
580 'url' : 'http://www.bbc.com/sport/0/football/33653409' ,
584 'title' : 'Transfers: Cristiano Ronaldo to Man Utd, Arsenal to spend?' ,
585 'description' : 'md5:398fca0e2e701c609d726e034fa1fc89' ,
590 'skip_download' : True ,
593 # single video with playlist URL from weather section
594 'url' : 'http://www.bbc.com/weather/features/33601775' ,
595 'only_matching' : True ,
597 # custom redirection to www.bbc.com
598 'url' : 'http://www.bbc.co.uk/news/science-environment-33661876' ,
599 'only_matching' : True ,
603 def suitable ( cls
, url
):
604 return False if BBCCoUkIE
. suitable ( url
) else super ( BBCIE
, cls
). suitable ( url
)
606 def _extract_from_media_meta ( self
, media_meta
, video_id
):
607 # Direct links to media in media metadata (e.g.
608 # http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
609 # TODO: there are also f4m and m3u8 streams incorporated in playlist.sxml
610 source_files
= media_meta
. get ( 'sourceFiles' )
614 'format_id' : format_id
,
615 'ext' : f
. get ( 'encoding' ),
616 'tbr' : float_or_none ( f
. get ( 'bitrate' ), 1000 ),
617 'filesize' : int_or_none ( f
. get ( 'filesize' )),
618 } for format_id
, f
in source_files
. items () if f
. get ( 'url' )], []
620 programme_id
= media_meta
. get ( 'externalId' )
622 return self
._ download
_ media
_ selector
( programme_id
)
624 # Process playlist.sxml as legacy playlist
625 href
= media_meta
. get ( 'href' )
627 playlist
= self
._ download
_l egacy
_ playlist
_u rl
( href
)
628 _
, _
, _
, _
, formats
, subtitles
= self
._ extract
_ from
_l egacy
_ playlist
( playlist
, video_id
)
629 return formats
, subtitles
633 def _real_extract ( self
, url
):
634 playlist_id
= self
._ match
_ id
( url
)
636 webpage
= self
._ download
_ webpage
( url
, playlist_id
)
638 timestamp
= parse_iso8601 ( self
._ search
_ regex
(
639 [ r
'"datePublished":\s*"([^"]+)' ,
640 r
'<meta[^>]+property="article:published_time"[^>]+content="([^"]+)"' ,
641 r
'itemprop="datePublished"[^>]+datetime="([^"]+)"' ],
642 webpage
, 'date' , default
= None ))
644 # single video with playlist.sxml URL (e.g. http://www.bbc.com/sport/0/football/3365340ng)
645 playlist
= self
._ search
_ regex
(
646 r
'<param[^>]+name="playlist"[^>]+value="([^"]+)"' ,
647 webpage
, 'playlist' , default
= None )
649 programme_id
, title
, description
, duration
, formats
, subtitles
= \
650 self
._ process
_l egacy
_ playlist
_u rl
( playlist
, playlist_id
)
651 self
._ sort
_ formats
( formats
)
655 'description' : description
,
656 'duration' : duration
,
657 'timestamp' : timestamp
,
659 'subtitles' : subtitles
,
662 # single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
663 programme_id
= self
._ search
_ regex
(
664 [ r
'data-video-player-vpid="([\da-z] {8} )"' ,
665 r
'<param[^>]+name="externalIdentifier"[^>]+value="([\da-z] {8} )"' ],
666 webpage
, 'vpid' , default
= None )
668 formats
, subtitles
= self
._ download
_ media
_ selector
( programme_id
)
669 self
._ sort
_ formats
( formats
)
670 # digitalData may be missing (e.g. http://www.bbc.com/autos/story/20130513-hyundais-rock-star)
671 digital_data
= self
._ parse
_ json
(
673 r
'var\s+digitalData\s*=\s*( {.+?} );?\n' , webpage
, 'digital data' , default
= '{}' ),
674 programme_id
, fatal
= False )
675 page_info
= digital_data
. get ( 'page' , {}).get('pageInfo', {}
)
676 title
= page_info
. get ( 'pageName' ) or self
._ og
_ search
_ title
( webpage
)
677 description
= page_info
. get ( 'description' ) or self
._ og
_ search
_ description
( webpage
)
678 timestamp
= parse_iso8601 ( page_info
. get ( 'publicationDate' )) or timestamp
682 'description' : description
,
683 'timestamp' : timestamp
,
685 'subtitles' : subtitles
,
688 playlist_title
= self
._ html
_ search
_ regex
(
689 r
'<title>(.*?)(?:\s*-\s*BBC [^ ]+)?</title>' , webpage
, 'playlist title' )
690 playlist_description
= self
._ og
_ search
_ description
( webpage
, default
= None )
692 def extract_all ( pattern
):
693 return list ( filter ( None , map (
694 lambda s
: self
._ parse
_ json
( s
, playlist_id
, fatal
= False ),
695 re
. findall ( pattern
, webpage
))))
697 # Multiple video article (e.g.
698 # http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
699 EMBED_URL
= r
'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+[\da-z] {8} (?:\b[^"]+)?'
701 for match
in extract_all ( r
'new\s+SMP\(( {.+?} )\)' ):
702 embed_url
= match
. get ( 'playerSettings' , {}). get ( 'externalEmbedUrl' )
703 if embed_url
and re
. match ( EMBED_URL
, embed_url
):
704 entries
. append ( embed_url
)
705 entries
. extend ( re
. findall (
706 r
'setPlaylist\("( %s )"\)' % EMBED_URL
, webpage
))
708 return self
. playlist_result (
709 [ self
. url_result ( entry
, 'BBCCoUk' ) for entry
in entries
],
710 playlist_id
, playlist_title
, playlist_description
)
712 # Multiple video article (e.g. http://www.bbc.com/news/world-europe-32668511)
713 medias
= extract_all ( r
"data-media-meta='( {[^']+} )'" )
716 # Single video article (e.g. http://www.bbc.com/news/video_and_audio/international)
717 media_asset
= self
._ search
_ regex
(
718 r
'mediaAssetPage\.init\(\s*( {.+?} ), "/' ,
719 webpage
, 'media asset' , default
= None )
721 media_asset_page
= self
._ parse
_ json
( media_asset
, playlist_id
, fatal
= False )
723 for video
in media_asset_page
. get ( 'videos' , {}). values ():
724 medias
. extend ( video
. values ())
727 # Multiple video playlist with single `now playing` entry (e.g.
728 # http://www.bbc.com/news/video_and_audio/must_see/33767813)
729 vxp_playlist
= self
._ parse
_ json
(
731 r
'<script[^>]+class="vxp-playlist-data"[^>]+type="application/json"[^>]*>([^<]+)</script>' ,
732 webpage
, 'playlist data' ),
735 for item
in vxp_playlist
:
736 media
= item
. get ( 'media' )
739 playlist_medias
. append ( media
)
740 # Download single video if found media with asset id matching the video id from URL
741 if item
. get ( 'advert' , {}). get ( 'assetId' ) == playlist_id
:
744 # Fallback to the whole playlist
746 medias
= playlist_medias
749 for num
, media_meta
in enumerate ( medias
, start
= 1 ):
750 formats
, subtitles
= self
._ extract
_ from
_ media
_ meta
( media_meta
, playlist_id
)
753 self
._ sort
_ formats
( formats
)
755 video_id
= media_meta
. get ( 'externalId' )
757 video_id
= playlist_id
if len ( medias
) == 1 else ' %s-%s ' % ( playlist_id
, num
)
759 title
= media_meta
. get ( 'caption' )
761 title
= playlist_title
if len ( medias
) == 1 else ' %s - Video %s ' % ( playlist_title
, num
)
763 duration
= int_or_none ( media_meta
. get ( 'durationInSeconds' )) or parse_duration ( media_meta
. get ( 'duration' ))
766 for image
in media_meta
. get ( 'images' , {}). values ():
767 images
. extend ( image
. values ())
768 if 'image' in media_meta
:
769 images
. append ( media_meta
[ 'image' ])
772 'url' : image
. get ( 'href' ),
773 'width' : int_or_none ( image
. get ( 'width' )),
774 'height' : int_or_none ( image
. get ( 'height' )),
775 } for image
in images
]
780 'thumbnails' : thumbnails
,
781 'duration' : duration
,
782 'timestamp' : timestamp
,
784 'subtitles' : subtitles
,
787 return self
. playlist_result ( entries
, playlist_id
, playlist_title
, playlist_description
)