[twitter:amplify] Add TwitterAmplifyIE for handling Twitter smart URLs
[youtube-dl] / youtube_dl / extractor / twitter.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5
6 from .common import InfoExtractor
7 from ..utils import (
8     float_or_none,
9     xpath_text,
10     remove_end,
11     int_or_none,
12     ExtractorError,
13     sanitized_Request,
14 )
15
16
17 class TwitterBaseIE(InfoExtractor):
18     def _get_vmap_video_url(self, vmap_url, video_id):
19         vmap_data = self._download_xml(vmap_url, video_id)
20         return xpath_text(vmap_data, './/MediaFile').strip()
21
22
23 class TwitterCardIE(TwitterBaseIE):
24     IE_NAME = 'twitter:card'
25     _VALID_URL = r'https?://(?:www\.)?twitter\.com/i/cards/tfw/v1/(?P<id>\d+)'
26     _TESTS = [
27         {
28             'url': 'https://twitter.com/i/cards/tfw/v1/560070183650213889',
29             'md5': '4fa26a35f9d1bf4b646590ba8e84be19',
30             'info_dict': {
31                 'id': '560070183650213889',
32                 'ext': 'mp4',
33                 'title': 'TwitterCard',
34                 'thumbnail': 're:^https?://.*\.jpg$',
35                 'duration': 30.033,
36             }
37         },
38         {
39             'url': 'https://twitter.com/i/cards/tfw/v1/623160978427936768',
40             'md5': '7ee2a553b63d1bccba97fbed97d9e1c8',
41             'info_dict': {
42                 'id': '623160978427936768',
43                 'ext': 'mp4',
44                 'title': 'TwitterCard',
45                 'thumbnail': 're:^https?://.*\.jpg',
46                 'duration': 80.155,
47             },
48         },
49         {
50             'url': 'https://twitter.com/i/cards/tfw/v1/654001591733886977',
51             'md5': 'b6f35e8b08a0bec6c8af77a2f4b3a814',
52             'info_dict': {
53                 'id': 'dq4Oj5quskI',
54                 'ext': 'mp4',
55                 'title': 'Ubuntu 11.10 Overview',
56                 'description': 'Take a quick peek at what\'s new and improved in Ubuntu 11.10.\n\nOnce installed take a look at 10 Things to Do After Installing: http://www.omgubuntu.co.uk/2011/10/10-things-to-do-after-installing-ubuntu-11-10/',
57                 'upload_date': '20111013',
58                 'uploader': 'OMG! Ubuntu!',
59                 'uploader_id': 'omgubuntu',
60             },
61             'add_ie': ['Youtube'],
62         },
63         {
64             'url': 'https://twitter.com/i/cards/tfw/v1/665289828897005568',
65             'md5': 'ab2745d0b0ce53319a534fccaa986439',
66             'info_dict': {
67                 'id': 'iBb2x00UVlv',
68                 'ext': 'mp4',
69                 'upload_date': '20151113',
70                 'uploader_id': '1189339351084113920',
71                 'uploader': '@ArsenalTerje',
72                 'title': 'Vine by @ArsenalTerje',
73             },
74             'add_ie': ['Vine'],
75         }
76     ]
77
78     def _real_extract(self, url):
79         video_id = self._match_id(url)
80
81         # Different formats served for different User-Agents
82         USER_AGENTS = [
83             'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/20.0 (Chrome)',  # mp4
84             'Mozilla/5.0 (Windows NT 5.2; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0',  # webm
85         ]
86
87         config = None
88         formats = []
89         for user_agent in USER_AGENTS:
90             request = sanitized_Request(url)
91             request.add_header('User-Agent', user_agent)
92             webpage = self._download_webpage(request, video_id)
93
94             iframe_url = self._html_search_regex(
95                 r'<iframe[^>]+src="((?:https?:)?//(?:www.youtube.com/embed/[^"]+|(?:www\.)?vine\.co/v/\w+/card))"',
96                 webpage, 'video iframe', default=None)
97             if iframe_url:
98                 return self.url_result(iframe_url)
99
100             config = self._parse_json(self._html_search_regex(
101                 r'data-player-config="([^"]+)"', webpage, 'data player config'),
102                 video_id)
103             if 'playlist' not in config:
104                 if 'vmapUrl' in config:
105                     formats.append({
106                         'url': self._get_vmap_video_url(config['vmapUrl'], video_id),
107                     })
108                     break   # same video regardless of UA
109                 continue
110
111             video_url = config['playlist'][0]['source']
112
113             f = {
114                 'url': video_url,
115             }
116
117             m = re.search(r'/(?P<width>\d+)x(?P<height>\d+)/', video_url)
118             if m:
119                 f.update({
120                     'width': int(m.group('width')),
121                     'height': int(m.group('height')),
122                 })
123             formats.append(f)
124         self._sort_formats(formats)
125
126         thumbnail = config.get('posterImageUrl')
127         duration = float_or_none(config.get('duration'))
128
129         return {
130             'id': video_id,
131             'title': 'TwitterCard',
132             'thumbnail': thumbnail,
133             'duration': duration,
134             'formats': formats,
135         }
136
137
138 class TwitterIE(InfoExtractor):
139     IE_NAME = 'twitter'
140     _VALID_URL = r'https?://(?:www\.|m\.|mobile\.)?twitter\.com/(?P<user_id>[^/]+)/status/(?P<id>\d+)'
141     _TEMPLATE_URL = 'https://twitter.com/%s/status/%s'
142
143     _TESTS = [{
144         'url': 'https://twitter.com/freethenipple/status/643211948184596480',
145         'md5': 'db6612ec5d03355953c3ca9250c97e5e',
146         'info_dict': {
147             'id': '643211948184596480',
148             'ext': 'mp4',
149             'title': 'FREE THE NIPPLE - FTN supporters on Hollywood Blvd today!',
150             'thumbnail': 're:^https?://.*\.jpg',
151             'duration': 12.922,
152             'description': 'FREE THE NIPPLE on Twitter: "FTN supporters on Hollywood Blvd today! http://t.co/c7jHH749xJ"',
153             'uploader': 'FREE THE NIPPLE',
154             'uploader_id': 'freethenipple',
155         },
156     }, {
157         'url': 'https://twitter.com/giphz/status/657991469417025536/photo/1',
158         'md5': 'f36dcd5fb92bf7057f155e7d927eeb42',
159         'info_dict': {
160             'id': '657991469417025536',
161             'ext': 'mp4',
162             'title': 'Gifs - tu vai cai tu vai cai tu nao eh capaz disso tu vai cai',
163             'description': 'Gifs on Twitter: "tu vai cai tu vai cai tu nao eh capaz disso tu vai cai https://t.co/tM46VHFlO5"',
164             'thumbnail': 're:^https?://.*\.png',
165             'uploader': 'Gifs',
166             'uploader_id': 'giphz',
167         },
168     }, {
169         'url': 'https://twitter.com/starwars/status/665052190608723968',
170         'md5': '39b7199856dee6cd4432e72c74bc69d4',
171         'info_dict': {
172             'id': '665052190608723968',
173             'ext': 'mp4',
174             'title': 'Star Wars - A new beginning is coming December 18. Watch the official 60 second #TV spot for #StarWars: #TheForceAwakens.',
175             'description': 'Star Wars on Twitter: "A new beginning is coming December 18. Watch the official 60 second #TV spot for #StarWars: #TheForceAwakens."',
176             'uploader_id': 'starwars',
177             'uploader': 'Star Wars',
178         },
179     }]
180
181     def _real_extract(self, url):
182         mobj = re.match(self._VALID_URL, url)
183         user_id = mobj.group('user_id')
184         twid = mobj.group('id')
185
186         webpage = self._download_webpage(self._TEMPLATE_URL % (user_id, twid), twid)
187
188         username = remove_end(self._og_search_title(webpage), ' on Twitter')
189
190         title = description = self._og_search_description(webpage).strip('').replace('\n', ' ').strip('“”')
191
192         # strip  'https -_t.co_BJYgOjSeGA' junk from filenames
193         title = re.sub(r'\s+(https?://[^ ]+)', '', title)
194
195         info = {
196             'uploader_id': user_id,
197             'uploader': username,
198             'webpage_url': url,
199             'description': '%s on Twitter: "%s"' % (username, description),
200             'title': username + ' - ' + title,
201         }
202
203         card_id = self._search_regex(
204             r'["\']/i/cards/tfw/v1/(\d+)', webpage, 'twitter card url', default=None)
205         if card_id:
206             card_url = 'https://twitter.com/i/cards/tfw/v1/' + card_id
207             info.update({
208                 '_type': 'url_transparent',
209                 'ie_key': 'TwitterCard',
210                 'url': card_url,
211             })
212             return info
213
214         mobj = re.search(r'''(?x)
215             <video[^>]+class="animated-gif"[^>]+
216                 (?:data-height="(?P<height>\d+)")?[^>]+
217                 (?:data-width="(?P<width>\d+)")?[^>]+
218                 (?:poster="(?P<poster>[^"]+)")?[^>]*>\s*
219                 <source[^>]+video-src="(?P<url>[^"]+)"
220         ''', webpage)
221
222         if mobj:
223             info.update({
224                 'id': twid,
225                 'url': mobj.group('url'),
226                 'height': int_or_none(mobj.group('height')),
227                 'width': int_or_none(mobj.group('width')),
228                 'thumbnail': mobj.group('poster'),
229             })
230             return info
231
232         raise ExtractorError('There\'s not video in this tweet.')
233
234
235 class TwitterAmplifyIE(TwitterBaseIE):
236     IE_NAME = 'twitter:amplify'
237     _VALID_URL = 'https?://amp\.twimg\.com/v/(?P<id>[0-9a-f\-]{36})'
238
239     _TEST = {
240         'url': 'https://amp.twimg.com/v/0ba0c3c7-0af3-4c0a-bed5-7efd1ffa2951',
241         'md5': '7df102d0b9fd7066b86f3159f8e81bf6',
242         'info_dict': {
243             'id': '0ba0c3c7-0af3-4c0a-bed5-7efd1ffa2951',
244             'ext': 'mp4',
245             'title': 'Twitter Video',
246         },
247     }
248
249     def _real_extract(self, url):
250         video_id = self._match_id(url)
251         webpage = self._download_webpage(url, video_id)
252
253         vmap_url = self._html_search_meta(
254             'twitter:amplify:vmap', webpage, 'vmap url')
255         video_url = self._get_vmap_video_url(vmap_url, video_id)
256
257         return {
258             'id': video_id,
259             'title': 'Twitter Video',
260             'url': video_url,
261         }