PHP
Twitter
TwitterAPI
TwistOAuth

TwitterAPIのアップロード系エンドポイントまとめ (140秒動画対応)

More than 1 year has passed since last update.


概要

Twitterが140秒までの動画ツイートに対応したと聞いたので,以前からあるエンドポイントも含めてアップロード系のものをまとめてみました.

サンプルには拙作のTwistOAuthを用いたコードを掲載します.


POST statuses/update_with_media

単一の画像のアップロードに使えます.

echo "Uploading and tweeting...\n";

$to->postMultipart('statuses/update_with_media', [
'status' => 'hello',
'@media[]' => 'photo.jpg',
]);

echo "Done!\n";


  • 現在非推奨

  • サーバとの通信が1回で済む

  • multipart/form-dataが強制される


    • エンドポイント固有のパラメータはoauth_signatureの生成対象に含まれない




  • media[]というパラメータ名であるにも関わらず,単一の画像しか受け付けない


POST media/upload (コマンド無し)

1枚以上の画像のアップロードに使えます.

$media_ids = [];

echo "Uploading A.jpg...\n";
$media_ids[] = $to->postMultipart('media/upload', [
'@media' => 'A.jpg',
])->media_id_string;

echo "Uploading B.jpg...\n";
$media_ids[] = $to->post('media/upload', [
'@media' => 'B.jpg', // 自動的にデータがBase64エンコードされる
])->media_id_string;

echo "Tweeting...\n";
$to->post('statuses/update', [
'status' => 'hello',
'media_ids' => implode(',', $media_ids),
]);

echo "Done!\n";


  • サーバとの通信が2回以上必要

  • multipart/form-dataとapplication/x-www-form-urlencodedから選べる


    • multipart/form-dataの場合,エンドポイント固有のパラメータはoauth_signatureの生成対象に含まれない

    • application/x-www-form-urlencodedの場合,メディアデータのみBase64エンコードする必要がある



オーバーヘッドの小さいmultipart/form-dataを推奨します.


POST media/upload (コマンドあり・同期)

いろいろなデータのアップロードに使えます.


  • 1枚以上の画像

  • 1つのアニメGIF (0.5秒〜30秒)

  • 1つの動画 (0.5秒〜30秒)

以下のコマンドを順番にコールする必要があります.


  1. INIT

  2. APPEND

  3. FINALIZE

$filename = 'small_video.mp4';

echo "Initializing...\n";
$info = $to->post('media/upload', [
'command' => 'INIT',
'media_type' => 'video/mp4',
'total_bytes' => filesize($filename),
]);

echo "Uploading video...\n";
$to->postMultipart('media/upload', [
'command' => 'APPEND',
'media_id' => $info->media_id_string,
'segment_index' => 0,
'@media' => $filename,
]);

echo "Finalizing...\n";
$to->post('media/upload', [
'command' => 'FINALIZE',
'media_id' => $info->media_id_string,
]);

echo "Tweeting...\n";
$to->post('statuses/update', [
'status' => 'hello',
'media_ids' => $info->media_id_string,
]);

echo "Done!\n";


  • サーバとの通信が4回以上必要

  • multipart/form-dataとapplication/x-www-form-urlencodedから選べる


    • multipart/form-dataの場合,エンドポイント固有のパラメータはoauth_signatureの生成対象に含まれない

    • application/x-www-form-urlencodedの場合,メディアデータのみBase64エンコードする必要がある



オーバーヘッドの小さいmultipart/form-dataを推奨します.


GET|POST media/upload (コマンドあり・非同期)

いろいろなデータのアップロードに使えます.こちらは大きなデータをアップロードする前提なので,チャンク分割を入れます.分割を行わないと,413 Request Entity Too Largeが返されてしまう場合があります.


  • 1枚以上の画像


    • media_category=tweet_image



  • 1つのアニメGIF (0.5秒〜140秒)


    • media_category=tweet_gif



  • 1つの動画 (0.5秒〜140秒)


    • media_category=tweet_video



以下のコマンドを順番にコールする必要があります.



  1. INIT (media_category指定必須)

  2. APPEND

  3. FINALIZE

  4. STATUS

$file = new \SplFileObject('large_video.mp4', 'rb');

$chunk_size = 262144; // 適当に調節してください

echo "Initializing...\n";
$info = $to->post('media/upload', [
'command' => 'INIT',
'media_type' => 'video/mp4',
'total_bytes' => $file->getSize(),
'media_category' => 'tweet_video', // これを忘れると非同期のほうにならない
]);

$curls = [];
for ($i = 0; '' !== $buffer = $file->fread($chunk_size); ++$i) { // チャンクサイズごとに分割
$curls[] = $to->curlPostMultipart('media/upload', [
'command' => 'APPEND',
'media_id' => $info->media_id_string,
'segment_index' => $i,
'#media' => $buffer,
]); // リクエストせずにリクエスト用のcURLリソースだけを用意
}

echo "Uploading chunks...\n";
$to->curlMultiExec($curls, true); // 1チャンクごとに待ってると時間がかかるので並列化

echo "Finalizing...\n";
$info = $to->post('media/upload', [
'command' => 'FINALIZE',
'media_id' => $info->media_id_string,
]);

echo "Waiting for processing...\n";
while ($info->processing_info->state === 'pending' || $info->processing_info->state === 'in_progress') { // 終わるか失敗するまで繰り返す
$percent = isset($info->processing_info->progress_percent) // 場合によってはパーセント表記が返ってくる
? "({$info->processing_info->progress_percent}%)"
: ''
;
echo "State: {$info->processing_info->state} {$percent}\n";
sleep($info->processing_info->check_after_secs); // 待つように指示されたぶんだけ待つ
$info = $to->get('media/upload', [
'command' => 'STATUS',
'media_id' => $info->media_id_string,
]); // 情報を更新
}

if ($info->processing_info->state === 'failed') {
// 失敗時は例外をスロー
throw new \RuntimeException($info->processing_info->error->name);
}

echo "Tweeting...\n";
$to->post('statuses/update', [
'status' => 'hello',
'media_ids' => $info->media_id_string,
]);

echo "Done!\n";


  • サーバとの通信がかなりの回数必要

  • multipart/form-dataとapplication/x-www-form-urlencodedから選べる


    • multipart/form-dataの場合,エンドポイント固有のパラメータはoauth_signatureの生成対象に含まれない

    • application/x-www-form-urlencodedの場合,メディアデータのみBase64エンコードする必要がある



  • 失敗した時に$info->processing->state"fail"になることがある


    • しかし,そのときのHTTPステータスコードは200 OK

    • エラー内容次第では従来の {"errors":[{"code":1,"message":"xx"}]}400 Bad Requestで来ることもある

    • 失敗したメディアでstatuses/updateしようとするとしっかり400 Bad Requestのエラーになるので,必ずしもこのエラーハンドリングを特別扱いして行う必要はない



オーバーヘッドの小さいmultipart/form-dataを推奨します.

追記: TwistOAuthの後継であるCowitterでは,面倒なアップロードのための記述を丸投げできます


Cowitterのメソッドを活用する場合

Co::wait(function () use ($client) {

$file = new \SplFileObject('large_video.mp4', 'rb');

$on_uploading = function ($percent) {
echo "Uploading ... ({$percent}%)\n";
};
$on_processing = function ($percent) {
echo "Processing ... ({$percent}%)\n";
};

yield $client->postAsync('statuses/update', [
'status' => 'My video',
'media_ids' => (yield $client->uploadVideoAsync($file, $on_uploading, $on_processing))->media_id_string,
]);

echo "Done!\n";
});