Denis Kreshikhin

Denis
Kreshikhin

software development, computer science

2012–2016 © Denis Kreshikhin

Uploading video to Twitter with Go

The possibility of short video uploading in Twitter is provided by official API for almost a year. Of course Twitter also let to show video from Youtube or Vimeo before, but video uploaded in Twitter directly will be played on scrolling without any clicks or taps. That increases the attractiveness to users.

Twitter OAuth

First of all we need to register our application on Twitter and take consumer_key and consumer_secret. The key and secret are required for OAuth. We will use library "github.com/mrjones/oauth" for establishing a connection through OAuth.

consumerKey := "YOUR_COSUMER_KEY"
consumerSecret := "YOUR_COSUMER_SECRET"

c := oauth.NewConsumer(
    consumerKey,
    consumerSecret,
    oauth.ServiceProvider{
        RequestTokenUrl:   "https://api.twitter.com/oauth/request_token",
        AuthorizeTokenUrl: "https://api.twitter.com/oauth/authorize",
        AccessTokenUrl:    "https://api.twitter.com/oauth/access_token",
    })

c.Debug(false)

Authorization of a user is required for video upload. So for process automation is useful to save authorization data from previous session in a file (e.g. twitter.json). In this case there aren't any necessity for make login in browser manually each time.

accessToken := ReadAccessToken("twitter.json")

if accessToken == nil {
    return;

    requestToken, u, err := c.GetRequestTokenAndUrl("oob")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("(1) Go to: " + u)
    fmt.Println("(2) Grant access, you should get back a verification code.")
    fmt.Println("(3) Enter that verification code here: ")

    verificationCode := ""
    fmt.Scanln(&verificationCode)

    accessToken, err = c.AuthorizeToken(requestToken, verificationCode)
    if err != nil {
        log.Fatal(err)
    }
}

client, err := c.MakeHttpClient(accessToken)
if err != nil {
    panic(err)
}

The variable client containing the token opens an access to Twitter API for us.

Upload video by Twitter API

We can't attach a video directly to a message at moment of sending, because unique media_id is required for publication of the message in Twitter. We should take media_id before uploading the video by the next steps:

  1. Send length of the content and take media_id
  2. Upload the content
  3. Finish content uploading by special request
  4. Send message with media_id

We will store URLs for requests in constants StatusUpdate and MediaUpload. The structure Twitter will store only reference to http.Client:


package twitter

const StatusUpdate string = "https://api.twitter.com/1.1/statuses/update.json"
const MediaUpload string = "https://upload.twitter.com/1.1/media/upload.json"

type Twitter struct {
    client *http.Client
}

func NewTwitter(client *http.Client) *Twitter {
    self := &Twitter{}
    self.client = client
    return self
}

Initialization and taking of media_id

At moment of initialization we should set two important parameters: media_type and total_bytes. The parameters should precisely to fit the content, because Twitter will use this parameters for check correctness of uploading. An inaccuracy in values of this parameters will result to denial of publication the content.

It's also necessary to set the correct mime type in the header application/x-www-form-urlencoded:

func (self *Twitter) MediaInit(media []byte) (*MediaInitResponse, error) {
    form := url.Values{}
    form.Add("command", "INIT")
    form.Add("media_type", "video/mp4")
    form.Add("total_bytes", fmt.Sprint(len(media)))

    fmt.Println(form.Encode())

    req, err := http.NewRequest("POST",
        MediaUpload, strings.NewReader(form.Encode()))

    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    res, err := self.client.Do(req)

    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    fmt.Println("response", string(body))

    var mediaInitResponse MediaInitResponse
    err = json.Unmarshal(body, &mediaInitResponse)

    if err != nil {
        return nil, err
    }

    fmt.Println("Initialized media: ", mediaInitResponse);

    return &mediaInitResponse, nil
}

Upload of content

After we take right response with media_id, which as a rule stores some big integer value, we can upload our content. It should be borne in mind, despite of limit in 15MB we can't upload all video in one chunk.

At first, Twitter will return error for chunks more 5MB. At second, we can't show smooth changes in progress bar in case of uploading of a content by big chunks.

So it will be optimally to split content on 500KB chunks. The chunks can be uploaded in arbitrary order.

func (self *Twitter) MediaAppend(mediaId uint64, media []byte) error {
    step := 500 * 1024
    for s := 0; s * step < len(media); s++ {
        var body bytes.Buffer
        rangeBegining := s * step
        rangeEnd := (s + 1) * step
        if rangeEnd > len(media) {
            rangeEnd = len(media)
        }

        fmt.Println("try to append ", rangeBegining, "-", rangeEnd)

        w := multipart.NewWriter(&body)

        w.WriteField("command", "APPEND")
        w.WriteField("media_id", fmt.Sprint(mediaId))
        w.WriteField("segment_index", fmt.Sprint(s))

        fw, err := w.CreateFormFile("media", "example.mp4")

        fmt.Println(body.String())

        n, err := fw.Write(media[rangeBegining:rangeEnd])

        fmt.Println("len ", n)

        w.Close()

        req, err := http.NewRequest("POST", MediaUpload, &body)

        req.Header.Add("Content-Type", w.FormDataContentType())

        res, err := self.client.Do(req)
        if err != nil {
            return err
        }

        resBody, err := ioutil.ReadAll(res.Body)
        fmt.Println("append response ", string(resBody))
    }

    return nil
}

Note the line req.Header.Add("Content-Type", w.FormDataContentType()). It plays important role in upload, because it makes our request multipart. Without this line Twitter will returns error of upload, because multipart body is required by server.

Finalization content upload

After receiving a successful response on every chunk we should finalize upload process. As a rule if type of content of length of data were set incorrectly we take error on this step of upload (before sending our message):

func (self *Twitter) MediaFinilize(mediaId uint64) error {
    form := url.Values{}
    form.Add("command", "FINALIZE")
    form.Add("media_id", fmt.Sprint(mediaId))

    req, err := http.NewRequest("POST",
        MediaUpload, strings.NewReader(form.Encode()))

    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    res, err := self.client.Do(req)
    if err != nil {
        return err
    }

    body, err := ioutil.ReadAll(res.Body)
    fmt.Println("final response ", string(body))

    return nil
}

Send message with attached media

Now we can set media_id as parameter in our message after successful upload.

It should be borne in mind, the media_id should be set in field media_ids even for a video. It's can be single string e.g. status=text&media_ids=1234567890.

func (self *Twitter) UpdateStatusWithMedia(text string, mediaId uint64) error {
    form := url.Values{}
    form.Add("status", text)
    form.Add("media_ids", fmt.Sprint(mediaId))

    req, err := http.NewRequest("POST",
        StatusUpdate, strings.NewReader(form.Encode()))

    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    res, err := self.client.Do(req)
    if err != nil {
        return err
    }

    body, err := ioutil.ReadAll(res.Body)
    fmt.Println("status response ", string(body))

    return nil
}

What if nothing works?

  • Check a size of your video should be <15MB
  • Check a type of your video is mp4
  • Check a duration of your video <30s
  • Check width, height and resolution of your video are respond of Twitter requirements
  • Try to upload your video through browser
  • Try to upload your video through another browsers

The requirements of Twitter for video are described here: https://dev.twitter.com/rest/public/uploading-media#videorecs

Conclusion

Despite the difficulties, direct upload through official API is very good thing for developers.

Source code: https://github.com/kreshikhin/twitter-media-uploader

Official Twitter manual: https://dev.twitter.com/rest/reference/post/media/upload