@ -5,65 +5,105 @@ import re
from .common import InfoExtractor
from .common import InfoExtractor
from ..utils import (
from ..utils import (
int_or_none ,
determine_ext ,
determine_ext ,
float_or_none ,
int_or_none ,
)
)
class LimeLightBaseIE ( InfoExtractor ) :
class LimelightBaseIE ( InfoExtractor ) :
_PLAYLIST_SERVICE_URL = ' http://production-ps.lvp.llnw.net/r/PlaylistService/ %s / %s / %s '
_API_URL = ' http://api.video.limelight.com/rest/organizations/ %s / %s / %s / %s .json '
def get_playlist_service ( self , id , method ) :
return self . _download_json ( self . PLAYLIST_SERVICE_URL % ( id , method ) , id )
def _call_playlist_service ( self , item_id , method , fatal = True ) :
return self . _download_json (
self . _PLAYLIST_SERVICE_URL % ( self . _PLAYLIST_SERVICE_PATH , item_id , method ) ,
item_id , ' Downloading PlaylistService %s JSON ' % method , fatal = fatal )
def get_api ( self , orgId , id , method ) :
return self . _download_json ( self . API_URL % ( orgId , id , method ) , id )
def _call_api ( self , organization_id , item_id , method ) :
return self . _download_json (
self . _API_URL % ( organization_id , self . _API_PATH , item_id , method ) ,
item_id , ' Downloading API %s JSON ' % method )
def process_data ( self , mobileUrls , streams , properties ) :
def _extract ( self , item_id , pc_method , mobile_method , meta_method ) :
pc = self . _call_playlist_service ( item_id , pc_method )
metadata = self . _call_api ( pc [ ' orgId ' ] , item_id , meta_method )
mobile = self . _call_playlist_service ( item_id , mobile_method , fatal = False )
return pc , mobile , metadata
def _extract_info ( self , streams , mobile_urls , properties ) :
video_id = properties [ ' media_id ' ]
video_id = properties [ ' media_id ' ]
formats = [ ]
formats = [ ]
for mobileUrl in mobileUrls :
if ' .m3u8 ' in mobileUrl [ ' mobileUrl ' ] :
formats . extend ( self . _extract_m3u8_formats ( mobileUrl [ ' mobileUrl ' ] , video_id ) )
else :
formats . append ( { ' url ' : mobileUrl [ ' mobileUrl ' ] } )
for stream in streams :
for stream in streams :
if ' .f4m ' in stream [ ' url ' ] :
formats . extend ( self . _extract_f4m_formats ( stream [ ' url ' ] , video_id ) )
stream_url = stream . get ( ' url ' )
if not stream_url :
continue
if ' .f4m ' in stream_url :
formats . extend ( self . _extract_f4m_formats ( stream_url , video_id ) )
else :
else :
fmt = {
fmt = {
' url ' : stream . get ( ' url ' ) ,
' abr ' : stream . get ( ' audioBitRate ' ) ,
' vbr ' : stream . get ( ' videoBitRate ' ) ,
' fps ' : stream . get ( ' videoFrameRate ' ) ,
' width ' : stream . get ( ' videoWidthInPixels ' ) ,
' height ' : stream . get ( ' videoHeightInPixels ' ) ,
' ext ' : determine_ext ( stream . get ( ' url ' ) )
' url ' : stream_url ,
' abr ' : float_or_none ( stream . get ( ' audioBitRate ' ) ) ,
' vbr ' : float_or_none ( stream . get ( ' videoBitRate ' ) ) ,
' fps ' : float_or_none ( stream . get ( ' videoFrameRate ' ) ) ,
' width ' : int_or_none ( stream . get ( ' videoWidthInPixels ' ) ) ,
' height ' : int_or_none ( stream . get ( ' videoHeightInPixels ' ) ) ,
' ext ' : determine_ext ( stream_url )
}
}
rtmp = re . search ( r ' ^(?P<url>rtmp://[^/]+/(?P<app>.+))/(?P<playpath>mp4:.+)$ ' , stream [ ' url ' ] )
rtmp = re . search ( r ' ^(?P<url>rtmpe? ://[^/]+/(?P<app>.+))/(?P<playpath>mp4:.+)$ ' , stream_url )
if rtmp :
if rtmp :
format_id = ' rtmp '
if stream . get ( ' videoBitRate ' ) :
format_id + = ' - %d ' % int_or_none ( stream [ ' videoBitRate ' ] )
fmt . update ( {
fmt . update ( {
' url ' : rtmp . group ( ' url ' ) ,
' url ' : rtmp . group ( ' url ' ) ,
' play_path ' : rtmp . group ( ' playpath ' ) ,
' play_path ' : rtmp . group ( ' playpath ' ) ,
' app ' : rtmp . group ( ' app ' ) ,
' app ' : rtmp . group ( ' app ' ) ,
' ext ' : ' flv ' ,
' format_id ' : format_id ,
} )
} )
formats . append ( fmt )
formats . append ( fmt )
for mobile_url in mobile_urls :
media_url = mobile_url . get ( ' mobileUrl ' )
if not media_url :
continue
format_id = mobile_url . get ( ' targetMediaPlatform ' )
if determine_ext ( media_url ) == ' m3u8 ' :
formats . extend ( self . _extract_m3u8_formats (
media_url , video_id , ' mp4 ' , entry_protocol = ' m3u8_native ' ,
preference = - 1 , m3u8_id = format_id ) )
else :
formats . append ( {
' url ' : media_url ,
' format_id ' : format_id ,
' preference ' : - 1 ,
} )
self . _sort_formats ( formats )
self . _sort_formats ( formats )
title = properties [ ' title ' ]
title = properties [ ' title ' ]
description = properties . get ( ' description ' )
description = properties . get ( ' description ' )
timestamp = properties . get ( ' create_date ' )
duration = int_or_none ( properties . get ( ' duration_in_milliseconds ' ) )
filesize = properties . get ( ' total_storage_in_bytes ' )
timestamp = int_or_none ( properties . get ( ' publish_date ' ) or properties . get ( ' create_date ' ) )
duration = floa t_or_none( properties . get ( ' duration_in_milliseconds ' ) , 1000 )
filesize = int_or_none ( properties . get ( ' total_storage_in_bytes ' ) )
categories = [ properties . get ( ' category ' ) ]
categories = [ properties . get ( ' category ' ) ]
tags = properties . get ( ' tags ' , [ ] )
thumbnails = [ {
thumbnails = [ {
' url ' : thumbnail . get ( ' url ' ) ,
' url ' : thumbnail [ ' url ' ] ,
' width ' : int_or_none ( thumbnail . get ( ' width ' ) ) ,
' width ' : int_or_none ( thumbnail . get ( ' width ' ) ) ,
' height ' : int_or_none ( thumbnail . get ( ' height ' ) ) ,
' height ' : int_or_none ( thumbnail . get ( ' height ' ) ) ,
} for thumbnail in properties . get ( ' thumbnails ' ) ]
subtitles = { caption . get ( ' language_code ' ) : [ { ' url ' : caption . get ( ' url ' ) } ] for caption in properties . get ( ' captions ' ) }
} for thumbnail in properties . get ( ' thumbnails ' , [ ] ) if thumbnail . get ( ' url ' ) ]
subtitles = { }
for caption in properties . get ( ' captions ' , { } ) :
lang = caption . get ( ' language_code ' )
subtitles_url = caption . get ( ' url ' )
if lang and subtitles_url :
subtitles [ lang ] = [ {
' url ' : subtitles_url ,
} ]
return {
return {
' id ' : video_id ,
' id ' : video_id ,
@ -74,44 +114,50 @@ class LimeLightBaseIE(InfoExtractor):
' duration ' : duration ,
' duration ' : duration ,
' filesize ' : filesize ,
' filesize ' : filesize ,
' categories ' : categories ,
' categories ' : categories ,
' tags ' : tags ,
' thumbnails ' : thumbnails ,
' thumbnails ' : thumbnails ,
' subtitles ' : subtitles ,
' subtitles ' : subtitles ,
}
}
class LimeLightMediaIE ( LimeL ightBaseIE ) :
class LimelightMediaIE ( Limel ightBaseIE ) :
IE_NAME = ' limelight '
IE_NAME = ' limelight '
_VALID_URL = r ' http://link \ .videoplatform \ .limelight \ .com/media/?.*mediaId= (?P<id>[a-z0-9]{32}) '
_VALID_URL = r ' (?:limelight:media:| http://link\ .videoplatform \ .limelight \ .com/media/ \ ?? \ bmediaId=) (?P<id>[a-z0-9]{32})'
_TEST = {
_TEST = {
' url ' : ' http://link.videoplatform.limelight.com/media/?mediaId=3ffd040b522b4485b6d84effc750cd86 ' ,
' url ' : ' http://link.videoplatform.limelight.com/media/?mediaId=3ffd040b522b4485b6d84effc750cd86 ' ,
' md5 ' : ' 3213605088be599705677ef785db6972 ' ,
' info_dict ' : {
' info_dict ' : {
' id ' : ' 3ffd040b522b4485b6d84effc750cd86 ' ,
' id ' : ' 3ffd040b522b4485b6d84effc750cd86 ' ,
' ext ' : ' mp4 ' ,
' ext ' : ' flv ' ,
' title ' : ' HaP and the HB Prince Trailer ' ,
' title ' : ' HaP and the HB Prince Trailer ' ,
' description ' : ' As Harry Potter begins his 6th year at Hogwarts School of Witchcraft and Wizardry, he discovers an old book marked mysteriously " This book is the property of the Half-Blood Prince " and begins to learn more about Lord Voldemort \' s dark past. ' ,
' description ' : ' As Harry Potter begins his 6th year at Hogwarts School of Witchcraft and Wizardry, he discovers an old book marked mysteriously " This book is the property of the Half-Blood Prince " and begins to learn more about Lord Voldemort \' s dark past. ' ,
' thumbnail ' : ' re:^https?://.* \ .jpeg$ ' ,
' thumbnail ' : ' re:^https?://.* \ .jpeg$ ' ,
' duration ' : 144230 ,
' duration ' : 144.23 ,
' timestamp ' : 1244136834 ,
' timestamp ' : 1244136834 ,
" upload_date " : " 20090604 " ,
}
' upload_date ' : ' 20090604 ' ,
} ,
' params ' : {
# rtmp download
' skip_download ' : True ,
} ,
}
}
PLAYLIST_SERVICE_URL = ' http://production-ps.lvp.llnw.net/r/PlaylistService/media/ %s / %s '
API_URL = ' http://api.video.limelight.com/rest/organizations/ %s /media/ %s / %s .json '
_PLAYLIST_SERVICE_PATH = ' media '
_API_PATH = ' media '
def _real_extract ( self , url ) :
def _real_extract ( self , url ) :
video_id = self . _match_id ( url )
video_id = self . _match_id ( url )
mobile_json_data = self . get_playlist_service ( video_id , ' getMobilePlaylistByMediaId ' )
pc_json_data = self . get_playlist_service ( video_id , ' getPlaylistByMediaId ' )
properties = self . get_api ( pc_json_data [ ' orgId ' ] , video_id , ' properties ' )
pc , mobile , metadata = self . _extract (
video_id , ' getPlaylistByMediaId ' , ' getMobilePlaylistByMediaId ' , ' properties ' )
return self . process_data ( mobile_json_data [ ' mediaList ' ] [ 0 ] [ ' mobileUrls ' ] , pc_json_data [ ' playlistItems ' ] [ 0 ] [ ' streams ' ] , properties )
return self . _extract_info (
pc [ ' playlistItems ' ] [ 0 ] . get ( ' streams ' , [ ] ) ,
mobile [ ' mediaList ' ] [ 0 ] . get ( ' mobileUrls ' , [ ] ) if mobile else [ ] ,
metadata )
class LimeLightChannelIE ( LimeLightBaseIE ) :
class LimelightChannelIE ( Limel ightBaseIE ) :
IE_NAME = ' limelight:channel '
IE_NAME = ' limelight:channel '
_VALID_URL = r ' http://link \ .videoplatform \ .limelight \ .com/media/?.*channelId= (?P<id>[a-z0-9]{32}) '
_VALID_URL = r ' (?:limelight:channel:| http://link\ .videoplatform \ .limelight \ .com/media/ \ ?? \ bchannelId=) (?P<id>[a-z0-9]{32})'
_TEST = {
_TEST = {
' url ' : ' http://link.videoplatform.limelight.com/media/?channelId=ab6a524c379342f9b23642917020c082 ' ,
' url ' : ' http://link.videoplatform.limelight.com/media/?channelId=ab6a524c379342f9b23642917020c082 ' ,
' info_dict ' : {
' info_dict ' : {
@ -120,31 +166,29 @@ class LimeLightChannelIE(LimeLightBaseIE):
} ,
} ,
' playlist_mincount ' : 3 ,
' playlist_mincount ' : 3 ,
}
}
PLAYLIST_SERVICE_URL = ' http://production-ps.lvp.llnw.net/r/PlaylistService/ channel/ %s / %s '
API_URL = ' http://api.video.limelight.com/rest/organizations/ %s / channels/ %s / %s .json '
_PLAYLIST_SERVICE_PATH = ' channel '
_API_PATH = ' channels '
def _real_extract ( self , url ) :
def _real_extract ( self , url ) :
channel_id = self . _match_id ( url )
channel_id = self . _match_id ( url )
mobile_json_data = self . get_playlist_service ( channel_id , ' getMobilePlaylistWithNItemsByChannelId?begin=0&count=-1 ' )
pc_json_data = self . get_playlist_service ( channel_id , ' getPlaylistByChannelId ' )
medias = self . get_api ( pc_json_data [ ' orgId ' ] , channel_id , ' media ' )
pc , mobile , medias = self . _extract (
channel_id , ' getPlaylistByChannelId ' ,
' getMobilePlaylistWithNItemsByChannelId?begin=0&count=-1 ' , ' media ' )
entries = [ ]
for i in range ( len ( medias [ ' media_list ' ] ) ) :
entries . append ( self . process_data ( mobile_json_data [ ' mediaList ' ] [ i ] [ ' mobileUrls ' ] , pc_json_data [ ' playlistItems ' ] [ i ] [ ' streams ' ] , medias [ ' media_list ' ] [ i ] ) )
entries = [
self . _extract_info (
pc [ ' playlistItems ' ] [ i ] . get ( ' streams ' , [ ] ) ,
mobile [ ' mediaList ' ] [ i ] . get ( ' mobileUrls ' , [ ] ) if mobile else [ ] ,
medias [ ' media_list ' ] [ i ] )
for i in range ( len ( medias [ ' media_list ' ] ) ) ]
return {
' id ' : channel_id ,
' title ' : pc_json_data [ ' title ' ] ,
' entries ' : entries ,
' _type ' : ' playlist ' ,
}
return self . playlist_result ( entries , channel_id , pc [ ' title ' ] )
class LimeLightChannelListIE ( LimeL ightBaseIE ) :
class LimelightChannelListIE ( LimelightBaseIE ) :
IE_NAME = ' limelight:channel_list '
IE_NAME = ' limelight:channel_list '
_VALID_URL = r ' http://link \ .videoplatform \ .limelight \ .com/media/?.*channelListId=(?P<id>[a-z0-9]{32}) '
_VALID_URL = r ' (?:limelight:channel_list:|http://link \ .videoplatform \ .limelight \ .com/media/ \ ?.*? \ bchannelListId=)(?P<id>[a-z0-9]{32}) '
_TEST = {
_TEST = {
' url ' : ' http://link.videoplatform.limelight.com/media/?channelListId=301b117890c4465c8179ede21fd92e2b ' ,
' url ' : ' http://link.videoplatform.limelight.com/media/?channelListId=301b117890c4465c8179ede21fd92e2b ' ,
' info_dict ' : {
' info_dict ' : {
@ -153,24 +197,15 @@ class LimeLightChannelListIE(LimeLightBaseIE):
} ,
} ,
' playlist_mincount ' : 2 ,
' playlist_mincount ' : 2 ,
}
}
PLAYLIST_SERVICE_URL = ' http://production-ps.lvp.llnw.net/r/PlaylistService/ channel_list/ %s / %s '
_PLAYLIST_SERVICE_PATH = ' channel_list '
def _real_extract ( self , url ) :
def _real_extract ( self , url ) :
channel_list_id = self . _match_id ( url )
channel_list_id = self . _match_id ( url )
json_data = self . get _playlist_service( channel_list_id , ' getMobileChannelListById ' )
channel_list = self . _call _playlist_service( channel_list_id , ' getMobileChannelListById ' )
entries = [ ]
for channel in json_data [ ' channelList ' ] :
entries . append ( {
' url ' : ' http://link.videoplatform.limelight.com/media/?channelId= %s ' % channel [ ' id ' ] ,
' _type ' : ' url ' ,
' ie_key ' : ' LimeLightChannel ' ,
} )
entries = [
self . url_result ( ' limelight:channel: %s ' % channel [ ' id ' ] , ' LimelightChannel ' )
for channel in channel_list [ ' channelList ' ] ]
return {
' id ' : channel_list_id ,
' title ' : json_data [ ' title ' ] ,
' entries ' : entries ,
' _type ' : ' playlist ' ,
}
return self . playlist_result ( entries , channel_list_id , channel_list [ ' title ' ] )