2015-07-25 16:21:42 +02:00
# coding: utf-8
2014-02-08 15:55:28 +01:00
from __future__ import unicode_literals
2016-08-15 23:36:23 +02:00
import itertools
2019-02-24 15:01:25 +01:00
import re
2014-02-08 15:55:28 +01:00
2015-02-19 16:46:41 +01:00
from . common import InfoExtractor
2015-04-30 23:59:13 +02:00
from . . utils import (
2017-05-26 17:12:24 +02:00
clean_html ,
2016-08-07 13:01:50 +02:00
dict_get ,
2015-04-30 23:59:13 +02:00
ExtractorError ,
2015-07-25 16:21:42 +02:00
float_or_none ,
2017-05-26 17:12:24 +02:00
get_element_by_class ,
2015-04-30 23:59:13 +02:00
int_or_none ,
2018-06-02 23:07:59 +02:00
js_to_json ,
2015-07-25 16:21:42 +02:00
parse_duration ,
parse_iso8601 ,
2016-08-07 13:01:50 +02:00
try_get ,
2015-10-10 16:34:06 +02:00
unescapeHTML ,
2019-02-24 15:01:25 +01:00
url_or_none ,
2017-05-26 17:12:24 +02:00
urlencode_postdata ,
urljoin ,
2015-04-30 23:59:13 +02:00
)
2015-10-25 20:04:55 +01:00
from . . compat import (
2019-03-05 19:21:57 +01:00
compat_etree_Element ,
2015-10-25 20:04:55 +01:00
compat_HTTPError ,
2016-08-15 23:36:23 +02:00
compat_urlparse ,
2015-10-25 20:04:55 +01:00
)
2014-02-08 15:55:28 +01:00
2015-07-29 20:55:06 +02:00
2015-02-19 16:46:41 +01:00
class BBCCoUkIE ( InfoExtractor ) :
2014-02-08 15:55:28 +01:00
IE_NAME = ' bbc.co.uk '
2014-02-08 22:00:24 +01:00
IE_DESC = ' BBC iPlayer '
2018-08-19 21:05:07 +02:00
_ID_REGEX = r ' (?:[pbm][ \ da-z] {7} |w[ \ da-z] { 7,14}) '
2016-01-02 14:22:39 +01:00
_VALID_URL = r ''' (?x)
https ? : / /
( ? : www \. ) ? bbc \. co \. uk /
( ? :
programmes / ( ? ! articles / ) |
iplayer ( ? : / [ ^ / ] + ) ? / ( ? : episode / | playlist / ) |
2017-06-29 17:29:28 +02:00
music / ( ? : clips | audiovideo / popular ) [ / #]|
2017-08-19 18:54:15 +02:00
radio / player / |
2019-08-26 17:16:18 +02:00
sounds / play / |
2017-08-19 18:54:15 +02:00
events / [ ^ / ] + / play / [ ^ / ] + /
2016-01-02 14:22:39 +01:00
)
2016-06-17 18:42:52 +02:00
( ? P < id > % s ) ( ? ! / ( ? : episodes | broadcasts | clips ) )
2016-01-02 14:22:39 +01:00
''' % _ID_REGEX
2014-02-08 15:55:28 +01:00
2017-05-26 17:12:24 +02:00
_LOGIN_URL = ' https://account.bbc.com/signin '
_NETRC_MACHINE = ' bbc '
2015-07-29 20:55:06 +02:00
_MEDIASELECTOR_URLS = [
2015-09-26 16:06:21 +02:00
# Provides HQ HLS streams with even better quality that pc mediaset but fails
# with geolocation in some cases when it's even not geo restricted at all (e.g.
2015-11-14 18:08:13 +01:00
# http://www.bbc.co.uk/programmes/b06bp7lf). Also may fail with selectionunavailable.
2015-09-18 20:51:41 +02:00
' http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/ %s ' ,
2015-07-29 20:55:06 +02:00
' http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/ %s ' ,
]
2015-06-19 08:52:25 +02:00
2015-10-10 19:01:20 +02:00
_MEDIASELECTION_NS = ' http://bbc.co.uk/2008/mp/mediaselection '
_EMP_PLAYLIST_NS = ' http://bbc.co.uk/2008/emp/playlist '
_NAMESPACES = (
_MEDIASELECTION_NS ,
_EMP_PLAYLIST_NS ,
)
2014-02-08 22:00:24 +01:00
_TESTS = [
{
2014-02-19 00:46:14 +01:00
' url ' : ' http://www.bbc.co.uk/programmes/b039g8p7 ' ,
2014-02-08 22:00:24 +01:00
' info_dict ' : {
2014-02-19 00:46:14 +01:00
' id ' : ' b039d07m ' ,
2016-07-17 12:29:36 +02:00
' ext ' : ' flv ' ,
2019-08-26 17:04:38 +02:00
' title ' : ' Kaleidoscope, Leonard Cohen ' ,
2015-01-02 17:13:26 +01:00
' description ' : ' The Canadian poet and songwriter reflects on his musical career. ' ,
2014-02-08 22:00:24 +01:00
} ,
' params ' : {
2016-07-17 12:29:36 +02:00
# rtmp download
2014-02-08 22:00:24 +01:00
' skip_download ' : True ,
}
2014-02-08 15:55:28 +01:00
} ,
2014-02-08 22:00:24 +01:00
{
' url ' : ' http://www.bbc.co.uk/iplayer/episode/b00yng5w/The_Man_in_Black_Series_3_The_Printed_Name/ ' ,
' info_dict ' : {
' id ' : ' b00yng1d ' ,
' ext ' : ' flv ' ,
' title ' : ' The Man in Black: Series 3: The Printed Name ' ,
' description ' : " Mark Gatiss introduces Nicholas Pierpan ' s chilling tale of a writer ' s devilish pact with a mysterious man. Stars Ewan Bailey. " ,
' duration ' : 1800 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
2014-02-17 18:26:12 +01:00
} ,
' skip ' : ' Episode is no longer available on BBC iPlayer Radio ' ,
2014-02-08 22:00:24 +01:00
} ,
{
' url ' : ' http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/ ' ,
' info_dict ' : {
' id ' : ' b00yng1d ' ,
' ext ' : ' flv ' ,
2014-02-08 22:04:21 +01:00
' title ' : ' The Voice UK: Series 3: Blind Auditions 5 ' ,
2016-02-14 10:37:17 +01:00
' 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. ' ,
2014-02-08 22:04:21 +01:00
' duration ' : 5100 ,
2014-02-08 22:00:24 +01:00
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
} ,
2016-07-17 12:29:36 +02:00
' skip ' : ' Currently BBC iPlayer TV programmes are available to play in the UK only ' ,
2014-11-30 17:37:56 +01:00
} ,
{
' url ' : ' http://www.bbc.co.uk/iplayer/episode/p026c7jt/tomorrows-worlds-the-unearthly-history-of-science-fiction-2-invasion ' ,
' info_dict ' : {
' id ' : ' b03k3pb7 ' ,
' ext ' : ' flv ' ,
' title ' : " Tomorrow ' s Worlds: The Unearthly History of Science Fiction " ,
' description ' : ' 2. Invasion ' ,
' duration ' : 3600 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
} ,
2016-07-17 12:29:36 +02:00
' skip ' : ' Currently BBC iPlayer TV programmes are available to play in the UK only ' ,
2014-12-28 22:00:24 +01:00
} , {
' url ' : ' http://www.bbc.co.uk/programmes/b04v20dw ' ,
' info_dict ' : {
' id ' : ' b04v209v ' ,
' ext ' : ' flv ' ,
' title ' : ' Pete Tong, The Essential New Tune Special ' ,
' description ' : " Pete has a very special mix - all of 2014 ' s Essential New Tunes! " ,
' duration ' : 10800 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
2015-12-05 09:51:13 +01:00
} ,
' skip ' : ' Episode is no longer available on BBC iPlayer Radio ' ,
2015-01-03 15:43:40 +01:00
} , {
2016-01-03 21:55:25 +01:00
' url ' : ' http://www.bbc.co.uk/music/clips/p022h44b ' ,
2015-01-03 15:43:40 +01:00
' note ' : ' Audio ' ,
' info_dict ' : {
2016-01-03 21:55:25 +01:00
' id ' : ' p022h44j ' ,
2016-07-17 12:29:36 +02:00
' ext ' : ' flv ' ,
2016-01-03 21:55:25 +01:00
' title ' : ' BBC Proms Music Guides, Rachmaninov: Symphonic Dances ' ,
' description ' : " In this Proms Music Guide, Andrew McGregor looks at Rachmaninov ' s Symphonic Dances. " ,
' duration ' : 227 ,
2015-01-03 15:43:40 +01:00
} ,
' params ' : {
2016-07-17 12:29:36 +02:00
# rtmp download
2015-01-03 15:43:40 +01:00
' skip_download ' : True ,
}
} , {
' url ' : ' http://www.bbc.co.uk/music/clips/p025c0zz ' ,
' note ' : ' Video ' ,
' info_dict ' : {
' id ' : ' p025c103 ' ,
2016-07-17 12:29:36 +02:00
' ext ' : ' flv ' ,
2015-01-03 15:43:40 +01:00
' title ' : ' Reading and Leeds Festival, 2014, Rae Morris - Closer (Live on BBC Three) ' ,
' description ' : ' Rae Morris performs Closer for BBC Three at Reading 2014 ' ,
' duration ' : 226 ,
} ,
' params ' : {
2016-07-17 12:29:36 +02:00
# rtmp download
2015-01-03 15:43:40 +01:00
' skip_download ' : True ,
}
2015-05-01 00:02:56 +02:00
} , {
' url ' : ' http://www.bbc.co.uk/iplayer/episode/b054fn09/ad/natural-world-20152016-2-super-powered-owls ' ,
' info_dict ' : {
' id ' : ' p02n76xf ' ,
' ext ' : ' flv ' ,
' title ' : ' Natural World, 2015-2016: 2. Super Powered Owls ' ,
' description ' : ' md5:e4db5c937d0e95a7c6b5e654d429183d ' ,
' duration ' : 3540 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
} ,
2016-07-17 12:29:36 +02:00
' skip ' : ' geolocation ' ,
2015-06-18 16:00:13 +02:00
} , {
' url ' : ' http://www.bbc.co.uk/iplayer/episode/b05zmgwn/royal-academy-summer-exhibition ' ,
' info_dict ' : {
' id ' : ' b05zmgw1 ' ,
' ext ' : ' flv ' ,
' 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. ' ,
' title ' : ' Royal Academy Summer Exhibition ' ,
' duration ' : 3540 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
} ,
2016-07-17 12:29:36 +02:00
' skip ' : ' geolocation ' ,
2015-09-26 16:07:12 +02:00
} , {
# iptv-all mediaset fails with geolocation however there is no geo restriction
# for this programme at all
2016-01-03 21:55:25 +01:00
' url ' : ' http://www.bbc.co.uk/programmes/b06rkn85 ' ,
2015-09-26 16:07:12 +02:00
' info_dict ' : {
2016-01-03 21:55:25 +01:00
' id ' : ' b06rkms3 ' ,
2015-09-26 16:07:12 +02:00
' ext ' : ' flv ' ,
2016-01-03 21:55:25 +01:00
' title ' : " Best of the Mini-Mixes 2015: Part 3, Annie Mac ' s Friday Night - BBC Radio 1 " ,
' description ' : " Annie has part three in the Best of the Mini-Mixes 2015, plus the year ' s Most Played! " ,
2015-09-26 16:07:12 +02:00
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
} ,
2016-07-17 12:29:36 +02:00
' skip ' : ' Now it \' s really geo-restricted ' ,
2016-01-28 18:27:48 +01:00
} , {
2019-03-09 13:14:41 +01:00
# compact player (https://github.com/ytdl-org/youtube-dl/issues/8147)
2016-01-28 18:27:48 +01:00
' url ' : ' http://www.bbc.co.uk/programmes/p028bfkf/player ' ,
' info_dict ' : {
' id ' : ' p028bfkj ' ,
2016-07-17 12:29:36 +02:00
' ext ' : ' flv ' ,
2016-01-28 18:27:48 +01:00
' title ' : ' Extract from BBC documentary Look Stranger - Giant Leeks and Magic Brews ' ,
' description ' : ' Extract from BBC documentary Look Stranger - Giant Leeks and Magic Brews ' ,
} ,
' params ' : {
2016-07-17 12:29:36 +02:00
# rtmp download
2016-01-28 18:27:48 +01:00
' skip_download ' : True ,
} ,
2019-08-26 17:16:18 +02:00
} , {
' url ' : ' https://www.bbc.co.uk/sounds/play/m0007jzb ' ,
' note ' : ' Audio ' ,
' info_dict ' : {
' id ' : ' m0007jz9 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' BBC Proms, 2019, Prom 34: West– Eastern Divan Orchestra ' ,
' description ' : " Live BBC Proms. West– Eastern Divan Orchestra with Daniel Barenboim and Martha Argerich. " ,
' duration ' : 9840 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
}
2015-01-02 15:37:54 +01:00
} , {
' url ' : ' http://www.bbc.co.uk/iplayer/playlist/p01dvks4 ' ,
' only_matching ' : True ,
2015-01-03 15:43:40 +01:00
} , {
' url ' : ' http://www.bbc.co.uk/music/clips#p02frcc3 ' ,
' only_matching ' : True ,
2015-01-30 18:47:09 +01:00
} , {
' url ' : ' http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo ' ,
' only_matching ' : True ,
2016-01-02 14:22:39 +01:00
} , {
' url ' : ' http://www.bbc.co.uk/radio/player/p03cchwf ' ,
' only_matching ' : True ,
2017-06-29 17:29:28 +02:00
} , {
' url ' : ' https://www.bbc.co.uk/music/audiovideo/popular#p055bc55 ' ,
' only_matching ' : True ,
2017-08-30 00:27:56 +02:00
} , {
' url ' : ' http://www.bbc.co.uk/programmes/w3csv1y9 ' ,
' only_matching ' : True ,
2018-08-19 21:05:07 +02:00
} , {
' url ' : ' https://www.bbc.co.uk/programmes/m00005xn ' ,
' only_matching ' : True ,
} , {
' url ' : ' https://www.bbc.co.uk/programmes/w172w4dww1jqt5s ' ,
' only_matching ' : True ,
2017-06-29 17:29:28 +02:00
} ]
2014-02-08 22:00:24 +01:00
2017-02-10 19:45:48 +01:00
_USP_RE = r ' /([^/]+?) \ .ism(?: \ .hlsv2 \ .ism)?/[^/]+ \ .m3u8 '
2017-05-26 17:12:24 +02:00
def _login ( self ) :
username , password = self . _get_login_info ( )
if username is None :
return
login_page = self . _download_webpage (
self . _LOGIN_URL , None , ' Downloading signin page ' )
login_form = self . _hidden_inputs ( login_page )
login_form . update ( {
' username ' : username ,
' password ' : password ,
} )
post_url = urljoin ( self . _LOGIN_URL , self . _search_regex (
r ' <form[^>]+action=([ " \' ])(?P<url>.+?) \ 1 ' , login_page ,
' post url ' , default = self . _LOGIN_URL , group = ' url ' ) )
response , urlh = self . _download_webpage_handle (
post_url , None , ' Logging in ' , data = urlencode_postdata ( login_form ) ,
headers = { ' Referer ' : self . _LOGIN_URL } )
if self . _LOGIN_URL in urlh . geturl ( ) :
error = clean_html ( get_element_by_class ( ' form-message ' , response ) )
if error :
raise ExtractorError (
' Unable to login: %s ' % error , expected = True )
raise ExtractorError ( ' Unable to log in ' )
def _real_initialize ( self ) :
self . _login ( )
2015-07-29 20:55:06 +02:00
class MediaSelectionError ( Exception ) :
def __init__ ( self , id ) :
self . id = id
2014-02-08 22:00:24 +01:00
def _extract_asx_playlist ( self , connection , programme_id ) :
asx = self . _download_xml ( connection . get ( ' href ' ) , programme_id , ' Downloading ASX playlist ' )
return [ ref . get ( ' href ' ) for ref in asx . findall ( ' ./Entry/ref ' ) ]
def _extract_items ( self , playlist ) :
2015-10-10 19:01:20 +02:00
return playlist . findall ( ' ./ { %s }item ' % self . _EMP_PLAYLIST_NS )
def _findall_ns ( self , element , xpath ) :
elements = [ ]
for ns in self . _NAMESPACES :
elements . extend ( element . findall ( xpath % ns ) )
return elements
2014-02-08 22:00:24 +01:00
def _extract_medias ( self , media_selection ) :
2015-10-10 19:01:20 +02:00
error = media_selection . find ( ' ./ { %s }error ' % self . _MEDIASELECTION_NS )
if error is None :
media_selection . find ( ' ./ { %s }error ' % self . _EMP_PLAYLIST_NS )
2014-11-30 17:37:56 +01:00
if error is not None :
2015-07-29 20:55:06 +02:00
raise BBCCoUkIE . MediaSelectionError ( error . get ( ' id ' ) )
2015-10-10 19:01:20 +02:00
return self . _findall_ns ( media_selection , ' ./ { %s }media ' )
2014-02-08 22:00:24 +01:00
def _extract_connections ( self , media ) :
2015-10-10 19:01:20 +02:00
return self . _findall_ns ( media , ' ./ { %s }connection ' )
2014-02-08 22:00:24 +01:00
2015-02-19 16:46:41 +01:00
def _get_subtitles ( self , media , programme_id ) :
2014-02-08 22:00:24 +01:00
subtitles = { }
for connection in self . _extract_connections ( media ) :
2019-02-24 15:01:25 +01:00
cc_url = url_or_none ( connection . get ( ' href ' ) )
if not cc_url :
continue
captions = self . _download_xml (
cc_url , programme_id , ' Downloading captions ' , fatal = False )
2019-03-05 19:21:57 +01:00
if not isinstance ( captions , compat_etree_Element ) :
2019-02-24 15:01:25 +01:00
continue
2014-02-08 22:00:24 +01:00
lang = captions . get ( ' { http://www.w3.org/XML/1998/namespace}lang ' , ' en ' )
2015-02-19 16:46:41 +01:00
subtitles [ lang ] = [
{
' url ' : connection . get ( ' href ' ) ,
' ext ' : ' ttml ' ,
} ,
]
2014-02-08 22:00:24 +01:00
return subtitles
2014-02-08 15:55:28 +01:00
2015-07-29 20:55:06 +02:00
def _raise_extractor_error ( self , media_selection_error ) :
raise ExtractorError (
' %s returned error: %s ' % ( self . IE_NAME , media_selection_error . id ) ,
expected = True )
2014-11-30 17:37:56 +01:00
def _download_media_selector ( self , programme_id ) :
2015-07-29 20:55:06 +02:00
last_exception = None
for mediaselector_url in self . _MEDIASELECTOR_URLS :
try :
return self . _download_media_selector_url (
mediaselector_url % programme_id , programme_id )
except BBCCoUkIE . MediaSelectionError as e :
2015-11-14 18:08:13 +01:00
if e . id in ( ' notukerror ' , ' geolocation ' , ' selectionunavailable ' ) :
2015-07-29 20:55:06 +02:00
last_exception = e
continue
self . _raise_extractor_error ( e )
self . _raise_extractor_error ( last_exception )
2015-07-25 16:21:42 +02:00
def _download_media_selector_url ( self , url , programme_id = None ) :
2018-06-17 23:04:47 +02:00
media_selection = self . _download_xml (
url , programme_id , ' Downloading media selection XML ' ,
expected_status = ( 403 , 404 ) )
2015-07-25 16:21:42 +02:00
return self . _process_media_selector ( media_selection , programme_id )
2014-02-08 15:55:28 +01:00
2015-07-25 16:21:42 +02:00
def _process_media_selector ( self , media_selection , programme_id ) :
2014-02-08 15:55:28 +01:00
formats = [ ]
2014-02-08 22:00:24 +01:00
subtitles = None
2016-08-06 20:24:59 +02:00
urls = [ ]
2014-02-08 22:00:24 +01:00
2014-11-30 17:37:56 +01:00
for media in self . _extract_medias ( media_selection ) :
kind = media . get ( ' kind ' )
2016-08-06 19:48:09 +02:00
if kind in ( ' video ' , ' audio ' ) :
bitrate = int_or_none ( media . get ( ' bitrate ' ) )
encoding = media . get ( ' encoding ' )
service = media . get ( ' service ' )
width = int_or_none ( media . get ( ' width ' ) )
height = int_or_none ( media . get ( ' height ' ) )
file_size = int_or_none ( media . get ( ' media_file_size ' ) )
for connection in self . _extract_connections ( media ) :
2016-08-06 20:24:59 +02:00
href = connection . get ( ' href ' )
if href in urls :
continue
if href :
urls . append ( href )
2016-08-06 19:48:09 +02:00
conn_kind = connection . get ( ' kind ' )
protocol = connection . get ( ' protocol ' )
supplier = connection . get ( ' supplier ' )
transfer_format = connection . get ( ' transferFormat ' )
format_id = supplier or conn_kind or protocol
if service :
format_id = ' %s _ %s ' % ( service , format_id )
# ASX playlist
if supplier == ' asx ' :
for i , ref in enumerate ( self . _extract_asx_playlist ( connection , programme_id ) ) :
formats . append ( {
' url ' : ref ,
' format_id ' : ' ref %s _ %s ' % ( i , format_id ) ,
} )
elif transfer_format == ' dash ' :
formats . extend ( self . _extract_mpd_formats (
href , programme_id , mpd_id = format_id , fatal = False ) )
elif transfer_format == ' hls ' :
formats . extend ( self . _extract_m3u8_formats (
href , programme_id , ext = ' mp4 ' , entry_protocol = ' m3u8_native ' ,
m3u8_id = format_id , fatal = False ) )
2017-02-10 19:45:48 +01:00
if re . search ( self . _USP_RE , href ) :
usp_formats = self . _extract_m3u8_formats (
2017-12-12 18:04:05 +01:00
re . sub ( self . _USP_RE , r ' / \ 1.ism/ \ 1.m3u8 ' , href ) ,
2017-02-10 19:45:48 +01:00
programme_id , ext = ' mp4 ' , entry_protocol = ' m3u8_native ' ,
m3u8_id = format_id , fatal = False )
for f in usp_formats :
if f . get ( ' height ' ) and f [ ' height ' ] > 720 :
continue
formats . append ( f )
2016-08-06 19:48:09 +02:00
elif transfer_format == ' hds ' :
formats . extend ( self . _extract_f4m_formats (
href , programme_id , f4m_id = format_id , fatal = False ) )
else :
2016-08-07 12:14:15 +02:00
if not service and not supplier and bitrate :
2016-08-07 13:05:13 +02:00
format_id + = ' - %d ' % bitrate
2016-08-06 19:48:09 +02:00
fmt = {
' format_id ' : format_id ,
' filesize ' : file_size ,
}
if kind == ' video ' :
fmt . update ( {
' width ' : width ,
' height ' : height ,
2017-04-10 17:56:22 +02:00
' tbr ' : bitrate ,
2016-08-06 19:48:09 +02:00
' vcodec ' : encoding ,
} )
else :
fmt . update ( {
' abr ' : bitrate ,
' acodec ' : encoding ,
' vcodec ' : ' none ' ,
} )
2017-04-10 17:53:06 +02:00
if protocol in ( ' http ' , ' https ' ) :
2016-08-06 19:48:09 +02:00
# Direct link
fmt . update ( {
' url ' : href ,
} )
elif protocol == ' rtmp ' :
application = connection . get ( ' application ' , ' ondemand ' )
auth_string = connection . get ( ' authString ' )
identifier = connection . get ( ' identifier ' )
server = connection . get ( ' server ' )
fmt . update ( {
' url ' : ' %s :// %s / %s ? %s ' % ( protocol , server , application , auth_string ) ,
' play_path ' : identifier ,
' app ' : ' %s ? %s ' % ( application , auth_string ) ,
' page_url ' : ' http://www.bbc.co.uk ' ,
' player_url ' : ' http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf ' ,
' rtmp_live ' : False ,
' ext ' : ' flv ' ,
} )
2017-04-10 17:53:51 +02:00
else :
continue
2016-08-06 19:48:09 +02:00
formats . append ( fmt )
2014-11-30 17:37:56 +01:00
elif kind == ' captions ' :
2015-02-19 16:46:41 +01:00
subtitles = self . extract_subtitles ( media , programme_id )
2014-11-30 17:37:56 +01:00
return formats , subtitles
2014-02-08 22:00:24 +01:00
2014-12-28 22:00:24 +01:00
def _download_playlist ( self , playlist_id ) :
try :
playlist = self . _download_json (
' http://www.bbc.co.uk/programmes/ %s /playlist.json ' % playlist_id ,
playlist_id , ' Downloading playlist JSON ' )
version = playlist . get ( ' defaultAvailableVersion ' )
if version :
smp_config = version [ ' smpConfig ' ]
title = smp_config [ ' title ' ]
description = smp_config [ ' summary ' ]
for item in smp_config [ ' items ' ] :
kind = item [ ' kind ' ]
2017-04-12 21:38:43 +02:00
if kind not in ( ' programme ' , ' radioProgramme ' ) :
2014-12-28 22:00:24 +01:00
continue
programme_id = item . get ( ' vpid ' )
2015-07-23 22:56:54 +02:00
duration = int_or_none ( item . get ( ' duration ' ) )
2014-12-28 22:00:24 +01:00
formats , subtitles = self . _download_media_selector ( programme_id )
return programme_id , title , description , duration , formats , subtitles
except ExtractorError as ee :
2015-02-15 11:32:38 +01:00
if not ( isinstance ( ee . cause , compat_HTTPError ) and ee . cause . code == 404 ) :
2014-12-28 22:00:24 +01:00
raise
# fallback to legacy playlist
2015-07-25 16:21:42 +02:00
return self . _process_legacy_playlist ( playlist_id )
def _process_legacy_playlist_url ( self , url , display_id ) :
playlist = self . _download_legacy_playlist_url ( url , display_id )
return self . _extract_from_legacy_playlist ( playlist , display_id )
def _process_legacy_playlist ( self , playlist_id ) :
return self . _process_legacy_playlist_url (
' http://www.bbc.co.uk/iplayer/playlist/ %s ' % playlist_id , playlist_id )
def _download_legacy_playlist_url ( self , url , playlist_id = None ) :
return self . _download_xml (
url , playlist_id , ' Downloading legacy playlist XML ' )
2014-12-28 22:00:24 +01:00
2015-07-25 16:21:42 +02:00
def _extract_from_legacy_playlist ( self , playlist , playlist_id ) :
2015-10-10 19:01:20 +02:00
no_items = playlist . find ( ' ./ { %s }noItems ' % self . _EMP_PLAYLIST_NS )
2014-12-28 22:00:24 +01:00
if no_items is not None :
reason = no_items . get ( ' reason ' )
if reason == ' preAvailability ' :
msg = ' Episode %s is not yet available ' % playlist_id
elif reason == ' postAvailability ' :
msg = ' Episode %s is no longer available ' % playlist_id
elif reason == ' noMedia ' :
msg = ' Episode %s is not currently available ' % playlist_id
else :
msg = ' Episode %s is not available: %s ' % ( playlist_id , reason )
raise ExtractorError ( msg , expected = True )
for item in self . _extract_items ( playlist ) :
kind = item . get ( ' kind ' )
2017-04-12 21:38:43 +02:00
if kind not in ( ' programme ' , ' radioProgramme ' ) :
2014-12-28 22:00:24 +01:00
continue
2015-10-10 19:01:20 +02:00
title = playlist . find ( ' ./ { %s }title ' % self . _EMP_PLAYLIST_NS ) . text
description_el = playlist . find ( ' ./ { %s }summary ' % self . _EMP_PLAYLIST_NS )
2015-10-16 20:26:45 +02:00
description = description_el . text if description_el is not None else None
2015-07-25 16:21:42 +02:00
def get_programme_id ( item ) :
def get_from_attributes ( item ) :
pull changes from remote master (#190)
* [scrippsnetworks] Add new extractor(closes #19857)(closes #22981)
* [teachable] Improve locked lessons detection (#23528)
* [teachable] Fail with error message if no video URL found
* [extractors] add missing import for ScrippsNetworksIE
* [brightcove] cache brightcove player policy keys
* [prosiebensat1] improve geo restriction handling(closes #23571)
* [soundcloud] automatically update client id on failing requests
* [spankbang] Fix extraction (closes #23307, closes #23423, closes #23444)
* [spankbang] Improve removed video detection (#23423)
* [brightcove] update policy key on failing requests
* [pornhub] Fix extraction and add support for m3u8 formats (closes #22749, closes #23082)
* [pornhub] Improve locked videos detection (closes #22449, closes #22780)
* [brightcove] invalidate policy key cache on failing requests
* [soundcloud] fix client id extraction for non fatal requests
* [ChangeLog] Actualize
[ci skip]
* [devscripts/create-github-release] Switch to using PAT for authentication
Basic authentication will be deprecated soon
* release 2020.01.01
* [redtube] Detect private videos (#23518)
* [vice] improve extraction(closes #23631)
* [devscripts/create-github-release] Remove unused import
* [wistia] improve format extraction and extract subtitles(closes #22590)
* [nrktv:seriebase] Fix extraction (closes #23625) (#23537)
* [discovery] fix anonymous token extraction(closes #23650)
* [scrippsnetworks] add support for www.discovery.com videos
* [scrippsnetworks] correct test case URL
* [dctp] fix format extraction(closes #23656)
* [pandatv] Remove extractor (#23630)
* [naver] improve extraction
- improve geo-restriction handling
- extract automatic captions
- extract uploader metadata
- extract VLive HLS formats
* [naver] improve metadata extraction
* [cloudflarestream] improve extraction
- add support for bytehighway.net domain
- add support for signed URLs
- extract thumbnail
* [cloudflarestream] import embed URL extraction
* [lego] fix extraction and extract subtitle(closes #23687)
* [safari] Fix kaltura session extraction (closes #23679) (#23670)
* [orf:fm4] Fix extraction (#23599)
* [orf:radio] Clean description and improve extraction
* [twitter] add support for promo_video_website cards(closes #23711)
* [vodplatform] add support for embed.kwikmotion.com domain
* [ndr:base:embed] Improve thumbnails extraction (closes #23731)
* [canvas] Add support for new API endpoint and update tests (closes #17680, closes #18629)
* [travis] Add flake8 job (#23720)
* [yourporn] Fix extraction (closes #21645, closes #22255, closes #23459)
* [ChangeLog] Actualize
[ci skip]
* release 2020.01.15
* [soundcloud] Restore previews extraction (closes #23739)
* [orf:tvthek] Improve geo restricted videos detection (closes #23741)
* [zype] improve extraction
- extract subtitles(closes #21258)
- support URLs with alternative keys/tokens(#21258)
- extract more metadata
* [americastestkitchen] fix extraction
* [nbc] add support for nbc multi network URLs(closes #23049)
* [ard] improve extraction(closes #23761)
- simplify extraction
- extract age limit and series
- bypass geo-restriction
* [ivi:compilation] Fix entries extraction (closes #23770)
* [24video] Add support for 24video.vip (closes #23753)
* [businessinsider] Fix jwplatform id extraction (closes #22929) (#22954)
* [ard] add a missing condition
* [azmedien] fix extraction(closes #23783)
* [voicerepublic] fix extraction
* [stretchinternet] fix extraction(closes #4319)
* [youtube] Fix sigfunc name extraction (closes #23819)
* [ChangeLog] Actualize
[ci skip]
* release 2020.01.24
* [soundcloud] imporve private playlist/set tracks extraction
https://github.com/ytdl-org/youtube-dl/issues/3707#issuecomment-577873539
* [svt] fix article extraction(closes #22897)(closes #22919)
* [svt] fix series extraction(closes #22297)
* [viewlift] improve extraction
- fix extraction(closes #23851)
- add add support for authentication
- add support for more domains
* [vimeo] fix album extraction(closes #23864)
* [tva] Relax _VALID_URL (closes #23903)
* [tv5mondeplus] Fix extraction (closes #23907, closes #23911)
* [twitch:stream] Lowercase channel id for stream request (closes #23917)
* [sportdeutschland] Update to new sportdeutschland API
They switched to SSL, but under a different host AND path...
Remove the old test cases because these videos have become unavailable.
* [popcorntimes] Add extractor (closes #23949)
* [thisoldhouse] fix extraction(closes #23951)
* [toggle] Add support for mewatch.sg (closes #23895) (#23930)
* [compat] Introduce compat_realpath (refs #23991)
* [update] Fix updating via symlinks (closes #23991)
* [nytimes] improve format sorting(closes #24010)
* [abc:iview] Support 720p (#22907) (#22921)
* [nova:embed] Fix extraction (closes #23672)
* [nova:embed] Improve (closes #23690)
* [nova] Improve extraction (refs #23690)
* [jpopsuki] Remove extractor (closes #23858)
* [YoutubeDL] Fix playlist entry indexing with --playlist-items (closes #10591, closes #10622)
* [test_YoutubeDL] Fix get_ids
* [test_YoutubeDL] Add tests for #10591 (closes #23873)
* [24video] Add support for porn.24video.net (closes #23779, closes #23784)
* [npr] Add support for streams (closes #24042)
* [ChangeLog] Actualize
[ci skip]
* release 2020.02.16
* [tv2dk:bornholm:play] Fix extraction (#24076)
* [imdb] Fix extraction (closes #23443)
* [wistia] Add support for multiple generic embeds (closes #8347, closes #11385)
* [teachable] Add support for multiple videos per lecture (closes #24101)
* [pornhd] Fix extraction (closes #24128)
* [options] Remove duplicate short option -v for --version (#24162)
* [extractor/common] Convert ISM manifest to unicode before processing on python 2 (#24152)
* [YoutubeDL] Force redirect URL to unicode on python 2
* Remove no longer needed compat_str around geturl
* [youjizz] Fix extraction (closes #24181)
* [test_subtitles] Remove obsolete test
* [zdf:channel] Fix tests
* [zapiks] Fix test
* [xtube] Fix metadata extraction (closes #21073, closes #22455)
* [xtube:user] Fix test
* [telecinco] Fix extraction (refs #24195)
* [telecinco] Add support for article opening videos
* [franceculture] Fix extraction (closes #24204)
* [xhamster] Fix extraction (closes #24205)
* [ChangeLog] Actualize
[ci skip]
* release 2020.03.01
* [vimeo] Fix subtitles URLs (#24209)
* [servus] Add support for new URL schema (closes #23475, closes #23583, closes #24142)
* [youtube:playlist] Fix tests (closes #23872) (#23885)
* [peertube] Improve extraction
* [peertube] Fix issues and improve extraction (closes #23657)
* [pornhub] Improve title extraction (closes #24184)
* [vimeo] fix showcase password protected video extraction(closes #24224)
* [youtube] Fix age-gated videos support without login (closes #24248)
* [youtube] Fix tests
* [ChangeLog] Actualize
[ci skip]
* release 2020.03.06
* [nhk] update API version(closes #24270)
* [youtube] Improve extraction in 429 error conditions (closes #24283)
* [youtube] Improve age-gated videos extraction in 429 error conditions (refs #24283)
* [youtube] Remove outdated code
Additional get_video_info requests don't seem to provide any extra itags any longer
* [README.md] Clarify 429 error
* [pornhub] Add support for pornhubpremium.com (#24288)
* [utils] Add support for cookies with spaces used instead of tabs
* [ChangeLog] Actualize
[ci skip]
* release 2020.03.08
* Revert "[utils] Add support for cookies with spaces used instead of tabs"
According to [1] TABs must be used as separators between fields.
Files produces by some tools with spaces as separators are considered
malformed.
1. https://curl.haxx.se/docs/http-cookies.html
This reverts commit cff99c91d150df2a4e21962a3ca8d4ae94533b8c.
* [utils] Add reference to cookie file format
* Revert "[vimeo] fix showcase password protected video extraction(closes #24224)"
This reverts commit 12ee431676bb655f04c7dd416a73c1f142ed368d.
* [nhk] Relax _VALID_URL (#24329)
* [nhk] Remove obsolete rtmp formats (closes #24329)
* [nhk] Update m3u8 URL and use native hls (#24329)
* [ndr] Fix extraction (closes #24326)
* [xtube] Fix formats extraction (closes #24348)
* [xtube] Fix typo
* [hellporno] Fix extraction (closes #24399)
* [cbc:watch] Add support for authentication
* [cbc:watch] Fix authenticated device token caching (closes #19160)
* [soundcloud] fix download url extraction(closes #24394)
* [limelight] remove disabled API requests(closes #24255)
* [bilibili] Add support for new URL schema with BV ids (closes #24439, closes #24442)
* [bilibili] Add support for player.bilibili.com (closes #24402)
* [teachable] Extract chapter metadata (closes #24421)
* [generic] Look for teachable embeds before wistia
* [teachable] Update upskillcourses domain
New version does not use teachable platform any longer
* [teachable] Update gns3 domain
* [teachable] Update test
* [ChangeLog] Actualize
[ci skip]
* [ChangeLog] Actualize
[ci skip]
* release 2020.03.24
* [spankwire] Fix extraction (closes #18924, closes #20648)
* [spankwire] Add support for generic embeds (refs #24633)
* [youporn] Add support form generic embeds
* [mofosex] Add support for generic embeds (closes #24633)
* [tele5] Fix extraction (closes #24553)
* [extractor/common] Skip malformed ISM manifest XMLs while extracting ISM formats (#24667)
* [tv4] Fix ISM formats extraction (closes #24667)
* [twitch:clips] Extend _VALID_URL (closes #24290) (#24642)
* [motherless] Fix extraction (closes #24699)
* [nova:embed] Fix extraction (closes #24700)
* [youtube] Skip broken multifeed videos (closes #24711)
* [soundcloud] Extract AAC format
* [soundcloud] Improve AAC format extraction (closes #19173, closes #24708)
* [thisoldhouse] Fix video id extraction (closes #24548)
Added support for:
with of without "www."
and either ".chorus.build" or ".com"
It now validated correctly on older URL's
```
<iframe src="https://thisoldhouse.chorus.build/videos/zype/5e33baec27d2e50001d5f52f
```
and newer ones
```
<iframe src="https://www.thisoldhouse.com/videos/zype/5e2b70e95216cc0001615120
```
* [thisoldhouse] Improve video id extraction (closes #24549)
* [youtube] Fix DRM videos detection (refs #24736)
* [options] Clarify doc on --exec command (closes #19087) (#24883)
* [prosiebensat1] Improve extraction and remove 7tv.de support (#24948)
* [prosiebensat1] Extract series metadata
* [tenplay] Relax _VALID_URL (closes #25001)
* [tvplay] fix Viafree extraction(closes #15189)(closes #24473)(closes #24789)
* [yahoo] fix GYAO Player extraction and relax title URL regex(closes #24178)(closes #24778)
* [youtube] Use redirected video id if any (closes #25063)
* [youtube] Improve player id extraction and add tests
* [extractor/common] Extract multiple JSON-LD entries
* [crunchyroll] Fix and improve extraction (closes #25096, closes #25060)
* [ChangeLog] Actualize
[ci skip]
* release 2020.05.03
* [puhutv] Remove no longer available HTTP formats (closes #25124)
* [utils] Improve cookie files support
+ Add support for UTF-8 in cookie files
* Skip malformed cookie file entries instead of crashing (invalid entry len, invalid expires at)
* [dailymotion] Fix typo
* [compat] Introduce compat_cookiejar_Cookie
* [extractor/common] Use compat_cookiejar_Cookie for _set_cookie (closes #23256, closes #24776)
To always ensure cookie name and value are bytestrings on python 2.
* [orf] Add support for more radio stations (closes #24938) (#24968)
* [uol] fix extraction(closes #22007)
* [downloader/http] Finish downloading once received data length matches expected
Always do this if possible, i.e. if Content-Length or expected length is known, not only in test.
This will save unnecessary last extra loop trying to read 0 bytes.
* [downloader/http] Request last data block of exact remaining size
Always request last data block of exact size remaining to download if possible not the current block size.
* [iprima] Improve extraction (closes #25138)
* [youtube] Improve signature cipher extraction (closes #25188)
* [ChangeLog] Actualize
[ci skip]
* release 2020.05.08
* [spike] fix Bellator mgid extraction(closes #25195)
* [bbccouk] PEP8
* [mailru] Fix extraction (closes #24530) (#25239)
* [README.md] flake8 HTTPS URL (#25230)
* [youtube] Add support for yewtu.be (#25226)
* [soundcloud] reduce API playlist page limit(closes #25274)
* [vimeo] improve format extraction and sorting(closes #25285)
* [redtube] Improve title extraction (#25208)
* [indavideo] Switch to HTTPS for API request (#25191)
* [utils] Fix file permissions in write_json_file (closes #12471) (#25122)
* [redtube] Improve formats extraction and extract m3u8 formats (closes #25311, closes #25321)
* [ard] Improve _VALID_URL (closes #25134) (#25198)
* [giantbomb] Extend _VALID_URL (#25222)
* [postprocessor/ffmpeg] Embed series metadata with --add-metadata
* [youtube] Add support for more invidious instances (#25417)
* [ard:beta] Extend _VALID_URL (closes #25405)
* [ChangeLog] Actualize
[ci skip]
* release 2020.05.29
* [jwplatform] Improve embeds extraction (closes #25467)
* [periscope] Fix untitled broadcasts (#25482)
* [twitter:broadcast] Add untitled periscope broadcast test
* [malltv] Add support for sk.mall.tv (#25445)
* [brightcove] Fix subtitles extraction (closes #25540)
* [brightcove] Sort imports
* [twitch] Pass v5 accept header and fix thumbnails extraction (closes #25531)
* [twitch:stream] Fix extraction (closes #25528)
* [twitch:stream] Expect 400 and 410 HTTP errors from API
* [tele5] Prefer jwplatform over nexx (closes #25533)
* [jwplatform] Add support for bypass geo restriction
* [tele5] Bypass geo restriction
* [ChangeLog] Actualize
[ci skip]
* release 2020.06.06
* [kaltura] Add support for multiple embeds on a webpage (closes #25523)
* [youtube] Extract chapters from JSON (closes #24819)
* [facebook] Support single-video ID links
I stumbled upon this at https://www.facebook.com/bwfbadminton/posts/10157127020046316 . No idea how prevalent it is yet.
* [youtube] Fix playlist and feed extraction (closes #25675)
* [youtube] Fix thumbnails extraction and remove uploader id extraction warning (closes #25676)
* [youtube] Fix upload date extraction
* [youtube] Improve view count extraction
* [youtube] Fix uploader id and uploader URL extraction
* [ChangeLog] Actualize
[ci skip]
* release 2020.06.16
* [youtube] Fix categories and improve tags extraction
* [youtube] Force old layout (closes #25682, closes #25683, closes #25680, closes #25686)
* [ChangeLog] Actualize
[ci skip]
* release 2020.06.16.1
* [brightcove] Improve embed detection (closes #25674)
* [bellmedia] add support for cp24.com clip URLs(closes #25764)
* [youtube:playlists] Extend _VALID_URL (closes #25810)
* [youtube] Prevent excess HTTP 301 (#25786)
* [wistia] Restrict embed regex (closes #25969)
* [youtube] Improve description extraction (closes #25937) (#25980)
* [youtube] Fix sigfunc name extraction (closes #26134, closes #26135, closes #26136, closes #26137)
* [ChangeLog] Actualize
[ci skip]
* release 2020.07.28
* [xhamster] Extend _VALID_URL (closes #25789) (#25804)
* [xhamster] Fix extraction (closes #26157) (#26254)
* [xhamster] Extend _VALID_URL (closes #25927)
Co-authored-by: Remita Amine <remitamine@gmail.com>
Co-authored-by: Sergey M․ <dstftw@gmail.com>
Co-authored-by: nmeum <soeren+github@soeren-tempel.net>
Co-authored-by: Roxedus <me@roxedus.dev>
Co-authored-by: Singwai Chan <c.singwai@gmail.com>
Co-authored-by: cdarlint <cdarlint@users.noreply.github.com>
Co-authored-by: Johannes N <31795504+jonolt@users.noreply.github.com>
Co-authored-by: jnozsc <jnozsc@gmail.com>
Co-authored-by: Moritz Patelscheck <moritz.patelscheck@campus.tu-berlin.de>
Co-authored-by: PB <3854688+uno20001@users.noreply.github.com>
Co-authored-by: Philipp Hagemeister <phihag@phihag.de>
Co-authored-by: Xaver Hellauer <software@hellauer.bayern>
Co-authored-by: d2au <d2au.dev@gmail.com>
Co-authored-by: Jan 'Yenda' Trmal <jtrmal@gmail.com>
Co-authored-by: jxu <7989982+jxu@users.noreply.github.com>
Co-authored-by: Martin Ström <name@my-domain.se>
Co-authored-by: The Hatsune Daishi <nao20010128@gmail.com>
Co-authored-by: tsia <github@tsia.de>
Co-authored-by: 3risian <59593325+3risian@users.noreply.github.com>
Co-authored-by: Tristan Waddington <tristan.waddington@gmail.com>
Co-authored-by: Devon Meunier <devon.meunier@gmail.com>
Co-authored-by: Felix Stupp <felix.stupp@outlook.com>
Co-authored-by: tom <tomster954@gmail.com>
Co-authored-by: AndrewMBL <62922222+AndrewMBL@users.noreply.github.com>
Co-authored-by: willbeaufoy <will@willbeaufoy.net>
Co-authored-by: Philipp Stehle <anderschwiedu@googlemail.com>
Co-authored-by: hh0rva1h <61889859+hh0rva1h@users.noreply.github.com>
Co-authored-by: comsomisha <shmelev1996@mail.ru>
Co-authored-by: TotalCaesar659 <14265316+TotalCaesar659@users.noreply.github.com>
Co-authored-by: Juan Francisco Cantero Hurtado <iam@juanfra.info>
Co-authored-by: Dave Loyall <dave@the-good-guys.net>
Co-authored-by: tlsssl <63866177+tlsssl@users.noreply.github.com>
Co-authored-by: Rob <ankenyr@gmail.com>
Co-authored-by: Michael Klein <github@a98shuttle.de>
Co-authored-by: JordanWeatherby <47519158+JordanWeatherby@users.noreply.github.com>
Co-authored-by: striker.sh <19488257+strikersh@users.noreply.github.com>
Co-authored-by: Matej Dujava <mdujava@gmail.com>
Co-authored-by: Glenn Slayden <5589855+glenn-slayden@users.noreply.github.com>
Co-authored-by: MRWITEK <mrvvitek@gmail.com>
Co-authored-by: JChris246 <43832407+JChris246@users.noreply.github.com>
Co-authored-by: TheRealDude2 <the.real.dude@gmx.de>
2020-08-25 16:53:34 +02:00
for p in ( ' identifier ' , ' group ' ) :
2015-07-25 16:21:42 +02:00
value = item . get ( p )
if value and re . match ( r ' ^[pb][ \ da-z] {7} $ ' , value ) :
return value
get_from_attributes ( item )
2015-10-10 19:01:20 +02:00
mediator = item . find ( ' ./ { %s }mediator ' % self . _EMP_PLAYLIST_NS )
2015-07-25 16:21:42 +02:00
if mediator is not None :
return get_from_attributes ( mediator )
programme_id = get_programme_id ( item )
2015-07-23 22:56:54 +02:00
duration = int_or_none ( item . get ( ' duration ' ) )
2015-10-10 19:01:20 +02:00
if programme_id :
formats , subtitles = self . _download_media_selector ( programme_id )
else :
formats , subtitles = self . _process_media_selector ( item , playlist_id )
programme_id = playlist_id
2014-12-28 22:00:24 +01:00
return programme_id , title , description , duration , formats , subtitles
2014-11-30 17:37:56 +01:00
def _real_extract ( self , url ) :
group_id = self . _match_id ( url )
webpage = self . _download_webpage ( url , group_id , ' Downloading video page ' )
2017-06-29 17:27:53 +02:00
error = self . _search_regex (
r ' <div \ b[^>]+ \ bclass=[ " \' ]smp__message delta[ " \' ][^>]*>([^<]+)< ' ,
webpage , ' error ' , default = None )
if error :
raise ExtractorError ( error , expected = True )
2015-04-30 23:59:13 +02:00
programme_id = None
2015-12-05 09:45:24 +01:00
duration = None
2015-04-30 23:59:13 +02:00
tviplayer = self . _search_regex (
r ' mediator \ .bind \ (( { .+?}) \ s*, \ s*document \ .getElementById ' ,
webpage , ' player ' , default = None )
if tviplayer :
player = self . _parse_json ( tviplayer , group_id ) . get ( ' player ' , { } )
duration = int_or_none ( player . get ( ' duration ' ) )
programme_id = player . get ( ' vpid ' )
if not programme_id :
programme_id = self . _search_regex (
2015-12-01 21:34:31 +01:00
r ' " vpid " \ s*: \ s* " ( %s ) " ' % self . _ID_REGEX , webpage , ' vpid ' , fatal = False , default = None )
2015-04-30 23:59:13 +02:00
2014-11-30 17:37:56 +01:00
if programme_id :
formats , subtitles = self . _download_media_selector ( programme_id )
2016-01-02 14:42:11 +01:00
title = self . _og_search_title ( webpage , default = None ) or self . _html_search_regex (
2016-01-28 18:19:53 +01:00
( r ' <h2[^>]+id= " parent-title " [^>]*>(.+?)</h2> ' ,
r ' <div[^>]+class= " info " [^>]*> \ s*<h1>(.+?)</h1> ' ) , webpage , ' title ' )
2015-04-30 23:59:13 +02:00
description = self . _search_regex (
2016-01-28 18:23:13 +01:00
( r ' <p class= " [^ " ]*medium-description[^ " ]* " >([^<]+)</p> ' ,
r ' <div[^>]+class= " info_+synopsis " [^>]*>([^<]+)</div> ' ) ,
2015-12-05 09:45:24 +01:00
webpage , ' description ' , default = None )
if not description :
description = self . _html_search_meta ( ' description ' , webpage )
2014-11-30 17:37:56 +01:00
else :
2014-12-28 22:00:24 +01:00
programme_id , title , description , duration , formats , subtitles = self . _download_playlist ( group_id )
2014-02-08 22:00:24 +01:00
2014-02-08 15:55:28 +01:00
self . _sort_formats ( formats )
return {
2014-02-08 22:00:24 +01:00
' id ' : programme_id ,
2014-02-08 15:55:28 +01:00
' title ' : title ,
' description ' : description ,
2015-05-01 00:07:30 +02:00
' thumbnail ' : self . _og_search_thumbnail ( webpage , default = None ) ,
2014-02-08 15:55:28 +01:00
' duration ' : duration ,
' formats ' : formats ,
2014-02-08 22:00:24 +01:00
' subtitles ' : subtitles ,
2014-11-23 20:41:03 +01:00
}
2015-06-20 15:22:13 +02:00
2015-07-25 16:21:42 +02:00
class BBCIE ( BBCCoUkIE ) :
IE_NAME = ' bbc '
IE_DESC = ' BBC '
_VALID_URL = r ' https?://(?:www \ .)?bbc \ .(?:com|co \ .uk)/(?:[^/]+/)+(?P<id>[^/#?]+) '
2015-06-20 15:22:13 +02:00
2015-07-29 20:55:06 +02:00
_MEDIASELECTOR_URLS = [
2015-10-10 20:37:39 +02:00
# Provides HQ HLS streams but fails with geolocation in some cases when it's
# even not geo restricted at all
' http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/ %s ' ,
2015-07-29 20:55:06 +02:00
# Provides more formats, namely direct mp4 links, but fails on some videos with
# notukerror for non UK (?) users (e.g.
# http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
' http://open.live.bbc.co.uk/mediaselector/4/mtis/stream/ %s ' ,
# Provides fewer formats, but works everywhere for everybody (hopefully)
' http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/journalism-pc/vpid/ %s ' ,
]
2015-06-20 15:22:13 +02:00
_TESTS = [ {
2015-10-10 19:56:55 +02:00
# article with multiple videos embedded with data-playable containing vpids
2015-06-20 15:22:13 +02:00
' url ' : ' http://www.bbc.com/news/world-europe-32668511 ' ,
' info_dict ' : {
' id ' : ' world-europe-32668511 ' ,
2019-08-26 17:04:38 +02:00
' title ' : ' Russia stages massive WW2 parade ' ,
2015-07-25 16:21:42 +02:00
' description ' : ' md5:00ff61976f6081841f759a08bf78cc9c ' ,
2015-06-20 15:22:13 +02:00
} ,
' playlist_count ' : 2 ,
2015-07-17 09:47:02 +02:00
} , {
2015-10-10 19:56:55 +02:00
# article with multiple videos embedded with data-playable (more videos)
2015-06-20 15:22:13 +02:00
' url ' : ' http://www.bbc.com/news/business-28299555 ' ,
' info_dict ' : {
' id ' : ' business-28299555 ' ,
' title ' : ' Farnborough Airshow: Video highlights ' ,
2015-07-25 16:21:42 +02:00
' description ' : ' BBC reports and video highlights at the Farnborough Airshow. ' ,
2015-06-20 15:22:13 +02:00
} ,
' playlist_count ' : 9 ,
2015-07-25 16:21:42 +02:00
' skip ' : ' Save time ' ,
2015-07-27 18:05:51 +02:00
} , {
# article with multiple videos embedded with `new SMP()`
2015-10-10 19:56:55 +02:00
# broken
2015-07-27 18:05:51 +02:00
' url ' : ' http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460 ' ,
' info_dict ' : {
' id ' : ' 3662a707-0af9-3149-963f-47bea720b460 ' ,
2016-03-13 10:56:34 +01:00
' title ' : ' BUGGER ' ,
2015-07-27 18:05:51 +02:00
} ,
' playlist_count ' : 18 ,
2015-07-17 09:47:02 +02:00
} , {
2015-10-10 19:56:55 +02:00
# single video embedded with data-playable containing vpid
2015-06-20 15:22:13 +02:00
' url ' : ' http://www.bbc.com/news/world-europe-32041533 ' ,
' info_dict ' : {
' id ' : ' p02mprgb ' ,
2015-10-10 20:37:39 +02:00
' ext ' : ' mp4 ' ,
2015-06-20 15:22:13 +02:00
' title ' : ' Aerial footage showed the site of the crash in the Alps - courtesy BFM TV ' ,
2015-10-10 20:37:39 +02:00
' description ' : ' md5:2868290467291b37feda7863f7a83f54 ' ,
2015-06-20 15:22:13 +02:00
' duration ' : 47 ,
2015-07-25 16:21:42 +02:00
' timestamp ' : 1427219242 ,
2015-06-25 07:31:32 +02:00
' upload_date ' : ' 20150324 ' ,
2015-06-20 15:22:13 +02:00
} ,
' params ' : {
2015-07-25 16:21:42 +02:00
# rtmp download
2015-06-20 15:22:13 +02:00
' skip_download ' : True ,
}
2015-07-17 09:47:02 +02:00
} , {
2015-10-10 19:56:55 +02:00
# article with single video embedded with data-playable containing XML playlist
# with direct video links as progressiveDownloadUrl (for now these are extracted)
# and playlist with f4m and m3u8 as streamingUrl
2015-06-20 18:04:46 +02:00
' url ' : ' http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu ' ,
' info_dict ' : {
2015-07-25 16:21:42 +02:00
' id ' : ' 150615_telabyad_kentin_cogu ' ,
2015-06-20 18:04:46 +02:00
' ext ' : ' mp4 ' ,
2016-08-06 20:36:12 +02:00
' title ' : " YPG: Tel Abyad ' ı n tamamı kontrolümüzde" ,
2016-07-19 17:49:38 +02:00
' description ' : ' md5:33a4805a855c9baf7115fcbde57e7025 ' ,
2015-07-25 16:21:42 +02:00
' timestamp ' : 1434397334 ,
2015-06-25 07:31:32 +02:00
' upload_date ' : ' 20150615 ' ,
2015-06-20 18:04:46 +02:00
} ,
' params ' : {
' skip_download ' : True ,
}
2015-10-10 19:14:25 +02:00
} , {
2015-10-10 19:56:55 +02:00
# single video embedded with data-playable containing XML playlists (regional section)
2015-06-20 18:04:46 +02:00
' url ' : ' http://www.bbc.com/mundo/video_fotos/2015/06/150619_video_honduras_militares_hospitales_corrupcion_aw ' ,
' info_dict ' : {
2015-07-25 16:21:42 +02:00
' id ' : ' 150619_video_honduras_militares_hospitales_corrupcion_aw ' ,
2015-06-20 18:04:46 +02:00
' ext ' : ' mp4 ' ,
2015-07-25 16:21:42 +02:00
' title ' : ' Honduras militariza sus hospitales por nuevo escándalo de corrupción ' ,
2016-07-19 17:49:38 +02:00
' description ' : ' md5:1525f17448c4ee262b64b8f0c9ce66c8 ' ,
2015-07-25 16:21:42 +02:00
' timestamp ' : 1434713142 ,
2015-06-25 07:31:32 +02:00
' upload_date ' : ' 20150619 ' ,
2015-06-20 18:04:46 +02:00
} ,
' params ' : {
' skip_download ' : True ,
}
2015-08-04 16:44:22 +02:00
} , {
# single video from video playlist embedded with vxp-playlist-data JSON
' url ' : ' http://www.bbc.com/news/video_and_audio/must_see/33376376 ' ,
' info_dict ' : {
' id ' : ' p02w6qjc ' ,
2015-10-10 20:37:39 +02:00
' ext ' : ' mp4 ' ,
2015-08-04 16:44:22 +02:00
' title ' : ''' Judge Mindy Glazer: " I ' m sorry to see you here... I always wondered what happened to you " ''' ,
' duration ' : 56 ,
2015-12-05 09:54:25 +01:00
' description ' : ''' Judge Mindy Glazer: " I ' m sorry to see you here... I always wondered what happened to you " ''' ,
2015-08-04 16:44:22 +02:00
} ,
' params ' : {
' skip_download ' : True ,
}
2015-07-25 16:21:42 +02:00
} , {
# single video story with digitalData
' url ' : ' http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret ' ,
' info_dict ' : {
' id ' : ' p02q6gc4 ' ,
' ext ' : ' flv ' ,
' title ' : ' Sri Lanka’ s spicy secret ' ,
' description ' : ' As a new train line to Jaffna opens up the country’ s north, travellers can experience a truly distinct slice of Tamil culture. ' ,
' timestamp ' : 1437674293 ,
' upload_date ' : ' 20150723 ' ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
}
} , {
# single video story without digitalData
' url ' : ' http://www.bbc.com/autos/story/20130513-hyundais-rock-star ' ,
' info_dict ' : {
' id ' : ' p018zqqg ' ,
2015-10-10 20:37:39 +02:00
' ext ' : ' mp4 ' ,
2015-07-25 16:21:42 +02:00
' title ' : ' Hyundai Santa Fe Sport: Rock star ' ,
' description ' : ' md5:b042a26142c4154a6e472933cf20793d ' ,
2015-10-10 20:25:09 +02:00
' timestamp ' : 1415867444 ,
' upload_date ' : ' 20141113 ' ,
2015-07-25 16:21:42 +02:00
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
}
2016-08-07 13:01:50 +02:00
} , {
# single video embedded with Morph
' url ' : ' http://www.bbc.co.uk/sport/live/olympics/36895975 ' ,
' info_dict ' : {
' id ' : ' p041vhd0 ' ,
' ext ' : ' mp4 ' ,
' title ' : " Nigeria v Japan - Men ' s First Round " ,
' description ' : ' Live coverage of the first round from Group B at the Amazonia Arena. ' ,
' duration ' : 7980 ,
' uploader ' : ' BBC Sport ' ,
' uploader_id ' : ' bbc_sport ' ,
} ,
' params ' : {
# m3u8 download
' skip_download ' : True ,
} ,
' skip ' : ' Georestricted to UK ' ,
2015-07-25 16:21:42 +02:00
} , {
2015-10-10 19:56:55 +02:00
# single video with playlist.sxml URL in playlist param
2015-07-25 16:21:42 +02:00
' url ' : ' http://www.bbc.com/sport/0/football/33653409 ' ,
' info_dict ' : {
' id ' : ' p02xycnp ' ,
2015-10-10 20:37:39 +02:00
' ext ' : ' mp4 ' ,
2015-07-25 16:21:42 +02:00
' title ' : ' Transfers: Cristiano Ronaldo to Man Utd, Arsenal to spend? ' ,
2015-10-22 15:12:29 +02:00
' description ' : ' BBC Sport \' s David Ornstein has the latest transfer gossip, including rumours of a Manchester United return for Cristiano Ronaldo. ' ,
2015-07-25 16:21:42 +02:00
' duration ' : 140 ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
}
2015-10-10 16:55:46 +02:00
} , {
2015-10-10 19:56:55 +02:00
# article with multiple videos embedded with playlist.sxml in playlist param
2015-10-10 16:55:46 +02:00
' url ' : ' http://www.bbc.com/sport/0/football/34475836 ' ,
' info_dict ' : {
' id ' : ' 34475836 ' ,
2016-03-13 10:59:54 +01:00
' title ' : ' Jurgen Klopp: Furious football from a witty and winning coach ' ,
2016-04-16 12:00:19 +02:00
' description ' : ' Fast-paced football, wit, wisdom and a ready smile - why Liverpool fans should come to love new boss Jurgen Klopp. ' ,
2015-10-10 16:55:46 +02:00
} ,
' playlist_count ' : 3 ,
2016-03-13 10:59:54 +01:00
} , {
# school report article with single video
' url ' : ' http://www.bbc.co.uk/schoolreport/35744779 ' ,
' info_dict ' : {
' id ' : ' 35744779 ' ,
' title ' : ' School which breaks down barriers in Jerusalem ' ,
} ,
' playlist_count ' : 1 ,
2015-07-25 16:21:42 +02:00
} , {
# single video with playlist URL from weather section
' url ' : ' http://www.bbc.com/weather/features/33601775 ' ,
' only_matching ' : True ,
} , {
# custom redirection to www.bbc.com
' url ' : ' http://www.bbc.co.uk/news/science-environment-33661876 ' ,
' only_matching ' : True ,
2016-03-27 19:22:51 +02:00
} , {
# single video article embedded with data-media-vpid
' url ' : ' http://www.bbc.co.uk/sport/rowing/35908187 ' ,
' only_matching ' : True ,
2018-06-02 23:07:59 +02:00
} , {
' url ' : ' https://www.bbc.co.uk/bbcthree/clip/73d0bbd0-abc3-4cea-b3c0-cdae21905eb1 ' ,
' info_dict ' : {
' id ' : ' p06556y7 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Transfers: Cristiano Ronaldo to Man Utd, Arsenal to spend? ' ,
' description ' : ' md5:4b7dfd063d5a789a1512e99662be3ddd ' ,
} ,
' params ' : {
' skip_download ' : True ,
}
2018-07-21 12:49:55 +02:00
} , {
# window.__PRELOADED_STATE__
' url ' : ' https://www.bbc.co.uk/radio/play/b0b9z4yl ' ,
' info_dict ' : {
' id ' : ' b0b9z4vz ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Prom 6: An American in Paris and Turangalila ' ,
' description ' : ' md5:51cf7d6f5c8553f197e58203bc78dff8 ' ,
' uploader ' : ' Radio 3 ' ,
' uploader_id ' : ' bbc_radio_three ' ,
} ,
2018-12-31 17:20:40 +01:00
} , {
' url ' : ' http://www.bbc.co.uk/learningenglish/chinese/features/lingohack/ep-181227 ' ,
' info_dict ' : {
' id ' : ' p06w9tws ' ,
' ext ' : ' mp4 ' ,
' title ' : ' md5:2fabf12a726603193a2879a055f72514 ' ,
' description ' : ' Learn English words and phrases from this story ' ,
} ,
' add_ie ' : [ BBCCoUkIE . ie_key ( ) ] ,
2015-06-20 15:22:13 +02:00
} ]
2015-07-25 16:21:42 +02:00
@classmethod
def suitable ( cls , url ) :
2016-06-17 18:42:52 +02:00
EXCLUDE_IE = ( BBCCoUkIE , BBCCoUkArticleIE , BBCCoUkIPlayerPlaylistIE , BBCCoUkPlaylistIE )
return ( False if any ( ie . suitable ( url ) for ie in EXCLUDE_IE )
else super ( BBCIE , cls ) . suitable ( url ) )
2015-07-25 16:21:42 +02:00
def _extract_from_media_meta ( self , media_meta , video_id ) :
# Direct links to media in media metadata (e.g.
# http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
# TODO: there are also f4m and m3u8 streams incorporated in playlist.sxml
source_files = media_meta . get ( ' sourceFiles ' )
if source_files :
return [ {
' url ' : f [ ' url ' ] ,
' format_id ' : format_id ,
' ext ' : f . get ( ' encoding ' ) ,
' tbr ' : float_or_none ( f . get ( ' bitrate ' ) , 1000 ) ,
' filesize ' : int_or_none ( f . get ( ' filesize ' ) ) ,
} for format_id , f in source_files . items ( ) if f . get ( ' url ' ) ] , [ ]
programme_id = media_meta . get ( ' externalId ' )
if programme_id :
return self . _download_media_selector ( programme_id )
# Process playlist.sxml as legacy playlist
href = media_meta . get ( ' href ' )
if href :
playlist = self . _download_legacy_playlist_url ( href )
_ , _ , _ , _ , formats , subtitles = self . _extract_from_legacy_playlist ( playlist , video_id )
return formats , subtitles
return [ ] , [ ]
2015-10-10 17:32:27 +02:00
def _extract_from_playlist_sxml ( self , url , playlist_id , timestamp ) :
programme_id , title , description , duration , formats , subtitles = \
self . _process_legacy_playlist_url ( url , playlist_id )
self . _sort_formats ( formats )
return {
' id ' : programme_id ,
' title ' : title ,
' description ' : description ,
' duration ' : duration ,
' timestamp ' : timestamp ,
' formats ' : formats ,
' subtitles ' : subtitles ,
}
2015-06-20 15:22:13 +02:00
def _real_extract ( self , url ) :
2015-07-25 16:21:42 +02:00
playlist_id = self . _match_id ( url )
webpage = self . _download_webpage ( url , playlist_id )
2016-08-08 17:44:36 +02:00
json_ld_info = self . _search_json_ld ( webpage , playlist_id , default = { } )
2016-01-16 13:46:28 +01:00
timestamp = json_ld_info . get ( ' timestamp ' )
2016-03-13 10:54:56 +01:00
2016-01-16 13:46:28 +01:00
playlist_title = json_ld_info . get ( ' title ' )
2016-03-13 10:54:56 +01:00
if not playlist_title :
playlist_title = self . _og_search_title (
webpage , default = None ) or self . _html_search_regex (
r ' <title>(.+?)</title> ' , webpage , ' playlist title ' , default = None )
if playlist_title :
playlist_title = re . sub ( r ' (.+) \ s*- \ s*BBC.*?$ ' , r ' \ 1 ' , playlist_title ) . strip ( )
playlist_description = json_ld_info . get (
' description ' ) or self . _og_search_description ( webpage , default = None )
2015-10-10 20:25:09 +02:00
if not timestamp :
timestamp = parse_iso8601 ( self . _search_regex (
[ r ' <meta[^>]+property= " article:published_time " [^>]+content= " ([^ " ]+) " ' ,
r ' itemprop= " datePublished " [^>]+datetime= " ([^ " ]+) " ' ,
2015-10-10 20:39:28 +02:00
r ' " datePublished " : \ s* " ([^ " ]+) ' ] ,
2015-10-10 20:25:09 +02:00
webpage , ' date ' , default = None ) )
2015-07-25 16:21:42 +02:00
2015-10-10 19:40:20 +02:00
entries = [ ]
2015-10-10 16:40:56 +02:00
# article with multiple videos embedded with playlist.sxml (e.g.
# http://www.bbc.com/sport/0/football/34475836)
playlists = re . findall ( r ' <param[^>]+name= " playlist " [^>]+value= " ([^ " ]+) " ' , webpage )
2015-12-06 11:41:12 +01:00
playlists . extend ( re . findall ( r ' data-media-id= " ([^ " ]+/playlist \ .sxml) " ' , webpage ) )
2015-10-10 16:40:56 +02:00
if playlists :
2015-10-10 17:32:27 +02:00
entries = [
self . _extract_from_playlist_sxml ( playlist_url , playlist_id , timestamp )
for playlist_url in playlists ]
2015-06-20 18:04:46 +02:00
2015-10-10 19:40:20 +02:00
# news article with multiple videos embedded with data-playable
data_playables = re . findall ( r ' data-playable=([ " \' ])( { .+?}) \ 1 ' , webpage )
if data_playables :
for _ , data_playable_json in data_playables :
data_playable = self . _parse_json (
unescapeHTML ( data_playable_json ) , playlist_id , fatal = False )
if not data_playable :
continue
2015-10-10 17:32:27 +02:00
settings = data_playable . get ( ' settings ' , { } )
if settings :
2015-10-10 19:40:20 +02:00
# data-playable with video vpid in settings.playlistObject.items (e.g.
# http://www.bbc.com/news/world-us-canada-34473351)
2015-10-10 17:32:27 +02:00
playlist_object = settings . get ( ' playlistObject ' , { } )
if playlist_object :
items = playlist_object . get ( ' items ' )
if items and isinstance ( items , list ) :
2015-10-10 19:40:20 +02:00
title = playlist_object [ ' title ' ]
description = playlist_object . get ( ' summary ' )
2015-10-10 17:32:27 +02:00
duration = int_or_none ( items [ 0 ] . get ( ' duration ' ) )
programme_id = items [ 0 ] . get ( ' vpid ' )
2015-10-10 19:40:20 +02:00
formats , subtitles = self . _download_media_selector ( programme_id )
self . _sort_formats ( formats )
entries . append ( {
' id ' : programme_id ,
' title ' : title ,
' description ' : description ,
' timestamp ' : timestamp ,
' duration ' : duration ,
' formats ' : formats ,
' subtitles ' : subtitles ,
} )
else :
# data-playable without vpid but with a playlist.sxml URLs
# in otherSettings.playlist (e.g.
# http://www.bbc.com/turkce/multimedya/2015/10/151010_vid_ankara_patlama_ani)
playlist = data_playable . get ( ' otherSettings ' , { } ) . get ( ' playlist ' , { } )
if playlist :
2016-08-06 19:48:09 +02:00
entry = None
for key in ( ' streaming ' , ' progressiveDownload ' ) :
2016-07-19 17:49:38 +02:00
playlist_url = playlist . get ( ' %s Url ' % key )
if not playlist_url :
continue
try :
2016-08-06 19:48:09 +02:00
info = self . _extract_from_playlist_sxml (
playlist_url , playlist_id , timestamp )
if not entry :
entry = info
else :
entry [ ' title ' ] = info [ ' title ' ]
entry [ ' formats ' ] . extend ( info [ ' formats ' ] )
2016-07-19 17:49:38 +02:00
except Exception as e :
# Some playlist URL may fail with 500, at the same time
# the other one may work fine (e.g.
# http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
if isinstance ( e . cause , compat_HTTPError ) and e . cause . code == 500 :
continue
raise
2016-08-06 19:48:09 +02:00
if entry :
self . _sort_formats ( entry [ ' formats ' ] )
entries . append ( entry )
2015-10-10 19:40:20 +02:00
if entries :
return self . playlist_result ( entries , playlist_id , playlist_title , playlist_description )
2018-12-31 17:20:40 +01:00
# http://www.bbc.co.uk/learningenglish/chinese/features/lingohack/ep-181227
group_id = self . _search_regex (
r ' <div[^>]+ \ bclass=[ " \' ]video[ " \' ][^>]+ \ bdata-pid=[ " \' ]( %s ) ' % self . _ID_REGEX ,
webpage , ' group id ' , default = None )
if playlist_id :
return self . url_result (
' https://www.bbc.co.uk/programmes/ %s ' % group_id ,
ie = BBCCoUkIE . ie_key ( ) )
2015-10-10 19:40:20 +02:00
# single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
programme_id = self . _search_regex (
2016-03-27 19:22:51 +02:00
[ r ' data-(?:video-player|media)-vpid= " ( %s ) " ' % self . _ID_REGEX ,
2015-12-01 21:34:31 +01:00
r ' <param[^>]+name= " externalIdentifier " [^>]+value= " ( %s ) " ' % self . _ID_REGEX ,
r ' videoId \ s*: \ s*[ " \' ]( %s )[ " \' ] ' % self . _ID_REGEX ] ,
2015-10-10 19:40:20 +02:00
webpage , ' vpid ' , default = None )
2015-10-10 16:34:06 +02:00
2015-07-25 16:21:42 +02:00
if programme_id :
formats , subtitles = self . _download_media_selector ( programme_id )
self . _sort_formats ( formats )
# digitalData may be missing (e.g. http://www.bbc.com/autos/story/20130513-hyundais-rock-star)
digital_data = self . _parse_json (
self . _search_regex (
r ' var \ s+digitalData \ s*= \ s*( { .+?});? \ n ' , webpage , ' digital data ' , default = ' {} ' ) ,
programme_id , fatal = False )
page_info = digital_data . get ( ' page ' , { } ) . get ( ' pageInfo ' , { } )
title = page_info . get ( ' pageName ' ) or self . _og_search_title ( webpage )
description = page_info . get ( ' description ' ) or self . _og_search_description ( webpage )
timestamp = parse_iso8601 ( page_info . get ( ' publicationDate ' ) ) or timestamp
return {
' id ' : programme_id ,
' title ' : title ,
' description ' : description ,
' timestamp ' : timestamp ,
' formats ' : formats ,
' subtitles ' : subtitles ,
}
2015-07-17 09:47:02 +02:00
2016-08-07 13:01:50 +02:00
# Morph based embed (e.g. http://www.bbc.co.uk/sport/live/olympics/36895975)
# There are several setPayload calls may be present but the video
# seems to be always related to the first one
morph_payload = self . _parse_json (
self . _search_regex (
r ' Morph \ .setPayload \ ([^,]+, \ s*( { .+?}) \ ); ' ,
webpage , ' morph payload ' , default = ' {} ' ) ,
playlist_id , fatal = False )
if morph_payload :
components = try_get ( morph_payload , lambda x : x [ ' body ' ] [ ' components ' ] , list ) or [ ]
for component in components :
if not isinstance ( component , dict ) :
continue
lead_media = try_get ( component , lambda x : x [ ' props ' ] [ ' leadMedia ' ] , dict )
if not lead_media :
continue
identifiers = lead_media . get ( ' identifiers ' )
if not identifiers or not isinstance ( identifiers , dict ) :
continue
programme_id = identifiers . get ( ' vpid ' ) or identifiers . get ( ' playablePid ' )
if not programme_id :
continue
title = lead_media . get ( ' title ' ) or self . _og_search_title ( webpage )
formats , subtitles = self . _download_media_selector ( programme_id )
self . _sort_formats ( formats )
description = lead_media . get ( ' summary ' )
uploader = lead_media . get ( ' masterBrand ' )
uploader_id = lead_media . get ( ' mid ' )
duration = None
duration_d = lead_media . get ( ' duration ' )
if isinstance ( duration_d , dict ) :
duration = parse_duration ( dict_get (
duration_d , ( ' rawDuration ' , ' formattedDuration ' , ' spokenDuration ' ) ) )
return {
' id ' : programme_id ,
' title ' : title ,
' description ' : description ,
' duration ' : duration ,
' uploader ' : uploader ,
' uploader_id ' : uploader_id ,
' formats ' : formats ,
' subtitles ' : subtitles ,
}
2018-07-21 12:49:55 +02:00
preload_state = self . _parse_json ( self . _search_regex (
r ' window \ .__PRELOADED_STATE__ \ s*= \ s*( { .+?}); ' , webpage ,
' preload state ' , default = ' {} ' ) , playlist_id , fatal = False )
if preload_state :
current_programme = preload_state . get ( ' programmes ' , { } ) . get ( ' current ' ) or { }
programme_id = current_programme . get ( ' id ' )
if current_programme and programme_id and current_programme . get ( ' type ' ) == ' playable_item ' :
title = current_programme . get ( ' titles ' , { } ) . get ( ' tertiary ' ) or playlist_title
formats , subtitles = self . _download_media_selector ( programme_id )
self . _sort_formats ( formats )
synopses = current_programme . get ( ' synopses ' ) or { }
network = current_programme . get ( ' network ' ) or { }
duration = int_or_none (
current_programme . get ( ' duration ' , { } ) . get ( ' value ' ) )
thumbnail = None
image_url = current_programme . get ( ' image_url ' )
if image_url :
thumbnail = image_url . replace ( ' {recipe} ' , ' 1920x1920 ' )
return {
' id ' : programme_id ,
' title ' : title ,
' description ' : dict_get ( synopses , ( ' long ' , ' medium ' , ' short ' ) ) ,
' thumbnail ' : thumbnail ,
' duration ' : duration ,
' uploader ' : network . get ( ' short_title ' ) ,
' uploader_id ' : network . get ( ' id ' ) ,
' formats ' : formats ,
' subtitles ' : subtitles ,
}
2018-06-02 23:07:59 +02:00
bbc3_config = self . _parse_json (
self . _search_regex (
r ' (?s)bbcthreeConfig \ s*= \ s*( { .+?}) \ s*; \ s*< ' , webpage ,
' bbcthree config ' , default = ' {} ' ) ,
playlist_id , transform_source = js_to_json , fatal = False )
if bbc3_config :
bbc3_playlist = try_get (
bbc3_config , lambda x : x [ ' payload ' ] [ ' content ' ] [ ' bbcMedia ' ] [ ' playlist ' ] ,
dict )
if bbc3_playlist :
playlist_title = bbc3_playlist . get ( ' title ' ) or playlist_title
thumbnail = bbc3_playlist . get ( ' holdingImageURL ' )
entries = [ ]
for bbc3_item in bbc3_playlist [ ' items ' ] :
programme_id = bbc3_item . get ( ' versionID ' )
if not programme_id :
continue
formats , subtitles = self . _download_media_selector ( programme_id )
self . _sort_formats ( formats )
entries . append ( {
' id ' : programme_id ,
' title ' : playlist_title ,
' thumbnail ' : thumbnail ,
' timestamp ' : timestamp ,
' formats ' : formats ,
' subtitles ' : subtitles ,
} )
return self . playlist_result (
entries , playlist_id , playlist_title , playlist_description )
2015-07-27 18:05:51 +02:00
def extract_all ( pattern ) :
return list ( filter ( None , map (
lambda s : self . _parse_json ( s , playlist_id , fatal = False ) ,
re . findall ( pattern , webpage ) ) ) )
# Multiple video article (e.g.
# http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
2015-12-01 21:34:31 +01:00
EMBED_URL = r ' https?://(?:www \ .)?bbc \ .co \ .uk/(?:[^/]+/)+ %s (?: \ b[^ " ]+)? ' % self . _ID_REGEX
2015-07-27 18:05:51 +02:00
entries = [ ]
for match in extract_all ( r ' new \ s+SMP \ (( { .+?}) \ ) ' ) :
embed_url = match . get ( ' playerSettings ' , { } ) . get ( ' externalEmbedUrl ' )
if embed_url and re . match ( EMBED_URL , embed_url ) :
entries . append ( embed_url )
entries . extend ( re . findall (
r ' setPlaylist \ ( " ( %s ) " \ ) ' % EMBED_URL , webpage ) )
if entries :
return self . playlist_result (
2016-08-07 13:05:13 +02:00
[ self . url_result ( entry_ , ' BBCCoUk ' ) for entry_ in entries ] ,
2015-07-27 18:05:51 +02:00
playlist_id , playlist_title , playlist_description )
2015-07-25 16:21:42 +02:00
# Multiple video article (e.g. http://www.bbc.com/news/world-europe-32668511)
2015-07-27 18:05:51 +02:00
medias = extract_all ( r " data-media-meta= ' ( { [^ ' ]+}) ' " )
2015-07-25 16:21:42 +02:00
if not medias :
# Single video article (e.g. http://www.bbc.com/news/video_and_audio/international)
2015-08-04 16:44:22 +02:00
media_asset = self . _search_regex (
r ' mediaAssetPage \ .init \ ( \ s*( { .+?}), " / ' ,
webpage , ' media asset ' , default = None )
if media_asset :
media_asset_page = self . _parse_json ( media_asset , playlist_id , fatal = False )
medias = [ ]
for video in media_asset_page . get ( ' videos ' , { } ) . values ( ) :
medias . extend ( video . values ( ) )
if not medias :
# Multiple video playlist with single `now playing` entry (e.g.
# http://www.bbc.com/news/video_and_audio/must_see/33767813)
vxp_playlist = self . _parse_json (
2015-07-25 16:21:42 +02:00
self . _search_regex (
2015-08-04 16:44:22 +02:00
r ' <script[^>]+class= " vxp-playlist-data " [^>]+type= " application/json " [^>]*>([^<]+)</script> ' ,
webpage , ' playlist data ' ) ,
2015-07-25 16:21:42 +02:00
playlist_id )
2015-08-04 16:44:22 +02:00
playlist_medias = [ ]
for item in vxp_playlist :
media = item . get ( ' media ' )
if not media :
continue
playlist_medias . append ( media )
# Download single video if found media with asset id matching the video id from URL
if item . get ( ' advert ' , { } ) . get ( ' assetId ' ) == playlist_id :
medias = [ media ]
break
# Fallback to the whole playlist
if not medias :
medias = playlist_medias
2015-07-25 16:21:42 +02:00
entries = [ ]
for num , media_meta in enumerate ( medias , start = 1 ) :
formats , subtitles = self . _extract_from_media_meta ( media_meta , playlist_id )
if not formats :
continue
2015-06-20 15:22:13 +02:00
self . _sort_formats ( formats )
2015-07-25 16:21:42 +02:00
video_id = media_meta . get ( ' externalId ' )
if not video_id :
video_id = playlist_id if len ( medias ) == 1 else ' %s - %s ' % ( playlist_id , num )
title = media_meta . get ( ' caption ' )
if not title :
title = playlist_title if len ( medias ) == 1 else ' %s - Video %s ' % ( playlist_title , num )
duration = int_or_none ( media_meta . get ( ' durationInSeconds ' ) ) or parse_duration ( media_meta . get ( ' duration ' ) )
2015-06-25 07:31:32 +02:00
2015-07-25 16:21:42 +02:00
images = [ ]
for image in media_meta . get ( ' images ' , { } ) . values ( ) :
images . extend ( image . values ( ) )
if ' image ' in media_meta :
images . append ( media_meta [ ' image ' ] )
thumbnails = [ {
' url ' : image . get ( ' href ' ) ,
' width ' : int_or_none ( image . get ( ' width ' ) ) ,
' height ' : int_or_none ( image . get ( ' height ' ) ) ,
} for image in images ]
entries . append ( {
' id ' : video_id ,
2015-06-20 15:22:13 +02:00
' title ' : title ,
2015-07-25 16:21:42 +02:00
' thumbnails ' : thumbnails ,
2015-06-20 15:22:13 +02:00
' duration ' : duration ,
2015-07-25 16:21:42 +02:00
' timestamp ' : timestamp ,
2015-06-20 15:22:13 +02:00
' formats ' : formats ,
' subtitles ' : subtitles ,
2015-07-17 09:47:02 +02:00
} )
2015-06-20 15:22:13 +02:00
2015-07-25 16:21:42 +02:00
return self . playlist_result ( entries , playlist_id , playlist_title , playlist_description )
2015-10-22 15:13:03 +02:00
class BBCCoUkArticleIE ( InfoExtractor ) :
2016-09-08 13:29:05 +02:00
_VALID_URL = r ' https?://(?:www \ .)?bbc \ .co \ .uk/programmes/articles/(?P<id>[a-zA-Z0-9]+) '
2015-10-22 15:13:03 +02:00
IE_NAME = ' bbc.co.uk:article '
IE_DESC = ' BBC articles '
_TEST = {
' url ' : ' http://www.bbc.co.uk/programmes/articles/3jNQLTMrPlYGTBn0WV6M2MS/not-your-typical-role-model-ada-lovelace-the-19th-century-programmer ' ,
' info_dict ' : {
' id ' : ' 3jNQLTMrPlYGTBn0WV6M2MS ' ,
' title ' : ' Calculating Ada: The Countess of Computing - Not your typical role model: Ada Lovelace the 19th century programmer - BBC Four ' ,
' description ' : ' Hannah Fry reveals some of her surprising discoveries about Ada Lovelace during filming. ' ,
} ,
' playlist_count ' : 4 ,
' add_ie ' : [ ' BBCCoUk ' ] ,
}
def _real_extract ( self , url ) :
playlist_id = self . _match_id ( url )
webpage = self . _download_webpage ( url , playlist_id )
title = self . _og_search_title ( webpage )
description = self . _og_search_description ( webpage ) . strip ( )
entries = [ self . url_result ( programme_url ) for programme_url in re . findall (
r ' <div[^>]+typeof= " Clip " [^>]+resource= " ([^ " ]+) " ' , webpage ) ]
return self . playlist_result ( entries , playlist_id , title , description )
2016-06-17 18:42:52 +02:00
class BBCCoUkPlaylistBaseIE ( InfoExtractor ) :
2016-08-15 23:36:23 +02:00
def _entries ( self , webpage , url , playlist_id ) :
single_page = ' page ' in compat_urlparse . parse_qs (
compat_urlparse . urlparse ( url ) . query )
for page_num in itertools . count ( 2 ) :
for video_id in re . findall (
self . _VIDEO_ID_TEMPLATE % BBCCoUkIE . _ID_REGEX , webpage ) :
yield self . url_result (
self . _URL_TEMPLATE % video_id , BBCCoUkIE . ie_key ( ) )
if single_page :
return
next_page = self . _search_regex (
r ' <li[^>]+class=([ " \' ])pagination_+next \ 1[^>]*><a[^>]+href=([ " \' ])(?P<url>(?:(?! \ 2).)+) \ 2 ' ,
webpage , ' next page url ' , default = None , group = ' url ' )
if not next_page :
break
webpage = self . _download_webpage (
compat_urlparse . urljoin ( url , next_page ) , playlist_id ,
' Downloading page %d ' % page_num , page_num )
2016-06-17 18:42:52 +02:00
def _real_extract ( self , url ) :
playlist_id = self . _match_id ( url )
webpage = self . _download_webpage ( url , playlist_id )
title , description = self . _extract_title_and_description ( webpage )
2016-08-15 23:36:23 +02:00
return self . playlist_result (
self . _entries ( webpage , url , playlist_id ) ,
playlist_id , title , description )
2016-06-17 18:42:52 +02:00
class BBCCoUkIPlayerPlaylistIE ( BBCCoUkPlaylistBaseIE ) :
IE_NAME = ' bbc.co.uk:iplayer:playlist '
2016-07-21 17:37:36 +02:00
_VALID_URL = r ' https?://(?:www \ .)?bbc \ .co \ .uk/iplayer/(?:episodes|group)/(?P<id> %s ) ' % BBCCoUkIE . _ID_REGEX
2016-06-17 18:42:52 +02:00
_URL_TEMPLATE = ' http://www.bbc.co.uk/iplayer/episode/ %s '
_VIDEO_ID_TEMPLATE = r ' data-ip-id=[ " \' ]( %s ) '
2016-07-21 17:37:36 +02:00
_TESTS = [ {
2016-06-17 18:42:52 +02:00
' url ' : ' http://www.bbc.co.uk/iplayer/episodes/b05rcz9v ' ,
' info_dict ' : {
' id ' : ' b05rcz9v ' ,
' title ' : ' The Disappearance ' ,
' description ' : ' French thriller serial about a missing teenager. ' ,
} ,
' playlist_mincount ' : 6 ,
2016-07-21 17:34:55 +02:00
' skip ' : ' This programme is not currently available on BBC iPlayer ' ,
2016-07-21 17:37:36 +02:00
} , {
# Available for over a year unlike 30 days for most other programmes
' url ' : ' http://www.bbc.co.uk/iplayer/group/p02tcc32 ' ,
' info_dict ' : {
' id ' : ' p02tcc32 ' ,
' title ' : ' Bohemian Icons ' ,
' description ' : ' md5:683e901041b2fe9ba596f2ab04c4dbe7 ' ,
} ,
' playlist_mincount ' : 10 ,
} ]
2016-06-17 18:42:52 +02:00
def _extract_title_and_description ( self , webpage ) :
title = self . _search_regex ( r ' <h1>([^<]+)</h1> ' , webpage , ' title ' , fatal = False )
description = self . _search_regex (
r ' <p[^>]+class=([ " \' ])subtitle \ 1[^>]*>(?P<value>[^<]+)</p> ' ,
webpage , ' description ' , fatal = False , group = ' value ' )
return title , description
class BBCCoUkPlaylistIE ( BBCCoUkPlaylistBaseIE ) :
IE_NAME = ' bbc.co.uk:playlist '
_VALID_URL = r ' https?://(?:www \ .)?bbc \ .co \ .uk/programmes/(?P<id> %s )/(?:episodes|broadcasts|clips) ' % BBCCoUkIE . _ID_REGEX
_URL_TEMPLATE = ' http://www.bbc.co.uk/programmes/ %s '
_VIDEO_ID_TEMPLATE = r ' data-pid=[ " \' ]( %s ) '
_TESTS = [ {
' url ' : ' http://www.bbc.co.uk/programmes/b05rcz9v/clips ' ,
' info_dict ' : {
' id ' : ' b05rcz9v ' ,
' title ' : ' The Disappearance - Clips - BBC Four ' ,
' description ' : ' French thriller serial about a missing teenager. ' ,
} ,
' playlist_mincount ' : 7 ,
2016-08-15 23:43:10 +02:00
} , {
# multipage playlist, explicit page
' url ' : ' http://www.bbc.co.uk/programmes/b00mfl7n/clips?page=1 ' ,
' info_dict ' : {
' id ' : ' b00mfl7n ' ,
' title ' : ' Frozen Planet - Clips - BBC One ' ,
' description ' : ' md5:65dcbf591ae628dafe32aa6c4a4a0d8c ' ,
} ,
' playlist_mincount ' : 24 ,
} , {
# multipage playlist, all pages
' url ' : ' http://www.bbc.co.uk/programmes/b00mfl7n/clips ' ,
' info_dict ' : {
' id ' : ' b00mfl7n ' ,
' title ' : ' Frozen Planet - Clips - BBC One ' ,
' description ' : ' md5:65dcbf591ae628dafe32aa6c4a4a0d8c ' ,
} ,
' playlist_mincount ' : 142 ,
2016-06-17 18:42:52 +02:00
} , {
' url ' : ' http://www.bbc.co.uk/programmes/b05rcz9v/broadcasts/2016/06 ' ,
' only_matching ' : True ,
} , {
' url ' : ' http://www.bbc.co.uk/programmes/b05rcz9v/clips ' ,
' only_matching ' : True ,
} , {
' url ' : ' http://www.bbc.co.uk/programmes/b055jkys/episodes/player ' ,
' only_matching ' : True ,
} ]
def _extract_title_and_description ( self , webpage ) :
title = self . _og_search_title ( webpage , fatal = False )
description = self . _og_search_description ( webpage )
return title , description