Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
233 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
module Twitter | ||
module REST | ||
module Media | ||
# Maximum number of times to poll twitter for upload status | ||
MAX_STATUS_CHECKS = 20 | ||
|
||
# Upload a media file to twitter in one request | ||
# | ||
# @see https://dev.twitter.com/rest/reference/post/media/upload.html | ||
# @note This is only for small files, use the chunked upload for larger ones. | ||
# @rate_limited Yes | ||
# @authentication Requires user context | ||
# @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid. | ||
# @return [Integer] The media_id of the uploaded file. | ||
# @param file [File] An image file (PNG, JPEG or GIF). | ||
# @option options [String] :media_category Category with which to | ||
# identify media upload. When this is specified, it enables async | ||
# processing which allows larger uploads. See | ||
# https://dev.twitter.com/rest/media/uploading-media for details. | ||
# Possible values include tweet_image, tweet_gif, and tweet_video. | ||
def upload_media_simple(file, options = {}) | ||
Twitter::REST::Request.new(self, | ||
:multipart_post, | ||
'https://upload.twitter.com/1.1/media/upload.json', | ||
key: :media, | ||
file: file, | ||
**options).perform[:media_id] | ||
end | ||
|
||
# Upload a media file to twitter in chunks | ||
# | ||
# @see https://dev.twitter.com/rest/reference/post/media/upload.html | ||
# @rate_limited Yes | ||
# @authentication Requires user context | ||
# @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid. | ||
# @return [Integer] The media_id of the uploaded file. | ||
# @param file [File] An image or video file (PNG, JPEG, GIF, or MP4). | ||
# @option options [String] :media_category Category with which to | ||
# identify media upload. When this is specified, it enables async | ||
# processing which allows larger uploads. See | ||
# https://dev.twitter.com/rest/media/uploading-media for details. | ||
# Possible values include tweet_image, tweet_gif, and tweet_video. | ||
def upload_media_chunked(file, options = {}) | ||
media_id = chunked_upload_init(file, options)[:media_id] | ||
upload_chunks(media_id, file) | ||
poll_status(media_id) | ||
|
||
media_id | ||
end | ||
|
||
private | ||
|
||
# Finalize upload and poll status until upload is ready | ||
# | ||
# @param media_id [Integer] The media_id to check the status of | ||
def poll_status(media_id) | ||
response = chunked_upload_finalize(media_id) | ||
MAX_STATUS_CHECKS.times do | ||
return unless (info = response[:processing_info]) | ||
return if info[:state] == 'succeeded' | ||
|
||
raise Twitter::Error::ClientError, 'Upload Failed!' if info[:state] == 'failed' | ||
|
||
sleep info[:check_after_secs] | ||
|
||
response = chunked_upload_status(media_id) | ||
end | ||
|
||
raise Twitter::Error::ClientError, 'Max status checks exceeded!' | ||
end | ||
|
||
# Initialize a chunked upload | ||
# | ||
# @param file [File] Media file being uploaded | ||
# @param options [Hash] Additional parameters | ||
def chunked_upload_init(file, options) | ||
Twitter::REST::Request.new(self, :post, 'https://upload.twitter.com/1.1/media/upload.json', | ||
command: 'INIT', | ||
media_type: 'video/mp4', | ||
total_bytes: file.size, | ||
**options).perform | ||
end | ||
|
||
# Append chunks to the upload | ||
# | ||
# @param media_id [Integer] The media_id of the file being uploaded | ||
# @param file [File] Media file being uploaded | ||
def upload_chunks(media_id, file) | ||
until file.eof? | ||
chunk = file.read(5_000_000) | ||
segment ||= -1 | ||
segment += 1 | ||
chunked_upload_append(chunk, segment, media_id) | ||
end | ||
|
||
file.close | ||
end | ||
|
||
# Append a chunk to the upload | ||
# | ||
# @param chunk [String] File chunk to upload | ||
# @param segment [Integer] Index of chunk in file | ||
# @param media_id [Integer] The media_id of the file being uploaded | ||
# @return [Hash] Response JSON | ||
def chunked_upload_append(chunk, segment, media_id) | ||
Twitter::REST::Request.new(self, :multipart_post, 'https://upload.twitter.com/1.1/media/upload.json', | ||
command: 'APPEND', | ||
media_id: media_id, | ||
segment_index: segment, | ||
key: :media, | ||
file: StringIO.new(chunk)).perform | ||
end | ||
|
||
# Finalize the upload. This returns the processing status if applicable | ||
# | ||
# @param media_id [Integer] The media_id of the file being uploaded | ||
# @return [Hash] Response JSON | ||
def chunked_upload_finalize(media_id) | ||
Twitter::REST::Request.new(self, :post, 'https://upload.twitter.com/1.1/media/upload.json', | ||
command: 'FINALIZE', media_id: media_id).perform | ||
end | ||
|
||
# Check processing status for async uploads | ||
# | ||
# @param media_id [Integer] The media_id of the file being uploaded | ||
# @return [Hash] Response JSON | ||
def chunked_upload_status(media_id) | ||
Twitter::REST::Request.new(self, :get, 'https://upload.twitter.com/1.1/media/upload.json', | ||
command: 'STATUS', media_id: media_id).perform | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Twitter | ||
class KeepAlive | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# coding: utf-8 | ||
require 'helper' | ||
|
||
describe Twitter::REST::Media do | ||
before do | ||
@client = Twitter::REST::Client.new(consumer_key: 'CK', consumer_secret: 'CS', access_token: 'AT', access_token_secret: 'AS') | ||
end | ||
|
||
describe '#upload_media_simple' do | ||
before do | ||
stub_request(:post, 'https://upload.twitter.com/1.1/media/upload.json').to_return(body: fixture('upload.json'), headers: {content_type: 'application/json; charset=utf-8'}) | ||
end | ||
|
||
it 'uploads the file' do | ||
@client.upload_media_simple(fixture('pbjt.gif')) | ||
expect(a_request(:post, 'https://upload.twitter.com/1.1/media/upload.json')).to have_been_made | ||
end | ||
|
||
it 'returns the media id' do | ||
media_id = @client.upload_media_simple(fixture('pbjt.gif')) | ||
|
||
expect(media_id.to_s).to eq '470030289822314497' | ||
end | ||
|
||
it 'accepts a media_category parameter' do | ||
expect(Twitter::REST::Request).to receive(:new) | ||
.with(any_args, hash_including(media_category: 'test')) | ||
.and_return(double(perform: {media_id: 123})) | ||
|
||
@client.upload_media_simple(fixture('pbjt.gif'), media_category: 'test') | ||
end | ||
end | ||
|
||
describe '#upload_media_chunked' do | ||
context 'synchronous upload' do | ||
before do | ||
stub_request(:post, 'https://upload.twitter.com/1.1/media/upload.json').to_return(body: fixture('upload.json'), headers: {content_type: 'application/json; charset=utf-8'}) | ||
end | ||
|
||
it 'uploads the file in chunks' do | ||
@client.upload_media_chunked(fixture('1080p.mp4')) | ||
|
||
expect(a_request(:post, 'https://upload.twitter.com/1.1/media/upload.json')).to have_been_made.times(3) | ||
end | ||
|
||
it 'returns the media id' do | ||
media_id = @client.upload_media_chunked(fixture('1080p.mp4')) | ||
|
||
expect(media_id.to_s).to eq '470030289822314497' | ||
end | ||
end | ||
|
||
it 'polls the status until processing is complete' do | ||
stub_request(:post, 'https://upload.twitter.com/1.1/media/upload.json').to_return do |request| | ||
{ | ||
headers: {content_type: 'application/json; charset=utf-8'}, | ||
body: case request.body | ||
when /command=(INIT|APPEND)/ | ||
fixture('upload.json') | ||
when /command=FINALIZE/ | ||
'{"processing_info": {"state": "pending", "check_after_secs": 5}}' | ||
end, | ||
} | ||
end | ||
stub_request(:get, 'https://upload.twitter.com/1.1/media/upload.json') | ||
.with(query: {command: 'STATUS', media_id: '470030289822314497'}) | ||
.to_return( | ||
headers: {content_type: 'application/json; charset=utf-8'}, | ||
body: '{"processing_info": {"state": "succeeded"}}' | ||
) | ||
|
||
expect(@client).to receive(:sleep).with(5) | ||
|
||
@client.upload_media_chunked(fixture('1080p.mp4'), media_category: 'tweet_video') | ||
|
||
expect(a_request(:post, 'https://upload.twitter.com/1.1/media/upload.json')).to have_been_made.times(3) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters