New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converting GIF buffer to MP4 buffer without writing to file first #567

Open
GordoRank opened this Issue Jul 15, 2016 · 5 comments

Comments

Projects
None yet
5 participants
@GordoRank

GordoRank commented Jul 15, 2016

I am trying to convert animated GIF's to MP4 videos, on the fly, as the user requests them, after resizing/processing the GIF's first as requested.

After all processing on the GIF is complete I have its contents stored in a Buffer, and I need to finally output the resulting MP4 as a Buffer also.

Currently, I have to write the Buffer to a temporary file, perform the conversion, then read the converted file into a Buffer and finally pass it into the callback.

This is terribly inefficient and I would like to avoid writing to disk. How can I simply convert the GIF buffer into an MP4 buffer?

I tried using streamifier and passing in the GIF Buffer in order to use a readableStream as the source but I ended up getting an End of File Error.

Here is a stripped down version of the current method I am using which involves writing the files to disk before processing:

function makeMP4(gifBuffer, callback){
     fs.writeFile(input.gif,  gifBuffer, function(err) {
         ffmpeg(input.gif).outputOptions([
                        '-movflags faststart',
                        '-pix_fmt yuv420p',
                        '-vf scale=trunc(iw/2)*2:trunc(ih/2)*2'
        ])
        .inputFormat('gif')
        .on('end', function() {         
                        fs.readFile(output.mp4, function(err, mp4Buffer){
                fs.unlink('/tmp/' + src);
                fs.unlink('/tmp/' + dst);
                callback(null, mp4Buffer);                          
            });     
        }).save(output.mp4)         
    });
};

Any help would be greatly appreciated.

@njoyard

This comment has been minimized.

Member

njoyard commented Jul 17, 2016

This is terribly inefficient and I would like to avoid writing to disk.

Unfortunately there aren't many options here. Remember fluent-ffmpeg is just a wrapper library for the ffmpeg executable, so we have to work with what we have. Plus there is no reliable way of connecting both to stdin and stdout without risking deadlocks. So in the end you'll have to write either the input or the output to disk. This may be mitigated by writing to something that's likely to be a ramdisk (eg. /tmp).

If you really want performance, what you need is libav* bindings. I don't think those exist in nodejs.

I tried using streamifier and passing in the GIF Buffer in order to use a readableStream as the source but I ended up getting an End of File Error.

Can you show your code, as well as the full ffmpeg output (as returned in the 'error' handler 2nd and 3rd args) ?

@GordoRank

This comment has been minimized.

GordoRank commented Jul 18, 2016

Thanks for the response njoyard. As requested here is the full console output:

Spawned Ffmpeg with command: ffmpeg -f gif -i pipe:0 -movflags faststart -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -f mp4 pipe:1
Stderr output: ffmpeg version N-80901-gfebc862 Copyright (c) 2000-2016 the FFmpeg developers
Stderr output:   built with gcc 4.8 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
Stderr output:   configuration: --extra-libs=-ldl --prefix=/opt/ffmpeg --mandir=/usr/share/man --enable-avresample --disable-debug --enable-nonfree --enable-gpl --enable-version3 --enable-libopencore-amrnb --enable-libopencore-amrwb --disable-decoder=amrnb --disable-decoder=amrwb --enable-libpulse --enable-libfreetype --enable-gnutls --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-libvorbis --enable-libmp3lame --enable-libopus --enable-libvpx --enable-libspeex --enable-libass --enable-avisynth --enable-libsoxr --enable-libxvid --enable-libvidstab
Stderr output:   libavutil      55. 28.100 / 55. 28.100
Stderr output:   libavcodec     57. 48.101 / 57. 48.101
Stderr output:   libavformat    57. 41.100 / 57. 41.100
Stderr output:   libavdevice    57.  0.102 / 57.  0.102
Stderr output:   libavfilter     6. 47.100 /  6. 47.100
Stderr output:   libavresample   3.  0.  0 /  3.  0.  0
Stderr output:   libswscale      4.  1.100 /  4.  1.100
Stderr output:   libswresample   2.  1.100 /  2.  1.100
Stderr output:   libpostproc    54.  0.100 / 54.  0.100
Stderr output: pipe:0: End of file
Stderr output:
Cannot process video: ffmpeg exited with code 1: pipe:0: End of file
[Error: ffmpeg exited with code 1: pipe:0: End of file]

And here is the example code I am using to test this. For now I have not hooked up the output stream to create a new Buffer, but my understanding is that this should still work.

var inStream = streamifier.createReadStream(gifBuffer);

var command = ffmpeg(inStream)
    .inputFormat('gif')
    .outputOptions([
            '-movflags faststart',
            '-pix_fmt yuv420p',
            '-vf scale=trunc(iw/2)*2:trunc(ih/2)*2'
    ])  
    .toFormat('mp4')    
    .on('start', function(commandLine) {
        console.log('Spawned Ffmpeg with command: ' + commandLine);
    })
    .on('stderr', function(stderrLine) {
        console.log('Stderr output: ' + stderrLine);
    })
    .on('error', function(err, stdout, stderr) {
        console.log('Cannot process video: ' + err.message);
        console.log(stdout);
        console.log(stderr);
    })
    .on('progress', function(progress) {
        console.log('Processing: ' + progress.percent + '% done');
        })  
    .on('end', function() {
        console.log('Finished processing');
    });

var ffstream = command.pipe();
ffstream.on('data', function(chunk) {
    console.log('ffmpeg just wrote ' + chunk.length + ' bytes');
});

@njoyard njoyard added To investigate and removed Needs info labels Jul 18, 2016

@dam-ien

This comment has been minimized.

dam-ien commented Aug 17, 2016

Have you tried commenting out the event:

.on('progress', function(progress) {
    console.log('Processing: ' + progress.percent + '% done');
})  

I use fluent-ffmpeg to do similar: Chrome with WebRTC (webM/opus codec) -> Express (string base64) -> stream -> fluent-ffmpeg WebM to OGG -> stream -> IBM Watson speech to text -> res.send()
Subscribing to 'progress' and having the console.log() in the event interferes with the input pipe.

If you receive the gif file through a request in base64, I would also try to replace usage of streamifier:
var inStream = streamifier.createReadStream(gifBuffer);
by

  var stream = new stream.Readable();
  stream.push(data, 'base64');
  stream.push(null);
@jdp

This comment has been minimized.

Contributor

jdp commented Sep 6, 2016

@GordoRank You can avoid writing the GIF to disk by inputting it as a readable stream, but you will have to write the MP4 to disk. The reason is that the MP4 muxer requires a seekable output, as it jumps around while writing the output, as opposed to only appending it. That means ffmpeg can't stream the MP4 back to node-fluent-ffmpeg, as standard output isn't seekable, and that's the only mechanism available to return output as a stream.

As hinted at by @njoyard, a direct node-libav binding would work because a Buffer would be a seekable output, but there's no way to accomplish that with a library that wraps the ffmpeg program.

@jainn3

This comment has been minimized.

jainn3 commented Jul 13, 2017

I am trying to use ffmpeg with GIFs as input and MP4s as output. Looks like there is no support for gif_pipe and mp4_pipe. I am using shell from Java.
ffmpeg -formats
lists all the supported format
Is gif_pipe and mp4_pipe going to be supported in future?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment