2014-05-19 13:25:58 +02:00
# coding: utf-8
from __future__ import unicode_literals
import re
2016-08-28 17:43:15 +02:00
from . turner import TurnerBaseIE
2016-08-29 17:40:35 +02:00
from . . utils import (
ExtractorError ,
int_or_none ,
)
2014-05-19 13:25:58 +02:00
2014-11-23 20:41:03 +01:00
2016-08-28 17:43:15 +02:00
class AdultSwimIE ( TurnerBaseIE ) :
2014-12-06 06:49:41 +01:00
_VALID_URL = r ' https?://(?:www \ .)?adultswim \ .com/videos/(?P<is_playlist>playlists/)?(?P<show_path>[^/]+)/(?P<episode_path>[^/?#]+)/? '
_TESTS = [ {
' url ' : ' http://adultswim.com/videos/rick-and-morty/pilot ' ,
2014-05-19 14:32:45 +02:00
' playlist ' : [
2014-12-06 14:01:59 +01:00
{
2014-12-06 06:49:41 +01:00
' md5 ' : ' 247572debc75c7652f253c8daa51a14d ' ,
2014-05-19 14:32:45 +02:00
' info_dict ' : {
2014-12-06 06:49:41 +01:00
' id ' : ' rQxZvXQ4ROaSOqq-or2Mow-0 ' ,
2014-05-19 14:32:45 +02:00
' ext ' : ' flv ' ,
2014-12-06 06:49:41 +01:00
' title ' : ' Rick and Morty - Pilot Part 1 ' ,
' description ' : " Rick moves in with his daughter ' s family and establishes himself as a bad influence on his grandson, Morty. "
} ,
2014-05-19 14:32:45 +02:00
} ,
{
2014-12-06 06:49:41 +01:00
' md5 ' : ' 77b0e037a4b20ec6b98671c4c379f48d ' ,
2014-05-19 14:32:45 +02:00
' info_dict ' : {
2014-12-06 06:49:41 +01:00
' id ' : ' rQxZvXQ4ROaSOqq-or2Mow-3 ' ,
2014-05-19 14:32:45 +02:00
' ext ' : ' flv ' ,
2014-12-06 06:49:41 +01:00
' title ' : ' Rick and Morty - Pilot Part 4 ' ,
' description ' : " Rick moves in with his daughter ' s family and establishes himself as a bad influence on his grandson, Morty. "
} ,
2014-05-19 14:32:45 +02:00
} ,
2014-12-06 06:49:41 +01:00
] ,
' info_dict ' : {
2015-02-18 00:49:10 +01:00
' id ' : ' rQxZvXQ4ROaSOqq-or2Mow ' ,
2014-12-06 06:49:41 +01:00
' title ' : ' Rick and Morty - Pilot ' ,
' description ' : " Rick moves in with his daughter ' s family and establishes himself as a bad influence on his grandson, Morty. "
2015-10-10 13:28:12 +02:00
} ,
' skip ' : ' This video is only available for registered users ' ,
2014-12-06 06:49:41 +01:00
} , {
' url ' : ' http://www.adultswim.com/videos/playlists/american-parenting/putting-francine-out-of-business/ ' ,
' playlist ' : [
2014-05-19 14:32:45 +02:00
{
2014-12-06 06:49:41 +01:00
' md5 ' : ' 2eb5c06d0f9a1539da3718d897f13ec5 ' ,
2014-05-19 14:32:45 +02:00
' info_dict ' : {
2014-12-06 06:49:41 +01:00
' id ' : ' -t8CamQlQ2aYZ49ItZCFog-0 ' ,
2014-05-19 14:32:45 +02:00
' ext ' : ' flv ' ,
2014-12-06 06:49:41 +01:00
' title ' : ' American Dad - Putting Francine Out of Business ' ,
' description ' : ' Stan hatches a plan to get Francine out of the real estate business.Watch more American Dad on [adult swim]. '
} ,
2014-05-19 14:32:45 +02:00
}
2014-12-06 06:49:41 +01:00
] ,
' info_dict ' : {
2015-02-18 00:49:10 +01:00
' id ' : ' -t8CamQlQ2aYZ49ItZCFog ' ,
2014-12-06 06:49:41 +01:00
' title ' : ' American Dad - Putting Francine Out of Business ' ,
' description ' : ' Stan hatches a plan to get Francine out of the real estate business.Watch more American Dad on [adult swim]. '
} ,
2015-03-08 20:32:42 +01:00
} , {
' url ' : ' http://www.adultswim.com/videos/tim-and-eric-awesome-show-great-job/dr-steve-brule-for-your-wine/ ' ,
' playlist ' : [
{
' md5 ' : ' 3e346a2ab0087d687a05e1e7f3b3e529 ' ,
' info_dict ' : {
' id ' : ' sY3cMUR_TbuE4YmdjzbIcQ-0 ' ,
2015-12-21 17:07:19 +01:00
' ext ' : ' mp4 ' ,
2015-03-08 20:32:42 +01:00
' title ' : ' Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine ' ,
' description ' : ' Dr. Brule reports live from Wine Country with a special report on wines. \r \n Watch Tim and Eric Awesome Show Great Job! episode #20, " Embarrassed " on Adult Swim. \r \n \r \n ' ,
} ,
}
] ,
' info_dict ' : {
' id ' : ' sY3cMUR_TbuE4YmdjzbIcQ ' ,
' title ' : ' Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine ' ,
' description ' : ' Dr. Brule reports live from Wine Country with a special report on wines. \r \n Watch Tim and Eric Awesome Show Great Job! episode #20, " Embarrassed " on Adult Swim. \r \n \r \n ' ,
} ,
2015-12-21 17:07:19 +01:00
' params ' : {
# m3u8 download
' skip_download ' : True ,
}
2016-08-05 19:00:05 +02:00
} , {
# heroMetadata.trailer
' url ' : ' http://www.adultswim.com/videos/decker/inside-decker-a-new-hero/ ' ,
' info_dict ' : {
' id ' : ' I0LQFQkaSUaFp8PnAWHhoQ ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Decker - Inside Decker: A New Hero ' ,
' description ' : ' md5:c916df071d425d62d70c86d4399d3ee0 ' ,
' duration ' : 249.008 ,
} ,
' params ' : {
# m3u8 download
' skip_download ' : True ,
2016-08-28 17:43:15 +02:00
} ,
' expected_warnings ' : [ ' Unable to download f4m manifest ' ] ,
2014-12-06 06:49:41 +01:00
} ]
@staticmethod
def find_video_info ( collection , slug ) :
for video in collection . get ( ' videos ' ) :
if video . get ( ' slug ' ) == slug :
2015-10-10 18:57:05 +02:00
return video
2014-12-06 06:49:41 +01:00
@staticmethod
def find_collection_by_linkURL ( collections , linkURL ) :
for collection in collections :
if collection . get ( ' linkURL ' ) == linkURL :
return collection
@staticmethod
def find_collection_containing_video ( collections , slug ) :
for collection in collections :
for video in collection . get ( ' videos ' ) :
if video . get ( ' slug ' ) == slug :
2015-10-10 18:57:05 +02:00
return collection , video
2015-03-08 20:32:42 +01:00
return None , None
2014-05-19 13:25:58 +02:00
def _real_extract ( self , url ) :
mobj = re . match ( self . _VALID_URL , url )
2014-12-06 06:49:41 +01:00
show_path = mobj . group ( ' show_path ' )
episode_path = mobj . group ( ' episode_path ' )
is_playlist = True if mobj . group ( ' is_playlist ' ) else False
webpage = self . _download_webpage ( url , episode_path )
# Extract the value of `bootstrappedData` from the Javascript in the page.
2015-03-08 20:32:42 +01:00
bootstrapped_data = self . _parse_json ( self . _search_regex (
r ' var bootstrappedData = ( { .*}); ' , webpage , ' bootstraped data ' ) , episode_path )
2014-12-06 06:49:41 +01:00
# Downloading videos from a /videos/playlist/ URL needs to be handled differently.
# NOTE: We are only downloading one video (the current one) not the playlist
if is_playlist :
2015-03-08 20:32:42 +01:00
collections = bootstrapped_data [ ' playlists ' ] [ ' collections ' ]
2014-12-06 06:49:41 +01:00
collection = self . find_collection_by_linkURL ( collections , show_path )
video_info = self . find_video_info ( collection , episode_path )
show_title = video_info [ ' showTitle ' ]
segment_ids = [ video_info [ ' videoPlaybackID ' ] ]
else :
2015-03-08 20:32:42 +01:00
collections = bootstrapped_data [ ' show ' ] [ ' collections ' ]
2014-12-06 06:49:41 +01:00
collection , video_info = self . find_collection_containing_video ( collections , episode_path )
2015-03-08 20:32:42 +01:00
# Video wasn't found in the collections, let's try `slugged_video`.
if video_info is None :
if bootstrapped_data . get ( ' slugged_video ' , { } ) . get ( ' slug ' ) == episode_path :
video_info = bootstrapped_data [ ' slugged_video ' ]
2016-08-05 19:00:05 +02:00
if not video_info :
2016-08-29 17:40:35 +02:00
video_info = bootstrapped_data . get (
' heroMetadata ' , { } ) . get ( ' trailer ' , { } ) . get ( ' video ' )
if not video_info :
video_info = bootstrapped_data . get ( ' onlineOriginals ' , [ None ] ) [ 0 ]
2016-08-05 19:00:05 +02:00
if not video_info :
raise ExtractorError ( ' Unable to find video info ' )
2015-03-08 20:32:42 +01:00
show = bootstrapped_data [ ' show ' ]
2014-12-06 06:49:41 +01:00
show_title = show [ ' title ' ]
2015-09-29 17:33:21 +02:00
stream = video_info . get ( ' stream ' )
2016-08-05 19:00:05 +02:00
if stream and stream . get ( ' videoPlaybackID ' ) :
segment_ids = [ stream [ ' videoPlaybackID ' ] ]
elif video_info . get ( ' clips ' ) :
segment_ids = [ clip [ ' videoPlaybackID ' ] for clip in video_info [ ' clips ' ] ]
elif video_info . get ( ' videoPlaybackID ' ) :
segment_ids = [ video_info [ ' videoPlaybackID ' ] ]
else :
2016-08-25 10:55:23 +02:00
if video_info . get ( ' auth ' ) is True :
raise ExtractorError (
' This video is only available via cable service provider subscription that '
' is not currently supported. You may want to use --cookies. ' , expected = True )
else :
raise ExtractorError ( ' Unable to find stream or clips ' )
2014-12-06 06:49:41 +01:00
episode_id = video_info [ ' id ' ]
episode_title = video_info [ ' title ' ]
2016-08-29 17:40:35 +02:00
episode_description = video_info . get ( ' description ' )
episode_duration = int_or_none ( video_info . get ( ' duration ' ) )
view_count = int_or_none ( video_info . get ( ' views ' ) )
2014-05-19 13:25:58 +02:00
entries = [ ]
2014-12-06 06:49:41 +01:00
for part_num , segment_id in enumerate ( segment_ids ) :
2016-08-28 17:43:15 +02:00
segement_info = self . _extract_cvp_info (
' http://www.adultswim.com/videos/api/v0/assets?id= %s &platform=desktop ' % segment_id ,
segment_id , {
' secure ' : {
' media_src ' : ' http://androidhls-secure.cdn.turner.com/adultswim/big ' ,
' tokenizer_src ' : ' http://www.adultswim.com/astv/mvpd/processors/services/token_ipadAdobe.do ' ,
} ,
} )
2014-12-06 06:49:41 +01:00
segment_title = ' %s - %s ' % ( show_title , episode_title )
if len ( segment_ids ) > 1 :
segment_title + = ' Part %d ' % ( part_num + 1 )
2016-08-28 17:43:15 +02:00
segement_info . update ( {
2014-05-19 13:25:58 +02:00
' id ' : segment_id ,
' title ' : segment_title ,
2016-08-28 17:43:15 +02:00
' description ' : episode_description ,
2014-05-19 13:25:58 +02:00
} )
2016-08-28 17:43:15 +02:00
entries . append ( segement_info )
2014-05-19 13:25:58 +02:00
return {
' _type ' : ' playlist ' ,
' id ' : episode_id ,
2014-12-06 06:49:41 +01:00
' display_id ' : episode_path ,
2014-05-19 13:25:58 +02:00
' entries ' : entries ,
2014-12-06 06:49:41 +01:00
' title ' : ' %s - %s ' % ( show_title , episode_title ) ,
' description ' : episode_description ,
2016-08-29 17:40:35 +02:00
' duration ' : episode_duration ,
' view_count ' : view_count ,
2014-05-19 13:25:58 +02:00
}