Skip to content
Permalink
Browse files

twitter video upload: check format and size. for snarfed/bridgy#572

  • Loading branch information...
snarfed committed Jan 22, 2016
1 parent 812e9cf commit 7bc09a3e2b2596c48af1da80b8f6552db572b2de
Showing with 62 additions and 4 deletions.
  1. +29 −1 granary/test/test_twitter.py
  2. +33 −3 granary/twitter.py
@@ -1595,7 +1595,7 @@ def test_create_favorite(self):
self.assertIn('<span class="verb">favorite</span> <a href="https://twitter.com/snarfed_org/status/100">this tweet</a>:', preview.description)

def test_create_retweet(self):
self.expect_urlopen(twitter.API_POST_RETWEET % 333, TWEET, {'id': 333})
self.expect_urlopen(twitter.API_POST_RETWEET % 333, TWEET, params={'id': 333})
self.mox.ReplayAll()

tweet = copy.deepcopy(TWEET)
@@ -1816,3 +1816,31 @@ def test_create_with_video(self):
self.assert_equals({'url': 'http://posted/video', 'type': 'post'},
self.twitter.create(obj).content)

def test_create_with_video_too_big(self):
self.expect_urlopen(
'http://my/video', '',
response_headers={'Content-Length': twitter.MAX_VIDEO_SIZE + 1})
self.mox.ReplayAll()

ret = self.twitter.create({
'objectType': 'note',
'stream': {'url': 'http://my/video'},
})
self.assertTrue(ret.abort)
self.assertIn("larger than Twitter's 15MB limit.", ret.error_plain)
self.assertIn("larger than Twitter's 15MB limit.", ret.error_html)

def test_create_with_video_wrong_type(self):
self.expect_urlopen('http://my/video', '',
response_headers={'Content-Type': 'video/unknown'})
self.expect_urlopen('http://my/video.mov', '')
self.mox.ReplayAll()

for url in 'http://my/video', 'http://my/video.mov':
ret = self.twitter.create({
'objectType': 'note',
'stream': {'url': url},
})
self.assertTrue(ret.abort)
self.assertIn('Twitter only supports MP4 videos', ret.error_plain)
self.assertIn('Twitter only supports MP4 videos', ret.error_html)
@@ -17,6 +17,7 @@
import httplib
import json
import logging
import mimetypes
import re
import socket
import urllib
@@ -71,13 +72,20 @@
# https://dev.twitter.com/docs/tco-link-wrapper/faq
# * Max media per tweet.
# https://dev.twitter.com/rest/reference/post/statuses/update#api-param-media_ids
# * Allowed video formats, max video size, and upload chunk size:
# https://dev.twitter.com/rest/public/uploading-media#keepinmind
#
# TODO: pull these from /help/configuration.json instead.
# https://dev.twitter.com/docs/api/1.1/get/help/configuration
MAX_TWEET_LENGTH = 140
TCO_LENGTH = 23
MAX_MEDIA = 4

MB = 1024 * 1024
MAX_VIDEO_SIZE = 15 * MB
UPLOAD_CHUNK_SIZE = 5 * MB
VIDEO_MIME_TYPES = frozenset(('video/mp4',))


class OffsetTzinfo(datetime.tzinfo):
"""A simple, DST-unaware tzinfo from given utc offset in seconds.
@@ -667,7 +675,10 @@ def _create(self, obj, preview=None, include_link=False, ignore_formatting=False
preview_content += ('<br /><br /><video controls src="%s"><a href="%s">'
'this video</a></video>' % (video_url, video_url))
if not preview:
data['media_ids'] = self.upload_video(video_url)
ret = self.upload_video(video_url)
if isinstance(ret, source.CreationResult):
return ret
data['media_ids'] = ret

elif image_urls:
num_urls = len(image_urls)
@@ -840,16 +851,35 @@ def upload_video(self, url):
Args:
url: string URL of images
Returns: string media id
Returns: string media id or CreationResult on error
"""
logging.info('Fetching %s', url)
video_resp = urllib2.urlopen(url, timeout=HTTP_TIMEOUT)

# check format and size
type = video_resp.headers.get('Content-Type')
if not type:
type, _ = mimetypes.guess_type(url)
if type and type not in VIDEO_MIME_TYPES:
msg = 'Twitter only supports MP4 videos; yours looks like a %s.' % type
return source.creation_result(abort=True, error_plain=msg, error_html=msg)

length = video_resp.headers.get('Content-Length')
if not util.is_int(length):
msg = "Couldn't determine your video's size."
return source.creation_result(abort=True, error_plain=msg, error_html=msg)

length = int(length)
if int(length) > MAX_VIDEO_SIZE:
msg = "Your %sMB video is larger than Twitter's %dMB limit." % (
length // MB, MAX_VIDEO_SIZE // MB)
return source.creation_result(abort=True, error_plain=msg, error_html=msg)

# INIT
media_id = self.urlopen(API_UPLOAD_MEDIA, data=urllib.urlencode({
'command': 'INIT',
'media_type': 'video/mp4',
'total_bytes': video_resp.headers.get('Content-Length', ''),
'total_bytes': length,
}))['media_id_string']

# APPEND

0 comments on commit 7bc09a3

Please sign in to comment.
You can’t perform that action at this time.