2015-10-18 04:07:48 -05:00
# coding: utf-8
2015-06-27 14:22:25 -05:00
from __future__ import unicode_literals
import re
from . common import InfoExtractor
2016-09-24 06:57:55 -05:00
from . . compat import compat_urlparse
2015-06-27 14:22:25 -05:00
from . . utils import (
2016-06-08 08:28:10 -05:00
determine_ext ,
2017-06-03 11:41:55 -05:00
dict_get ,
ExtractorError ,
2015-06-27 14:22:25 -05:00
float_or_none ,
2015-11-11 12:13:42 -06:00
int_or_none ,
2017-06-03 11:41:55 -05:00
remove_end ,
try_get ,
xpath_text ,
2015-06-27 14:22:25 -05:00
)
2016-09-24 06:57:55 -05:00
from . periscope import PeriscopeIE
2015-06-27 14:22:25 -05:00
2016-02-21 02:41:24 -06:00
class TwitterBaseIE ( InfoExtractor ) :
2017-07-11 02:48:34 -05:00
def _extract_formats_from_vmap_url ( self , vmap_url , video_id ) :
2016-02-21 02:41:24 -06:00
vmap_data = self . _download_xml ( vmap_url , video_id )
2017-07-11 02:48:34 -05:00
video_url = xpath_text ( vmap_data , ' .//MediaFile ' ) . strip ( )
if determine_ext ( video_url ) == ' m3u8 ' :
return self . _extract_m3u8_formats (
video_url , video_id , ext = ' mp4 ' , m3u8_id = ' hls ' ,
entry_protocol = ' m3u8_native ' )
return [ {
' url ' : video_url ,
} ]
2016-02-21 02:41:24 -06:00
2017-06-03 11:41:55 -05:00
@staticmethod
def _search_dimensions_in_video_url ( a_format , video_url ) :
m = re . search ( r ' /(?P<width> \ d+)x(?P<height> \ d+)/ ' , video_url )
if m :
a_format . update ( {
' width ' : int ( m . group ( ' width ' ) ) ,
' height ' : int ( m . group ( ' height ' ) ) ,
} )
2016-02-21 02:41:24 -06:00
class TwitterCardIE ( TwitterBaseIE ) :
2015-10-18 04:13:58 -05:00
IE_NAME = ' twitter:card '
2017-12-10 07:10:52 -06:00
_VALID_URL = r ' https?://(?:www \ .)?twitter \ .com/i/(?P<path>cards/tfw/v1|videos(?:/tweet)?)/(?P<id> \ d+) '
2015-07-21 16:45:36 -05:00
_TESTS = [
{
' url ' : ' https://twitter.com/i/cards/tfw/v1/560070183650213889 ' ,
2016-02-21 02:57:56 -06:00
# MD5 checksums are different in different places
2015-07-21 16:45:36 -05:00
' info_dict ' : {
' id ' : ' 560070183650213889 ' ,
' ext ' : ' mp4 ' ,
2017-12-10 07:10:52 -06:00
' title ' : ' Twitter web player ' ,
2017-01-02 06:08:07 -06:00
' thumbnail ' : r ' re:^https?://.* \ .jpg$ ' ,
2015-07-21 16:45:36 -05:00
' duration ' : 30.033 ,
2017-07-11 03:46:37 -05:00
} ,
2015-06-27 14:22:25 -05:00
} ,
2015-07-21 16:45:36 -05:00
{
' url ' : ' https://twitter.com/i/cards/tfw/v1/623160978427936768 ' ,
' md5 ' : ' 7ee2a553b63d1bccba97fbed97d9e1c8 ' ,
' info_dict ' : {
' id ' : ' 623160978427936768 ' ,
' ext ' : ' mp4 ' ,
2017-12-10 07:10:52 -06:00
' title ' : ' Twitter web player ' ,
2018-05-31 17:16:00 -05:00
' thumbnail ' : r ' re:^https?://.*$ ' ,
2015-07-21 16:45:36 -05:00
} ,
2015-10-18 06:07:37 -05:00
} ,
{
' url ' : ' https://twitter.com/i/cards/tfw/v1/654001591733886977 ' ,
2016-09-24 06:57:55 -05:00
' md5 ' : ' b6d9683dd3f48e340ded81c0e917ad46 ' ,
2015-10-18 06:07:37 -05:00
' info_dict ' : {
' id ' : ' dq4Oj5quskI ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Ubuntu 11.10 Overview ' ,
2016-09-24 06:57:55 -05:00
' description ' : ' md5:a831e97fa384863d6e26ce48d1c43376 ' ,
2015-10-18 06:07:37 -05:00
' upload_date ' : ' 20111013 ' ,
' uploader ' : ' OMG! Ubuntu! ' ,
' uploader_id ' : ' omgubuntu ' ,
} ,
2015-11-14 03:03:26 -06:00
' add_ie ' : [ ' Youtube ' ] ,
2015-11-14 03:02:07 -06:00
} ,
{
' url ' : ' https://twitter.com/i/cards/tfw/v1/665289828897005568 ' ,
2017-07-11 03:46:37 -05:00
' md5 ' : ' 6dabeaca9e68cbb71c99c322a4b42a11 ' ,
2015-11-14 03:02:07 -06:00
' info_dict ' : {
' id ' : ' iBb2x00UVlv ' ,
' ext ' : ' mp4 ' ,
' upload_date ' : ' 20151113 ' ,
' uploader_id ' : ' 1189339351084113920 ' ,
2016-02-21 02:57:56 -06:00
' uploader ' : ' ArsenalTerje ' ,
' title ' : ' Vine by ArsenalTerje ' ,
2017-07-11 03:05:15 -05:00
' timestamp ' : 1447451307 ,
2015-11-14 03:02:07 -06:00
} ,
' add_ie ' : [ ' Vine ' ] ,
2016-03-02 23:39:04 -06:00
} , {
' url ' : ' https://twitter.com/i/videos/tweet/705235433198714880 ' ,
2017-07-11 03:46:37 -05:00
' md5 ' : ' 884812a2adc8aaf6fe52b15ccbfa3b88 ' ,
2016-03-02 23:39:04 -06:00
' info_dict ' : {
' id ' : ' 705235433198714880 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Twitter web player ' ,
2017-07-11 03:46:37 -05:00
' thumbnail ' : r ' re:^https?://.* ' ,
2016-03-02 23:39:04 -06:00
} ,
2016-11-18 12:49:13 -06:00
} , {
' url ' : ' https://twitter.com/i/videos/752274308186120192 ' ,
' only_matching ' : True ,
2016-03-02 23:39:04 -06:00
} ,
2015-07-21 16:45:36 -05:00
]
2015-06-27 14:22:25 -05:00
2018-06-03 03:58:12 -05:00
_API_BASE = ' https://api.twitter.com/1.1 '
2017-06-03 11:41:55 -05:00
def _parse_media_info ( self , media_info , video_id ) :
formats = [ ]
for media_variant in media_info . get ( ' variants ' , [ ] ) :
media_url = media_variant [ ' url ' ]
if media_url . endswith ( ' .m3u8 ' ) :
formats . extend ( self . _extract_m3u8_formats ( media_url , video_id , ext = ' mp4 ' , m3u8_id = ' hls ' ) )
elif media_url . endswith ( ' .mpd ' ) :
formats . extend ( self . _extract_mpd_formats ( media_url , video_id , mpd_id = ' dash ' ) )
else :
2017-12-10 07:10:52 -06:00
tbr = int_or_none ( dict_get ( media_variant , ( ' bitRate ' , ' bitrate ' ) ) , scale = 1000 )
2017-06-03 11:41:55 -05:00
a_format = {
' url ' : media_url ,
2017-12-10 07:10:52 -06:00
' format_id ' : ' http- %d ' % tbr if tbr else ' http ' ,
' tbr ' : tbr ,
2017-06-03 11:41:55 -05:00
}
# Reported bitRate may be zero
2017-12-10 07:10:52 -06:00
if not a_format [ ' tbr ' ] :
del a_format [ ' tbr ' ]
2017-06-03 11:41:55 -05:00
self . _search_dimensions_in_video_url ( a_format , media_url )
formats . append ( a_format )
return formats
def _extract_mobile_formats ( self , username , video_id ) :
webpage = self . _download_webpage (
' https://mobile.twitter.com/ %s /status/ %s ' % ( username , video_id ) ,
video_id , ' Downloading mobile webpage ' ,
headers = {
# A recent mobile UA is necessary for `gt` cookie
' User-Agent ' : ' Mozilla/5.0 (Android 6.0.1; Mobile; rv:54.0) Gecko/54.0 Firefox/54.0 ' ,
} )
main_script_url = self . _html_search_regex (
r ' <script[^>]+src= " ([^ " ]+main \ .[^ " ]+) " ' , webpage , ' main script URL ' )
main_script = self . _download_webpage (
main_script_url , video_id , ' Downloading main script ' )
bearer_token = self . _search_regex (
r ' BEARER_TOKEN \ s*: \ s* " ([^ " ]+) " ' ,
main_script , ' bearer token ' )
2017-12-10 07:10:52 -06:00
# https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id
2017-06-03 11:41:55 -05:00
api_data = self . _download_json (
2018-06-03 03:58:12 -05:00
' %s /statuses/show/ %s .json ' % ( self . _API_BASE , video_id ) ,
2017-12-10 07:10:52 -06:00
video_id , ' Downloading API data ' ,
2017-06-03 11:41:55 -05:00
headers = {
' Authorization ' : ' Bearer ' + bearer_token ,
} )
2017-12-10 07:10:52 -06:00
media_info = try_get ( api_data , lambda o : o [ ' extended_entities ' ] [ ' media ' ] [ 0 ] [ ' video_info ' ] ) or { }
2017-06-03 11:41:55 -05:00
return self . _parse_media_info ( media_info , video_id )
2015-06-27 14:22:25 -05:00
def _real_extract ( self , url ) :
2017-12-10 07:10:52 -06:00
path , video_id = re . search ( self . _VALID_URL , url ) . groups ( )
2015-06-27 14:22:25 -05:00
config = None
formats = [ ]
2016-03-03 00:24:24 -06:00
duration = None
2017-12-10 07:10:52 -06:00
urls = [ url ]
if path . startswith ( ' cards/ ' ) :
urls . append ( ' https://twitter.com/i/videos/ ' + video_id )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
for u in urls :
2018-12-18 15:46:19 -06:00
webpage = self . _download_webpage (
u , video_id , headers = { ' Referer ' : ' https://twitter.com/ ' } )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
iframe_url = self . _html_search_regex (
r ' <iframe[^>]+src= " ((?:https?:)?//(?:www \ .youtube \ .com/embed/[^ " ]+|(?:www \ .)?vine \ .co/v/ \ w+/card)) " ' ,
webpage , ' video iframe ' , default = None )
if iframe_url :
return self . url_result ( iframe_url )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
config = self . _parse_json ( self . _html_search_regex (
r ' data-(?:player-)?config= " ([^ " ]+) " ' , webpage ,
' data player config ' , default = ' {} ' ) ,
video_id )
2016-03-26 15:36:02 -05:00
2017-12-10 07:10:52 -06:00
if config . get ( ' source_type ' ) == ' vine ' :
return self . url_result ( config [ ' player_url ' ] , ' Vine ' )
2016-09-24 06:57:55 -05:00
2017-12-10 07:10:52 -06:00
periscope_url = PeriscopeIE . _extract_url ( webpage )
if periscope_url :
return self . url_result ( periscope_url , PeriscopeIE . ie_key ( ) )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
video_url = config . get ( ' video_url ' ) or config . get ( ' playlist ' , [ { } ] ) [ 0 ] . get ( ' source ' )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
if video_url :
if determine_ext ( video_url ) == ' m3u8 ' :
formats . extend ( self . _extract_m3u8_formats ( video_url , video_id , ext = ' mp4 ' , m3u8_id = ' hls ' ) )
else :
f = {
' url ' : video_url ,
}
2016-03-05 04:14:58 -06:00
2017-12-10 07:10:52 -06:00
self . _search_dimensions_in_video_url ( f , video_url )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
formats . append ( f )
2016-03-03 00:27:27 -06:00
2017-12-10 07:10:52 -06:00
vmap_url = config . get ( ' vmapUrl ' ) or config . get ( ' vmap_url ' )
if vmap_url :
formats . extend (
self . _extract_formats_from_vmap_url ( vmap_url , video_id ) )
2016-03-03 00:42:49 -06:00
2017-12-10 07:10:52 -06:00
media_info = None
2016-03-03 00:42:49 -06:00
2017-12-10 07:10:52 -06:00
for entity in config . get ( ' status ' , { } ) . get ( ' entities ' , [ ] ) :
if ' mediaInfo ' in entity :
media_info = entity [ ' mediaInfo ' ]
2016-03-03 00:24:24 -06:00
2017-12-10 07:10:52 -06:00
if media_info :
formats . extend ( self . _parse_media_info ( media_info , video_id ) )
duration = float_or_none ( media_info . get ( ' duration ' , { } ) . get ( ' nanos ' ) , scale = 1e9 )
username = config . get ( ' user ' , { } ) . get ( ' screen_name ' )
if username :
formats . extend ( self . _extract_mobile_formats ( username , video_id ) )
if formats :
2018-05-31 17:16:00 -05:00
title = self . _search_regex ( r ' <title>([^<]+)</title> ' , webpage , ' title ' )
thumbnail = config . get ( ' posterImageUrl ' ) or config . get ( ' image_src ' )
duration = float_or_none ( config . get ( ' duration ' ) , scale = 1000 ) or duration
2017-12-10 07:10:52 -06:00
break
2017-06-03 11:41:55 -05:00
2018-05-31 17:16:00 -05:00
if not formats :
2018-06-03 03:53:20 -05:00
headers = {
' Authorization ' : ' Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h %2F 40K4moUkGsoc % 3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw ' ,
' Referer ' : url ,
}
ct0 = self . _get_cookies ( url ) . get ( ' ct0 ' )
if ct0 :
headers [ ' csrf_token ' ] = ct0 . value
guest_token = self . _download_json (
2018-06-03 03:58:12 -05:00
' %s /guest/activate.json ' % self . _API_BASE , video_id ,
2018-06-03 03:53:20 -05:00
' Downloading guest token ' , data = b ' ' ,
headers = headers ) [ ' guest_token ' ]
headers [ ' x-guest-token ' ] = guest_token
self . _set_cookie ( ' api.twitter.com ' , ' gt ' , guest_token )
2018-05-31 17:16:00 -05:00
config = self . _download_json (
2018-06-03 03:58:12 -05:00
' %s /videos/tweet/config/ %s .json ' % ( self . _API_BASE , video_id ) ,
2018-06-03 03:53:20 -05:00
video_id , headers = headers )
2018-05-31 17:16:00 -05:00
track = config [ ' track ' ]
vmap_url = track . get ( ' vmapUrl ' )
if vmap_url :
formats = self . _extract_formats_from_vmap_url ( vmap_url , video_id )
else :
playback_url = track [ ' playbackUrl ' ]
if determine_ext ( playback_url ) == ' m3u8 ' :
formats = self . _extract_m3u8_formats (
playback_url , video_id , ' mp4 ' ,
entry_protocol = ' m3u8_native ' , m3u8_id = ' hls ' )
else :
formats = [ {
' url ' : playback_url ,
} ]
title = ' Twitter web player '
thumbnail = config . get ( ' posterImage ' )
duration = float_or_none ( track . get ( ' durationMs ' ) , scale = 1000 )
2017-06-03 11:41:55 -05:00
self . _remove_duplicate_formats ( formats )
2015-06-27 14:22:25 -05:00
self . _sort_formats ( formats )
return {
' id ' : video_id ,
2016-03-02 23:39:04 -06:00
' title ' : title ,
2015-06-27 14:22:25 -05:00
' thumbnail ' : thumbnail ,
' duration ' : duration ,
' formats ' : formats ,
}
2015-07-21 16:38:40 -05:00
2015-10-18 04:16:57 -05:00
class TwitterIE ( InfoExtractor ) :
2015-10-18 04:13:58 -05:00
IE_NAME = ' twitter '
2017-09-22 18:38:09 -05:00
_VALID_URL = r ' https?://(?:www \ .|m \ .|mobile \ .)?twitter \ .com/(?:i/web|(?P<user_id>[^/]+))/status/(?P<id> \ d+) '
2015-10-18 05:04:13 -05:00
_TEMPLATE_URL = ' https://twitter.com/ %s /status/ %s '
2017-09-22 18:38:09 -05:00
_TEMPLATE_STATUSES_URL = ' https://twitter.com/statuses/ %s '
2015-07-21 16:38:40 -05:00
2015-11-11 12:13:42 -06:00
_TESTS = [ {
2015-10-18 04:07:48 -05:00
' url ' : ' https://twitter.com/freethenipple/status/643211948184596480 ' ,
2015-07-21 16:38:40 -05:00
' info_dict ' : {
2015-10-18 04:07:48 -05:00
' id ' : ' 643211948184596480 ' ,
2015-07-21 16:38:40 -05:00
' ext ' : ' mp4 ' ,
2015-10-18 05:04:13 -05:00
' title ' : ' FREE THE NIPPLE - FTN supporters on Hollywood Blvd today! ' ,
2017-01-02 06:08:07 -06:00
' thumbnail ' : r ' re:^https?://.* \ .jpg ' ,
2015-10-18 04:07:48 -05:00
' description ' : ' FREE THE NIPPLE on Twitter: " FTN supporters on Hollywood Blvd today! http://t.co/c7jHH749xJ " ' ,
' uploader ' : ' FREE THE NIPPLE ' ,
' uploader_id ' : ' freethenipple ' ,
2017-09-19 15:58:06 -05:00
' duration ' : 12.922 ,
2015-07-21 16:38:40 -05:00
} ,
2015-11-11 12:13:42 -06:00
} , {
' url ' : ' https://twitter.com/giphz/status/657991469417025536/photo/1 ' ,
' md5 ' : ' f36dcd5fb92bf7057f155e7d927eeb42 ' ,
' info_dict ' : {
' id ' : ' 657991469417025536 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Gifs - tu vai cai tu vai cai tu nao eh capaz disso tu vai cai ' ,
' description ' : ' Gifs on Twitter: " tu vai cai tu vai cai tu nao eh capaz disso tu vai cai https://t.co/tM46VHFlO5 " ' ,
2017-01-02 06:08:07 -06:00
' thumbnail ' : r ' re:^https?://.* \ .png ' ,
2015-11-11 12:13:42 -06:00
' uploader ' : ' Gifs ' ,
' uploader_id ' : ' giphz ' ,
} ,
2016-02-21 03:29:28 -06:00
' expected_warnings ' : [ ' height ' , ' width ' ] ,
2016-06-08 08:12:14 -05:00
' skip ' : ' Account suspended ' ,
2015-11-13 12:09:42 -06:00
} , {
' url ' : ' https://twitter.com/starwars/status/665052190608723968 ' ,
' info_dict ' : {
' id ' : ' 665052190608723968 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Star Wars - A new beginning is coming December 18. Watch the official 60 second #TV spot for #StarWars: #TheForceAwakens. ' ,
' description ' : ' Star Wars on Twitter: " A new beginning is coming December 18. Watch the official 60 second #TV spot for #StarWars: #TheForceAwakens. " ' ,
' uploader_id ' : ' starwars ' ,
' uploader ' : ' Star Wars ' ,
} ,
2016-03-02 23:39:04 -06:00
} , {
' url ' : ' https://twitter.com/BTNBrentYarina/status/705235433198714880 ' ,
' info_dict ' : {
' id ' : ' 705235433198714880 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Brent Yarina - Khalil Iverson \' s missed highlight dunk. And made highlight dunk. In one highlight. ' ,
' description ' : ' Brent Yarina on Twitter: " Khalil Iverson \' s missed highlight dunk. And made highlight dunk. In one highlight. " ' ,
' uploader_id ' : ' BTNBrentYarina ' ,
' uploader ' : ' Brent Yarina ' ,
} ,
' params ' : {
# The same video as https://twitter.com/i/videos/tweet/705235433198714880
# Test case of TwitterCardIE
' skip_download ' : True ,
} ,
2016-03-03 00:42:49 -06:00
} , {
' url ' : ' https://twitter.com/jaydingeer/status/700207533655363584 ' ,
' info_dict ' : {
' id ' : ' 700207533655363584 ' ,
' ext ' : ' mp4 ' ,
2017-12-10 07:10:52 -06:00
' title ' : ' JG - BEAT PROD: @suhmeduh #Damndaniel ' ,
' description ' : ' JG on Twitter: " BEAT PROD: @suhmeduh https://t.co/HBrQ4AfpvZ #Damndaniel https://t.co/byBooq2ejZ " ' ,
2017-01-02 06:08:07 -06:00
' thumbnail ' : r ' re:^https?://.* \ .jpg ' ,
2017-12-10 07:10:52 -06:00
' uploader ' : ' JG ' ,
2016-03-03 00:42:49 -06:00
' uploader_id ' : ' jaydingeer ' ,
2017-09-19 15:58:06 -05:00
' duration ' : 30.0 ,
2016-03-03 00:42:49 -06:00
} ,
2016-03-26 15:36:02 -05:00
} , {
' url ' : ' https://twitter.com/Filmdrunk/status/713801302971588609 ' ,
' md5 ' : ' 89a15ed345d13b86e9a5a5e051fa308a ' ,
' info_dict ' : {
' id ' : ' MIOxnrUteUd ' ,
' ext ' : ' mp4 ' ,
2017-09-22 18:38:09 -05:00
' title ' : ' Vince Mancini - Vine of the day ' ,
' description ' : ' Vince Mancini on Twitter: " Vine of the day https://t.co/xmTvRdqxWf " ' ,
' uploader ' : ' Vince Mancini ' ,
2017-07-11 03:46:37 -05:00
' uploader_id ' : ' Filmdrunk ' ,
' timestamp ' : 1402826626 ,
2016-03-26 15:36:02 -05:00
' upload_date ' : ' 20140615 ' ,
} ,
' add_ie ' : [ ' Vine ' ] ,
2016-04-16 05:57:50 -05:00
} , {
' url ' : ' https://twitter.com/captainamerica/status/719944021058060289 ' ,
' info_dict ' : {
' id ' : ' 719944021058060289 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Captain America - @King0fNerd Are you sure you made the right choice? Find out in theaters. ' ,
' description ' : ' Captain America on Twitter: " @King0fNerd Are you sure you made the right choice? Find out in theaters. https://t.co/GpgYi9xMJI " ' ,
' uploader_id ' : ' captainamerica ' ,
' uploader ' : ' Captain America ' ,
2017-09-19 15:58:06 -05:00
' duration ' : 3.17 ,
2016-04-16 05:57:50 -05:00
} ,
2016-09-24 06:57:55 -05:00
} , {
' url ' : ' https://twitter.com/OPP_HSD/status/779210622571536384 ' ,
' info_dict ' : {
' id ' : ' 1zqKVVlkqLaKB ' ,
' ext ' : ' mp4 ' ,
2017-07-11 02:35:19 -05:00
' title ' : ' Sgt Kerry Schmidt - LIVE on #Periscope: Road rage, mischief, assault, rollover and fire in one occurrence ' ,
' description ' : ' Sgt Kerry Schmidt on Twitter: " LIVE on #Periscope: Road rage, mischief, assault, rollover and fire in one occurrence https://t.co/EKrVgIXF3s " ' ,
2016-09-24 06:57:55 -05:00
' upload_date ' : ' 20160923 ' ,
' uploader_id ' : ' OPP_HSD ' ,
2017-07-11 02:35:19 -05:00
' uploader ' : ' Sgt Kerry Schmidt ' ,
2016-09-24 06:57:55 -05:00
' timestamp ' : 1474613214 ,
} ,
' add_ie ' : [ ' Periscope ' ] ,
2017-06-03 11:41:55 -05:00
} , {
# has mp4 formats via mobile API
' url ' : ' https://twitter.com/news_al3alm/status/852138619213144067 ' ,
' info_dict ' : {
' id ' : ' 852138619213144067 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' عالم الأخبار - كلمة تاريخية بجلسة الجناسي التاريخية.. النائب خالد مؤنس العتيبي للمعارضين : اتقوا الله .. الظلم ظلمات يوم القيامة ' ,
' description ' : ' عالم الأخبار on Twitter: " كلمة تاريخية بجلسة الجناسي التاريخية.. النائب خالد مؤنس العتيبي للمعارضين : اتقوا الله .. الظلم ظلمات يوم القيامة https://t.co/xg6OhpyKfN " ' ,
' uploader ' : ' عالم الأخبار ' ,
' uploader_id ' : ' news_al3alm ' ,
2017-09-19 15:58:06 -05:00
' duration ' : 277.4 ,
2017-06-03 11:41:55 -05:00
} ,
2017-09-22 18:38:09 -05:00
} , {
' url ' : ' https://twitter.com/i/web/status/910031516746514432 ' ,
' info_dict ' : {
' id ' : ' 910031516746514432 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Préfet de Guadeloupe - [Direct] #Maria Le centre se trouve actuellement au sud de Basse-Terre. Restez confinés. Réfugiez-vous dans la pièce la + sûre. ' ,
' thumbnail ' : r ' re:^https?://.* \ .jpg ' ,
' description ' : ' Préfet de Guadeloupe on Twitter: " [Direct] #Maria Le centre se trouve actuellement au sud de Basse-Terre. Restez confinés. Réfugiez-vous dans la pièce la + sûre. https://t.co/mwx01Rs4lo " ' ,
' uploader ' : ' Préfet de Guadeloupe ' ,
' uploader_id ' : ' Prefet971 ' ,
' duration ' : 47.48 ,
} ,
' params ' : {
' skip_download ' : True , # requires ffmpeg
} ,
2018-05-31 17:16:00 -05:00
} , {
# card via api.twitter.com/1.1/videos/tweet/config
' url ' : ' https://twitter.com/LisPower1/status/1001551623938805763 ' ,
' info_dict ' : {
' id ' : ' 1001551623938805763 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' re:.*?Shep is on a roll today.*? ' ,
' thumbnail ' : r ' re:^https?://.* \ .jpg ' ,
' description ' : ' md5:63b036c228772523ae1924d5f8e5ed6b ' ,
' uploader ' : ' Lis Power ' ,
' uploader_id ' : ' LisPower1 ' ,
' duration ' : 111.278 ,
} ,
' params ' : {
' skip_download ' : True , # requires ffmpeg
} ,
2019-07-13 13:19:17 -05:00
} , {
' url ' : ' https://twitter.com/foobar/status/1087791357756956680 ' ,
' info_dict ' : {
' id ' : ' 1087791357756956680 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Twitter - A new is coming. Some of you got an opt-in to try it now. Check out the emoji button, quick keyboard shortcuts, upgraded trends, advanced search, and more. Let us know your thoughts! ' ,
' thumbnail ' : r ' re:^https?://.* \ .jpg ' ,
' description ' : ' md5:66d493500c013e3e2d434195746a7f78 ' ,
' uploader ' : ' Twitter ' ,
' uploader_id ' : ' Twitter ' ,
' duration ' : 61.567 ,
} ,
2015-11-11 12:13:42 -06:00
} ]
2015-07-21 16:38:40 -05:00
def _real_extract ( self , url ) :
2015-10-18 05:04:13 -05:00
mobj = re . match ( self . _VALID_URL , url )
twid = mobj . group ( ' id ' )
2016-06-08 08:12:14 -05:00
webpage , urlh = self . _download_webpage_handle (
2017-09-22 18:38:09 -05:00
self . _TEMPLATE_STATUSES_URL % twid , twid )
2016-06-08 08:12:14 -05:00
if ' twitter.com/account/suspended ' in urlh . geturl ( ) :
raise ExtractorError ( ' Account suspended by Twitter. ' , expected = True )
2015-10-18 05:04:13 -05:00
2019-07-13 13:19:17 -05:00
user_id = None
redirect_mobj = re . match ( self . _VALID_URL , urlh . geturl ( ) )
if redirect_mobj :
user_id = redirect_mobj . group ( ' user_id ' )
if not user_id :
2017-09-22 18:38:09 -05:00
user_id = mobj . group ( ' user_id ' )
2015-10-18 05:04:13 -05:00
username = remove_end ( self . _og_search_title ( webpage ) , ' on Twitter ' )
2015-11-13 12:09:42 -06:00
title = description = self . _og_search_description ( webpage ) . strip ( ' ' ) . replace ( ' \n ' , ' ' ) . strip ( ' “” ' )
2015-10-18 05:04:13 -05:00
# strip 'https -_t.co_BJYgOjSeGA' junk from filenames
2015-11-13 12:09:42 -06:00
title = re . sub ( r ' \ s+(https?://[^ ]+) ' , ' ' , title )
2015-10-18 05:04:13 -05:00
2015-11-11 12:13:42 -06:00
info = {
2015-10-18 05:04:13 -05:00
' uploader_id ' : user_id ,
' uploader ' : username ,
2015-07-21 16:38:40 -05:00
' webpage_url ' : url ,
2015-11-13 12:09:42 -06:00
' description ' : ' %s on Twitter: " %s " ' % ( username , description ) ,
2015-07-21 16:38:40 -05:00
' title ' : username + ' - ' + title ,
}
2015-11-11 12:13:42 -06:00
mobj = re . search ( r ''' (?x)
2016-02-21 03:29:28 -06:00
< video [ ^ > ] + class = " animated-gif " ( ? P < more_info > [ ^ > ] + ) > \s *
2015-11-11 12:13:42 -06:00
< source [ ^ > ] + video - src = " (?P<url>[^ " ] + ) "
''' , webpage)
if mobj :
2016-02-21 03:29:28 -06:00
more_info = mobj . group ( ' more_info ' )
height = int_or_none ( self . _search_regex (
r ' data-height= " ( \ d+) " ' , more_info , ' height ' , fatal = False ) )
width = int_or_none ( self . _search_regex (
r ' data-width= " ( \ d+) " ' , more_info , ' width ' , fatal = False ) )
thumbnail = self . _search_regex (
r ' poster= " ([^ " ]+) " ' , more_info , ' poster ' , fatal = False )
2015-11-11 12:13:42 -06:00
info . update ( {
' id ' : twid ,
' url ' : mobj . group ( ' url ' ) ,
2016-02-21 03:29:28 -06:00
' height ' : height ,
' width ' : width ,
' thumbnail ' : thumbnail ,
2015-11-11 12:13:42 -06:00
} )
return info
2016-09-24 06:57:55 -05:00
twitter_card_url = None
2016-03-02 23:39:04 -06:00
if ' class= " PlayableMedia ' in webpage :
2016-09-24 06:57:55 -05:00
twitter_card_url = ' %s //twitter.com/i/videos/tweet/ %s ' % ( self . http_scheme ( ) , twid )
else :
twitter_card_iframe_url = self . _search_regex (
r ' data-full-card-iframe-url=([ \' " ])(?P<url>(?:(?! \ 1).)+) \ 1 ' ,
webpage , ' Twitter card iframe URL ' , default = None , group = ' url ' )
if twitter_card_iframe_url :
twitter_card_url = compat_urlparse . urljoin ( url , twitter_card_iframe_url )
if twitter_card_url :
2016-03-02 23:39:04 -06:00
info . update ( {
' _type ' : ' url_transparent ' ,
' ie_key ' : ' TwitterCard ' ,
2016-09-24 06:57:55 -05:00
' url ' : twitter_card_url ,
2016-03-02 23:39:04 -06:00
} )
return info
2016-02-21 03:21:37 -06:00
raise ExtractorError ( ' There \' s no video in this tweet. ' )
2016-02-21 02:41:24 -06:00
class TwitterAmplifyIE ( TwitterBaseIE ) :
IE_NAME = ' twitter:amplify '
2016-09-08 05:04:57 -05:00
_VALID_URL = r ' https?://amp \ .twimg \ .com/v/(?P<id>[0-9a-f \ -] {36} ) '
2016-02-21 02:41:24 -06:00
_TEST = {
' url ' : ' https://amp.twimg.com/v/0ba0c3c7-0af3-4c0a-bed5-7efd1ffa2951 ' ,
' md5 ' : ' 7df102d0b9fd7066b86f3159f8e81bf6 ' ,
' info_dict ' : {
' id ' : ' 0ba0c3c7-0af3-4c0a-bed5-7efd1ffa2951 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Twitter Video ' ,
2016-02-21 03:16:35 -06:00
' thumbnail ' : ' re:^https?://.* ' ,
2016-02-21 02:41:24 -06:00
} ,
}
def _real_extract ( self , url ) :
video_id = self . _match_id ( url )
webpage = self . _download_webpage ( url , video_id )
vmap_url = self . _html_search_meta (
' twitter:amplify:vmap ' , webpage , ' vmap url ' )
2017-07-11 02:48:34 -05:00
formats = self . _extract_formats_from_vmap_url ( vmap_url , video_id )
2016-02-21 02:41:24 -06:00
2016-02-21 03:16:35 -06:00
thumbnails = [ ]
thumbnail = self . _html_search_meta (
' twitter:image:src ' , webpage , ' thumbnail ' , fatal = False )
def _find_dimension ( target ) :
w = int_or_none ( self . _html_search_meta (
' twitter: %s :width ' % target , webpage , fatal = False ) )
h = int_or_none ( self . _html_search_meta (
' twitter: %s :height ' % target , webpage , fatal = False ) )
return w , h
if thumbnail :
thumbnail_w , thumbnail_h = _find_dimension ( ' image ' )
thumbnails . append ( {
' url ' : thumbnail ,
' width ' : thumbnail_w ,
' height ' : thumbnail_h ,
} )
video_w , video_h = _find_dimension ( ' player ' )
2017-07-11 02:48:34 -05:00
formats [ 0 ] . update ( {
2016-02-21 03:16:35 -06:00
' width ' : video_w ,
' height ' : video_h ,
2017-07-11 02:48:34 -05:00
} )
2016-02-21 03:16:35 -06:00
2016-02-21 02:41:24 -06:00
return {
' id ' : video_id ,
' title ' : ' Twitter Video ' ,
2016-02-21 03:16:35 -06:00
' formats ' : formats ,
' thumbnails ' : thumbnails ,
2016-02-21 02:41:24 -06:00
}