Uploading video in node.js - always 400 media type unrecognized

media
video
api

#1

In node.js, I am retrieving an image via request in binary mode and then using POST media/upload with media:myBufferedData. This works fine for jpg images.

However, when I do the same with mp4 videos, it fails with the 400 media type unrecognized response. I am trying with tiny 100K videos. If I write them out to the file system, I can view them fine on my machine.

v1.2.5 of Twitter module


#2

Hi @michaelbrill, could you include a video example so we can reproduce the issue? And could you point specifically to which Twitter module you are using? Thanks!


#3

Hi, thanks for the response:

Here is the video: https://dl.dropboxusercontent.com/u/26843720/temp/video.mp4

The module I am using is this: https://www.npmjs.com/package/twitter

Sample code is:
fs.readFile(‘video.mp4’, function (err, body) {
twitterClient.post(‘media/upload’, {media: body}, function (error, media, response) {
});
});

Error is: 400 media type unrecognized.

Works fine with images


#4

Ah, I see the issue.

There is nothing wrong with the video itself. There are two ways to invoke the media/upload endpoint; sending the file all at once and chunking the data. The npm library you are using only supports the first type at the moment, but videos MUST be uploaded using the chunked method. I’ll reach out to the contributors to see if they are still maintaining the library and see if this is something they can add.

For more detail, I ran some tests directly on the API. Here’s what you get with the “all at once” method:

twurl -H upload.twitter.com -X POST "/1.1/media/upload.json" --file "video.mp4" --file-field "media"
{"request":"\/1.1\/media\/upload.json","error":"media type unrecognized."}

But if I use the same file with the chunked method, the upload and Tweet goes through.

INIT call and response:

twurl -H upload.twitter.com "/1.1/media/upload.json" -d "command=INIT&media_type=video/mp4&total_bytes=27128"
{"media_id":630127357085544448,"media_id_string":"630127357085544448","expires_after_secs":86399}

APPEND call:

twurl -H upload.twitter.com "/1.1/media/upload.json" -d "command=APPEND&media_id=630127357085544448&segment_index=0" --file video.mp4 --file-field "media"

FINALIZE call and response:

twurl -H upload.twitter.com "/1.1/media/upload.json" -d "command=FINALIZE&media_id=630127357085544448"
{"media_id":630127357085544448,"media_id_string":"630127357085544448","size":27128,"expires_after_secs":86400,"video":{"video_type":"video\/mp4"}}

Tweet post and truncated response:

twurl "/1.1/statuses/update.json" -d "status=testing video upload.&media_ids=630127357085544448"
{"created_at":"Sat Aug 08 21:26:19 +0000 2015","id":630127924721647616....}

#5

I will also mention to engineering that the error message should be changed to mention chunked upload in the case of video files instead of the misleading “media type unrecognized.” (Internal tracking: PREL-15363)


#6

Great… thanks so much for finding the problem. Chunking it is.


#7

Do you know if there is a working Javascript example of uploading chunked media to Twitter? Having no luck.


#8

If you’re still stuck on this I just finished a POC of uploading videos to twitter using node


#9

That’d be awesome… the solution I have now is to call out to twurl… not particularly elegant.


#10

I am trying to get this working in Node. If someone has a working example it would be much appreciated.


#11

Here you go guys:

How to upload a video using node


#12

Thanks piotrstomasik but I still get the following error

FINALIZED 400 {“request”:"/1.1/media/upload.json",“error”:“Invalid or unsupported media, Reason: UnsupportedMedia.”}


#13

I have got this working now. It was related to the video I was uploading. I tried a different MP4 and it worked. Thanks again.


#14

The video needs to be encoded with libx264. The mpeg4 encoder is not handle on the twitter side API.
Anywhere to find some recommendation about best choice of codecs ? For instance what about sound : mp3 / AAC / AC3 ?

Thanks alot !


#15

Hello @gabrielstuff,

Have you news on the audio format? I am looking for the same solution as you, it would be cool if you share the solution :slight_smile:

Thanks.


#16

Per the uploading media documentation:

  • Audio should be mono or stereo
  • Audio must be AAC with Low Complexity profile. High-Efficiency AAC is not supported.

