Merge pull request #396 from akarambir/upload-video-endpoint
Add chunked video upload functionality
This commit is contained in:
commit
c59de5c1df
3 changed files with 83 additions and 3 deletions
|
|
@ -35,6 +35,22 @@ Documentation:
|
||||||
* https://dev.twitter.com/rest/reference/post/statuses/update
|
* https://dev.twitter.com/rest/reference/post/statuses/update
|
||||||
* https://dev.twitter.com/rest/reference/post/media/upload
|
* https://dev.twitter.com/rest/reference/post/media/upload
|
||||||
|
|
||||||
|
Updating Status with Video
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
This uploads a video as a media object and associates it with a status update.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
video = open('/path/to/file/video.mp4', 'rb')
|
||||||
|
response = twitter.upload_video(media=video, media_type='video/mp4')
|
||||||
|
twitter.update_status(status='Checkout this cool video!', media_ids=[response['media_id']])
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
* https://dev.twitter.com/rest/reference/post/statuses/update
|
||||||
|
* https://dev.twitter.com/rest/reference/post/media/upload
|
||||||
|
|
||||||
Posting a Status with an Editing Image
|
Posting a Status with an Editing Image
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,10 @@ class Twython(EndpointsMixin, object):
|
||||||
retry_after=response.headers.get('X-Rate-Limit-Reset'))
|
retry_after=response.headers.get('X-Rate-Limit-Reset'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = response.json()
|
if response.status_code == 204:
|
||||||
|
content = response.content
|
||||||
|
else:
|
||||||
|
content = response.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise TwythonError('Response was not valid JSON. \
|
raise TwythonError('Response was not valid JSON. \
|
||||||
Unable to decode.')
|
Unable to decode.')
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ This map is organized the order functions are documented at:
|
||||||
https://dev.twitter.com/docs/api/1.1
|
https://dev.twitter.com/docs/api/1.1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
from .advisory import TwythonDeprecationWarning
|
from .advisory import TwythonDeprecationWarning
|
||||||
|
|
||||||
|
|
@ -139,6 +144,62 @@ class EndpointsMixin(object):
|
||||||
"""
|
"""
|
||||||
return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params)
|
return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params)
|
||||||
|
|
||||||
|
def upload_video(self, media, media_type, size=None):
|
||||||
|
"""Uploads video file to Twitter servers in chunks. The file will be available to be attached
|
||||||
|
to a status for 60 minutes. To attach to a update, pass a list of returned media ids
|
||||||
|
to the 'update_status' method using the 'media_ids' param.
|
||||||
|
|
||||||
|
Upload happens in 3 stages:
|
||||||
|
- INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error.
|
||||||
|
- APPEND calls each with media chunk. This returns a 204(No Content) if chunk is received.
|
||||||
|
- FINALIZE call to complete media upload. This returns media_id to be used with status update.
|
||||||
|
|
||||||
|
Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each.
|
||||||
|
|
||||||
|
Docs:
|
||||||
|
https://dev.twitter.com/rest/public/uploading-media#chunkedupload
|
||||||
|
"""
|
||||||
|
upload_url = 'https://upload.twitter.com/1.1/media/upload.json'
|
||||||
|
if not size:
|
||||||
|
media.seek(0, os.SEEK_END)
|
||||||
|
size = media.tell()
|
||||||
|
media.seek(0)
|
||||||
|
|
||||||
|
# Stage 1: INIT call
|
||||||
|
params = {
|
||||||
|
'command': 'INIT',
|
||||||
|
'media_type': media_type,
|
||||||
|
'total_bytes': size
|
||||||
|
}
|
||||||
|
response_init = self.post(upload_url, params=params)
|
||||||
|
media_id = response_init['media_id']
|
||||||
|
|
||||||
|
# Stage 2: APPEND calls with 1mb chunks
|
||||||
|
segment_index = 0
|
||||||
|
while True:
|
||||||
|
data = media.read(1*1024*1024)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
media_chunk = StringIO()
|
||||||
|
media_chunk.write(data)
|
||||||
|
media_chunk.seek(0)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'command': 'APPEND',
|
||||||
|
'media_id': media_id,
|
||||||
|
'segment_index': segment_index,
|
||||||
|
'media': media_chunk,
|
||||||
|
}
|
||||||
|
self.post(upload_url, params=params)
|
||||||
|
segment_index += 1
|
||||||
|
|
||||||
|
# Stage 3: FINALIZE call to complete upload
|
||||||
|
params = {
|
||||||
|
'command': 'FINALIZE',
|
||||||
|
'media_id': media_id
|
||||||
|
}
|
||||||
|
return self.post(upload_url, params=params)
|
||||||
|
|
||||||
def get_oembed_tweet(self, **params):
|
def get_oembed_tweet(self, **params):
|
||||||
"""Returns information allowing the creation of an embedded
|
"""Returns information allowing the creation of an embedded
|
||||||
representation of a Tweet on third party sites.
|
representation of a Tweet on third party sites.
|
||||||
|
|
@ -546,7 +607,7 @@ class EndpointsMixin(object):
|
||||||
list_mute_ids.iter_key = 'ids'
|
list_mute_ids.iter_key = 'ids'
|
||||||
|
|
||||||
def create_mute(self, **params):
|
def create_mute(self, **params):
|
||||||
"""Mutes the specified user, preventing their tweets appearing
|
"""Mutes the specified user, preventing their tweets appearing
|
||||||
in the authenticating user's timeline.
|
in the authenticating user's timeline.
|
||||||
|
|
||||||
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create
|
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create
|
||||||
|
|
@ -555,7 +616,7 @@ class EndpointsMixin(object):
|
||||||
return self.post('mutes/users/create', params=params)
|
return self.post('mutes/users/create', params=params)
|
||||||
|
|
||||||
def destroy_mute(self, **params):
|
def destroy_mute(self, **params):
|
||||||
"""Un-mutes the user specified in the user or ID parameter for
|
"""Un-mutes the user specified in the user or ID parameter for
|
||||||
the authenticating user.
|
the authenticating user.
|
||||||
|
|
||||||
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy
|
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue