Goのanacondaで140秒動画ツイート


はじめに

以前Goで動画つきツイートをするtwittebotを書きましたが,その際は5MB以下のみのツイートでした.
今回は140秒動画をanacondaを使用します.
現在(2018/4/15),140秒動画に対応するメソッドがないため,自作してそれを使用したいと思います.

twiiter APIに沿ったメソッドの作成

twitter API Documentationを参考に
anacondaのmedia.goに以下を追記します.

media.go

....

func (a TwitterApi) UploadVideoInit(totalBytes int, mimeType string) (chunkedMedia ChunkedMedia, err error) {
    v := url.Values{}
    v.Set("command", "INIT")
    v.Set("media_type", mimeType)
    v.Set("total_bytes", strconv.FormatInt(int64(totalBytes), 10))
    //追加
    v.Set("media_category", "tweet_video")

    var mediaResponse ChunkedMedia

    response_ch := make(chan response)
    a.queryQueue <- query{UploadBaseUrl + "/media/upload.json", v, &mediaResponse, _POST, response_ch}
    return mediaResponse, (<-response_ch).err
}
....

//以下を全て追加
type StatusMedia struct {
    MediaID          int64  `json:"media_id"`
    MediaIDString    string `json:"media_id_string"`
    MediaKey         string `json:"media_key"`
    ExpiresAfterSecs int    `json:"expires_after_secs"`
    ProcessingInfo   struct {
        State           string `json:"state"`
        CheckAfterSecs  int    `json:"check_after_secs"`
        ProgressPercent int    `json:"progress_percent"`
    } `json:"processing_info"`
}

func (a TwitterApi) UploadVideoStatus(mediaIdString string) (videoMedia StatusMedia, err error) {
    v := url.Values{}
    v.Set("command", "STATUS")
    v.Set("media_id", mediaIdString)

    var mediaResponse StatusMedia

    response_ch := make(chan response)
    a.queryQueue <- query{UploadBaseUrl + "/media/upload.json", v, &mediaResponse, _GET, response_ch}
    return mediaResponse, (<-response_ch).err
}

これで完成です.

上記のメソッドを使用して動画をtweetする

main.go
package main

import (
    "encoding/base64"
    "io/ioutil"
    "net/url"
    "os"

    "github.com/ChimeraCoder/anaconda"
)

func main() {
    anaconda.SetConsumerKey(os.Getenv("CONSUMER_KEY"))
    anaconda.SetConsumerSecret(os.Getenv("CONSUMER_SECRET"))
    api := anaconda.NewTwitterApi(os.Getenv("ACCESS_TOKEN"), os.Getenv("ACCESS_TOKEN_SECRET"))

    file, err := os.Open("ex.mp4")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    byteMedia, err := ioutil.ReadAll(file)
    if err != nil {
        panic(err)
    }

    fileStatus, err := file.Stat()
    if err != nil {
        panic(err)
    }
    size := int(fileStatus.Size())

    chunkMedia, err := api.UploadVideoInit(size, "video/mp4")
    if err != nil {
        panic(err)
    }

    //The base64-encoded chunk of media file. It must be <= 5 MB
    chunkSize  := 5 * 1024 * 1024
    index := 0
    for i := 0; i < size; i += chunkSize {
        var data string
        if (size-i)/chunkSize > 0 {
            data = base64.StdEncoding.EncodeToString(byteMedia[i : i+chunkSize])
        } else {
            data = base64.StdEncoding.EncodeToString(byteMedia[i:])
        }
        if err = api.UploadVideoAppend(chunkMedia.MediaIDString, index, data); err != nil {
            panic(err)
        }
        index++
    }

    video, err := api.UploadVideoFinalize(chunkMedia.MediaIDString)
    if err != nil {
        panic(err)
    }

    for {
        videos, err := api.UploadVideoStatus(chunkMedia.MediaIDString)
        if err != nil {
            panic(err)
        }
        if videos.ProcessingInfo.State == "succeeded" {
            break
        }
        time.Sleep(time.Duration(videos.ProcessingInfo.CheckAfterSecs))
    }

    params := url.Values{}
    params.Add("media_ids", video.MediaIDString)
    if _, err := api.PostTweet("text", params); err != nil {
        panic(err)
    }
}

肝は5MB以下でしか送信できないため,チャンクに分けて送信しなければいけないところです.
そして送信毎にCheckAfterSecsがレスポンスで返ってくるので,その時間だけsleepしProcessingInfo.Statesucceededになるまで待つ処理が必要です.

これでtweetできます.

最後に

これをもう少し綺麗に整えてPRを出そうとしたのですが,現在(2018/4/15),140秒動画に対応するメソッドがPRに既にありました笑

初めてのOSSにコミットしてみたかったーーーー!

終わり