#17

Hi,

Here is the configuration of the generated file (adding an audio file, and export to video.mp4).

var = {videoOptions
   fps: 25
   loop: 20 seconds //
   transition: true,
   transitionDuration: 1, // seconds
   videoBitrate 1024,
   videoCodec 'libx264'
   size '640x?'
   audioBitrate '128k'
   audioCodec 'libfdk_aac'
   audioChannels: 2,
   format: 'MP4'
}

To upload the file is not recognized:

FINALIZED 400 {"request": "\ / 1.1 \ / media \ /upload.json", "error": "InvalidContent."}
STATUS: 400 {"request": "\ / 1.1 \ / media \ /upload.json", "error": "Invalid MediaID."}

Could you help me please ?


#18

Can you share an example file online that is returning this API response?


#19

Yes of course : https://www.dropbox.com/s/5qvx2nbn4rfy8h9/video.mp4?dl=0

var videoshow = require('videoshow')
 
var images = [
  'tovelo.jpg'
]
 
var videoOptions = {
  fps: 25,
  loop: 20, // seconds 
  transition: true,
  transitionDuration: 1, // seconds 
  videoBitrate: 1024,
  videoCodec: 'libx264',
  size: '640x?',
  audioBitrate: '128k',
  audioCodec: 'libfdk_aac',
  audioChannels: 2,
  format: 'mp4'
}
 
videoshow(images, videoOptions)
  .audio('tovelo.mp3')
  .save('video.mp4')
  .on('start', function (command) {
    console.log('ffmpeg process started:', command)
  })
  .on('error', function (err, stdout, stderr) {
    console.error('Error:', err)
    console.error('ffmpeg stderr:', stderr)
  })
  .on('end', function (output) {
    console.error('Video created in:', output)
  })

VideoShow

And upload to Twitter :

var bufferLength, filePath, finished, fs, oauthCredentials, offset, request, segment_index, theBuffer;

request = require('request');
fs = require('fs');
filePath = './video.mp4';
bufferLength = 1000000;
theBuffer = new Buffer(bufferLength);
offset = 0;
segment_index = 0;
finished = 0;
oauthCredentials = {
    consumer_key: '',
    consumer_secret: '',
    token: '',
    token_secret: ''
};

fs.stat(filePath, function(err, stats) {
    var formData, normalAppendCallback, options;

    formData = {
        command: "INIT",
        media_type: 'video/mp4',
        total_bytes: stats.size
    };
    options = {
        url: 'https://upload.twitter.com/1.1/media/upload.json',
        oauth: oauthCredentials,
        formData: formData
    };

    normalAppendCallback = function(media_id) {
        return function(err, response, body) {

            finished++;
            if (finished === segment_index) {

                options.formData = {
                    command: 'FINALIZE',
                    media_id: media_id
                };
                request.post(options, function(err, response, body) {
                    console.log('FINALIZED',response.statusCode,body);

                    delete options.formData;

                    //Note: This is not working as expected yet.
                    options.qs = {
                        command: 'STATUS',
                        media_id: media_id
                    };
                    request.get(options, function(err, response, body) {
                        console.log('STATUS: ', response.statusCode, body);
                    });
                });
            }
        };
    };


    request.post(options, function(err, response, body) {
        var media_id;
        media_id = JSON.parse(body).media_id_string;

        fs.open(filePath, 'r', function(err, fd) {
            var bytesRead, data;

            while (offset < stats.size) {

                bytesRead = fs.readSync(fd, theBuffer, 0, bufferLength, null);
                data = bytesRead < bufferLength ? theBuffer.slice(0, bytesRead) : theBuffer;
                options.formData = {
                    command: "APPEND",
                    media_id: media_id,
                    segment_index: segment_index,
                    media_data: data.toString('base64')
                };
                request.post(options, normalAppendCallback(media_id));
                offset += bufferLength;
                segment_index++
            }
        });
    });
}); 

Thanks for your help!


#20

I get a failed upload with our large media upload sample code too, so I suspect there’s an issue with the file format in some way. I’ll have to ask around to determine if that’s the case as I cannot determine what the issue is myself.