fluent-ffmpeg / node-fluent-ffmpeg

A fluent API to FFMPEG (http://www.ffmpeg.org)
MIT License
7.92k stars 879 forks source link

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

Open GordoRank opened 8 years ago

GordoRank commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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');
});
dam-ien commented 8 years ago

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 commented 8 years ago

@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 commented 7 years ago

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?

youshy commented 5 years ago

Any new developments? I have almost the same issue - the only difference is that I want to do a slideshow from my mp4 video.

nunowar commented 5 years ago

When I'm using ffmpeg with buffer as input the output video is incomplete:

let inStream = streamifier.createReadStream(file.Body);

ffmpeg(inStream).inputFormat('gif')
     .outputOptions(['-pix_fmt yuv420p', '-movflags frag_keyframe+empty_moov', '-movflags +faststart'])
     .toFormat('mp4').save('test.mp4')
     .on('error', function(err, stdout, stderr) {
          console.log('Cannot process video: ' + err.message);
     })
     .on('end', function() {
          console.log('Finished processing');
     });

but if i use the same input file as file the video is ok, some help to find the problem?

i19k commented 2 years ago

When specifing the input format as "gif_pipe", it worked for me but I'm getting 0 seconds videos for some gifs. When I give filepath, it is working well but I cannot make it work for gif streams.

Gokulnath31 commented 1 year ago

Even I am facing the same issue. Giving input format as gif_type gives 1-sec videos