Skip to content

Commit c59de5c

Browse files
Merge pull request #396 from akarambir/upload-video-endpoint
Add chunked video upload functionality
2 parents 00d9d6a + 2cca895 commit c59de5c

File tree

3 files changed

+83
-3
lines changed

3 files changed

+83
-3
lines changed

docs/usage/advanced_usage.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ Documentation:
3535
* https://dev.twitter.com/rest/reference/post/statuses/update
3636
* https://dev.twitter.com/rest/reference/post/media/upload
3737

38+
Updating Status with Video
39+
--------------------------
40+
41+
This uploads a video as a media object and associates it with a status update.
42+
43+
.. code-block:: python
44+
45+
video = open('/path/to/file/video.mp4', 'rb')
46+
response = twitter.upload_video(media=video, media_type='video/mp4')
47+
twitter.update_status(status='Checkout this cool video!', media_ids=[response['media_id']])
48+
49+
Documentation:
50+
51+
* https://dev.twitter.com/rest/reference/post/statuses/update
52+
* https://dev.twitter.com/rest/reference/post/media/upload
53+
3854
Posting a Status with an Editing Image
3955
--------------------------------------
4056

twython/api.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ def _request(self, url, method='GET', params=None, api_call=None):
194194
retry_after=response.headers.get('X-Rate-Limit-Reset'))
195195

196196
try:
197-
content = response.json()
197+
if response.status_code == 204:
198+
content = response.content
199+
else:
200+
content = response.json()
198201
except ValueError:
199202
raise TwythonError('Response was not valid JSON. \
200203
Unable to decode.')

twython/endpoints.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
https://dev.twitter.com/docs/api/1.1
1515
"""
1616

17+
import os
1718
import warnings
19+
try:
20+
from StringIO import StringIO
21+
except ImportError:
22+
from io import StringIO
1823

1924
from .advisory import TwythonDeprecationWarning
2025

@@ -139,6 +144,62 @@ def upload_media(self, **params):
139144
"""
140145
return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params)
141146

147+
def upload_video(self, media, media_type, size=None):
148+
"""Uploads video file to Twitter servers in chunks. The file will be available to be attached
149+
to a status for 60 minutes. To attach to a update, pass a list of returned media ids
150+
to the 'update_status' method using the 'media_ids' param.
151+
152+
Upload happens in 3 stages:
153+
- INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error.
154+
- APPEND calls each with media chunk. This returns a 204(No Content) if chunk is received.
155+
- FINALIZE call to complete media upload. This returns media_id to be used with status update.
156+
157+
Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each.
158+
159+
Docs:
160+
https://dev.twitter.com/rest/public/uploading-media#chunkedupload
161+
"""
162+
upload_url = 'https://upload.twitter.com/1.1/media/upload.json'
163+
if not size:
164+
media.seek(0, os.SEEK_END)
165+
size = media.tell()
166+
media.seek(0)
167+
168+
# Stage 1: INIT call
169+
params = {
170+
'command': 'INIT',
171+
'media_type': media_type,
172+
'total_bytes': size
173+
}
174+
response_init = self.post(upload_url, params=params)
175+
media_id = response_init['media_id']
176+
177+
# Stage 2: APPEND calls with 1mb chunks
178+
segment_index = 0
179+
while True:
180+
data = media.read(1*1024*1024)
181+
if not data:
182+
break
183+
media_chunk = StringIO()
184+
media_chunk.write(data)
185+
media_chunk.seek(0)
186+
187+
params = {
188+
'command': 'APPEND',
189+
'media_id': media_id,
190+
'segment_index': segment_index,
191+
'media': media_chunk,
192+
}
193+
self.post(upload_url, params=params)
194+
segment_index += 1
195+
196+
# Stage 3: FINALIZE call to complete upload
197+
params = {
198+
'command': 'FINALIZE',
199+
'media_id': media_id
200+
}
201+
return self.post(upload_url, params=params)
202+
142203
def get_oembed_tweet(self, **params):
143204
"""Returns information allowing the creation of an embedded
144205
representation of a Tweet on third party sites.
@@ -546,7 +607,7 @@ def list_mute_ids(self, **params):
546607
list_mute_ids.iter_key = 'ids'
547608

548609
def create_mute(self, **params):
549-
"""Mutes the specified user, preventing their tweets appearing
610+
"""Mutes the specified user, preventing their tweets appearing
550611
in the authenticating user's timeline.
551612
552613
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create
@@ -555,7 +616,7 @@ def create_mute(self, **params):
555616
return self.post('mutes/users/create', params=params)
556617

557618
def destroy_mute(self, **params):
558-
"""Un-mutes the user specified in the user or ID parameter for
619+
"""Un-mutes the user specified in the user or ID parameter for
559620
the authenticating user.
560621
561622
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy

0 commit comments

Comments
 (0)