fluent-ffmpeg / node-fluent-ffmpeg

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

mp4 buffer as input fails to convert a video #932

Open slushnys opened 4 years ago

slushnys commented 4 years ago

Version information

Code to reproduce

A buffer comes from browser to node.js read by FileReader.

// videoArrayBuffer is a buffer coming from browser
let readableVideoBuffer = new stream.PassThrough();
    readableVideoBuffer.write(videoArrayBuffer);
    readableVideoBuffer.end()
ffmpeg()
      .input(readableVideoBuffer)
      .inputFormat("mp4")
      .outputOptions([
        "-pix_fmt yuv420p",
        "-movflags frag_keyframe+empty_moov",
        "-movflags +faststart"
      ])
      // .videoCodec("libx264")
      .toFormat("mp4")
      .save("test.mp4")

(note: if the problem only happens with some inputs, include a link to such an input file)

Expected results

expecting a properly saved video

Observed results

ffmpeg stderr: ffmpeg version 4.1.3 Copyright (c) 2000-2019 the FFmpeg developers built with Apple LLVM version 10.0.1 (clang-1001.0.46.4) configuration: --prefix=/usr/local/Cellar/ffmpeg/4.1.3_1 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags='-I/Library/Java/JavaVirtualMachines/adoptopenjdk-11.0.2.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/adoptopenjdk-11.0.2.jdk/Contents/Home/include/darwin' --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librubberband --enable-libsnappy --enable-libtesseract --enable-libtheora --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-videotoolbox --disable-libjack --disable-indev=jack --enable-libaom --enable-libsoxr libavutil 56. 22.100 / 56. 22.100 libavcodec 58. 35.100 / 58. 35.100 libavformat 58. 20.100 / 58. 20.100 libavdevice 58. 5.100 / 58. 5.100 libavfilter 7. 40.101 / 7. 40.101 libavresample 4. 0. 0 / 4. 0. 0 libswscale 5. 3.100 / 5. 3.100 libswresample 3. 3.100 / 3. 3.100 libpostproc 55. 3.100 / 55. 3.100 [mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f9715001400] stream 0, offset 0x4f: partial file [mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f9715001400] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 1280x720, 10843 kb/s): unspecified pixel format Consider increasing the value for the 'analyzeduration' and 'probesize' options Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pipe:0': Metadata: major_brand : mp42 minor_version : 0 compatible_brands: mp41isom creation_time : 2019-07-10T15:03:19.000000Z Duration: 00:00:02.13, start: 0.033333, bitrate: N/A Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 1280x720, 10843 kb/s, 30 fps, 30 tbr, 30k tbn, 60k tbc (default) Metadata: creation_time : 2019-07-10T15:03:19.000000Z handler_name : VideoHandler encoder : AVC Coding Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264)) [mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f9715001400] stream 0, offset 0x4f: partial file pipe:0: Invalid data found when processing input Cannot determine format of input stream 0:0 after EOF Error marking filters as finished Conversion failed!

Coriou commented 4 years ago

I have similar issues when using streams as inputs and / or outputs.

For example this works as you'd expect it to:

new ffmpeg(path.resolve("./debug-input.mp4"))
    .toFormat("mp4")
    .save(path.resolve("./debug.mp4"))

While this doesn't:

const input = fs.createReadStream(path.resolve("./debug-input.mp4"))
const output = fs.createWriteStream(path.resolve("./debug.mp4"))

new ffmpeg(input)
    .toFormat("mp4")
    .pipe(output)

Using a stream as input yields ffmpeg exited with code 1: pipe:0: Invalid data found when processing input Cannot determine format of input stream 0:0 after EOF - I've tried other types of NodeJS readable / writable streams with the same results exactly.

Similarly, using an output stream also yields an error:

const output = fs.createWriteStream(path.resolve("./debug.mp4"))
new ffmpeg(path.resolve("./debug-input.mp4"))
    .toFormat("mp4")
    .pipe(output)

ffmpeg exited with code 1: Conversion failed! (I'm guessing it can't write to the output stream)

I'm running ffmpeg 4.2.1 on MacOS and fluent-ffmpeg 2.1.2

njoyard commented 4 years ago

You have to specify the input format, ffmpeg usually uses the file extension to automatically find the format

Coriou commented 4 years ago

@njoyard I don't think it's the issue. I did try specifying the input format:

const input = fs.createReadStream(path.resolve("./debug-input.mp4"))
new FFMPEG(input)
    .inputFormat("mp4")
    .toFormat("mp4")
    .save(path.resolve("./debug.mp4"))

For the same Invalid data found when processing input

Also, this wouldn't explain why the output stream is also failing as I do specify a foFormat.

muhammadharis commented 4 years ago

@Coriou did you end up solving this? I'm getting the same problem.

Coriou commented 4 years ago

Nope, haven't looked back at it. I was thinking about what would be the best way to reproduce this directly with the ffmpeg binary (not using this node wrapper) but then got caught up in other things and never looked back at this issue since

adrian-kriegel commented 4 years ago

I am also experiencing this very error with a readable stream coming from node-ftp. Filestreams work fine though.

Coriou commented 4 years ago

@adrian-kriegel Could you show me a piece of code using filestreams that works for you please ?

adrian-kriegel commented 4 years ago

@Coriou I just tested it using a filestream as an output aswell as using one as the input. Using a filestream for the output does in fact not work for me. This works though: ffmpeg(fs.createReadStream('video.mp4')).inputFormat('mp4').[other commands] It seems to be the same code you posted. I am guessing that out mp4 files differ in some way.

slushnys commented 4 years ago

It has been some time since I opened this issue, but I think I solved my issue by the following:

  1. Create a PassThrough stream to write your required bytes. const inputForFFMPEG = new stream.PassThrough()
  2. Pass it to the ffmpeg command and do whatever you want with it. I hope it helps:
ffmpeg(inputForFFMPEG).save('./directoryName/file.mp4').on('end', async () => {
          try {
           // Here I uploaded file to google cloud storage
          } catch (e) {
            // Here and below is an obsessive error checking
            console.log(e);
            throw e
          }
        }).on('error', (err) => {
          console.log(err)
        })
      } catch (e) {
        console.log(e)
        throw e
      }

Note: You can use google cloud to upload files through automatically generated upload link straight to storage and feed ffmpeg with that files URL.

Hope any of this helps to someone in the future. Good luck and happy coding. 🎉

utkarsh914 commented 4 years ago

@Coriou you'll need to add both .outputOptions('-movflags frag_keyframe+empty_moov') and .toFormat("mp4") in case you're using output streams I don't exactly know the reason behind it. But it is something like mp4 needs to write header in the beginning after completing whole encoding.

Coriou commented 4 years ago

Thanks @utkarsh914, didn't work though. This code returns the exact same error:

import fs from "fs"
import path from "path"
import ffmpeg from "fluent-ffmpeg"

const input = fs.createReadStream(path.resolve("./test-input.mp4"))
const output = fs.createWriteStream(path.resolve("./test-output.mp4"))

new ffmpeg(input)
  .inputFormat("mp4")
  .outputOptions("-movflags frag_keyframe+empty_moov")
  .toFormat("mp4")
  .save(output)

Outputs

Error: ffmpeg exited with code 1: pipe:0: Invalid data found when processing input
Cannot determine format of input stream 0:0 after EOF
Error marking filters as finished
Conversion failed!

Even though I'd love for this issue to be resolved or - at least - understand what is wrong, it's a non-blocking issue for me as I just use Ffmpeg directly without any problems whatsoever.

Here the input file's probe:

ffprobe version 4.2.2 Copyright (c) 2007-2019 the FFmpeg developers
  built with Apple clang version 11.0.3 (clang-1103.0.32.59)
  configuration: --prefix=/usr/local/Cellar/ffmpeg/4.2.2_5 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags=-fno-stack-check --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libsoxr --enable-videotoolbox --disable-libjack --disable-indev=jack
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test-input.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
  Duration: 00:04:05.03, start: 0.000000, bitrate: 2259 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 2127 kb/s, 24 fps, 24 tbr, 12288 tbn, 48 tbc (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
federicocarboni commented 3 years ago

I know this is a bit late but, for future reference and for @Coriou This is a limitation of FFmpeg, specifically the MOV/MP4 muxer (switch to Matroska or WebM if you require streaming inputs), it requires output and input to be seekable.

There's the output-only workaround described @utkarsh914, which uses the empty_moov flag. From the FFmpeg Docs:

[writes] an initial moov atom directly at the start of the file, without describing any samples in it.

This works for outputs, but for inputs, there's no workaround I know about.

Coriou commented 3 years ago

Thanks @FedericoCarboni, glad to know what the issue is !

smaznet commented 3 years ago

i used this option and fixed thanks @FedericoCarboni

.addOutputOption('-movflags','frag_keyframe+empty_moov')
zbagley commented 2 years ago

This was INCREDIBLY hard to track down, and it appears to be a very long lasting bug. I would opt to prioritize updating the documents with a warning when either input or output are recognized as streams (code internal docs). Also, the lack of clear error here caused a multiple hour chase and it appears this is a 2 yr old known issue that deserves a dedicated error message at minimum or at least noting this issue on the generic message.

Appreciate the explanation from @FedericoCarboni

chetrit commented 2 years ago

Adding .addOutputOption('-movflags','frag_keyframe+empty_moov') didn't solve the problem

I still have the same error:

Error: ffmpeg exited with code 1: pipe:0: Invalid data found when processing input
Cannot determine format of input stream 0:0 after EOF

Do you have any ideas ? Or other possible fixes ?

federicocarboni commented 2 years ago

@chetrit as I said movflags is output-only. There is no way I know of to stream MP4 inputs to FFmpeg.

chetrit commented 2 years ago

So @FedericoCarboni , I need to save it locally, or to preconvert mp4 to another file format which is compatible with streams ?

federicocarboni commented 2 years ago

Either would work, you can download the file and then give the file path to ffmpeg, or stream the file in a format which supports it (e.g. Matroska/WebM).

chetrit commented 2 years ago

How can I stream the file in let's say webM ? you mean "preconverting" it in this format before restreaming it to fluent-ffmpeg ?

federicocarboni commented 2 years ago

If, for example, you are downloading a video from a server, if you have control over the server then you can convert video files to a suitable format (let's say WebM) before they are sent over the network. If you don't have that option then you must download the entire MP4/MOV video before you can process it through ffmpeg.

chetrit commented 2 years ago

Thanks, I see,

a last question: Will ffmpeg (or fluent ffmpeg) will ever implement this feature and make pipes with mp4 work ?

federicocarboni commented 2 years ago

@chetrit probably not, since this is a limitation of the format itself

stlr00 commented 1 year ago

Everything is somewhat more complicated. The problem is that metadata in mp4 can be not only at the beginning of the file, but also at the end. And accordingly ffpmeg cannot get them. If you try on the client like this: ffmpeg -i input.mp4 -movflags faststart -acodec copy -vcodec copy output.mp4 And then send output file to the server, then everything will work. Hope it was helpful!

ShiningRush commented 1 year ago

hi, @stlr00, I covert the mp4 file as you said, but when I run the command as below:

cat ./out.mp4 | ffmpeg -f mp4 -re -i pipe: -f flv 'rtmp://localhost'

It return the error moov atom not found, have any idea?

jordyvanraalte commented 1 year ago

For us, using paths referring to the video as input solved this issue.

FrenchMajesty commented 1 year ago

As of April 2023, this is also an issue I've experienced.

I cannot convert a file when the input and/or output are streams. My workaround is to:

As you can imagine this is very inefficient. I also run the risk using all the (limited) HDD space in my micro-service if the video & audio files are long. Ideally I'd work directly with the streams to avoid this issue.

Undead34 commented 1 year ago

Hi, I have problems with the duration of the converted audio, I don't know if it's my FFmpeg, but my audio, it has no duration when I save it with createWriteStream() or using pipe() I don't know why, but it's very rare.

    const inputStream = fs.createReadStream(inputFile);
    const outputStream = fs.createWriteStream(outputFile);

    const command = ffmpeg(inputStream)
      .outputOptions(["-c:a flac"]) // Utilizar el códec flac para la salida
      .output(outputStream)
      .on("end", () => {
        console.log("Transcodificación completada");
      })
      .on("error", (err) => {
        console.error("Error en la transcodificación:", err);
      });

    command.run();

    // Obtener la información de duración usando ffprobe
    ffmpeg.ffprobe(outputFile, (err, metadata) => {
      if (err) {
        console.error("Error al obtener información de duración:", err);
        return;
      }

      // Access file duration in seconds
      const duration = metadata.format.duration;
      console.log(`Duración del archivo: ${duration} segundos`);
    });

And when I use FFprobe on the newly saved file I get this error.

Error al obtener informaci├│n de duraci├│n: Error: ffprobe exited with code 1
ffprobe version 4.0.2 Copyright (c) 2007-2018 the FFmpeg developers
  built with gcc 7.3.1 (GCC) 20180722
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-bzlib --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom 
--enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth
  libavutil      56. 14.100 / 56. 14.100
  libavcodec     58. 18.100 / 58. 18.100
  libavformat    58. 12.100 / 58. 12.100
  libavdevice    58.  3.100 / 58.  3.100
  libavfilter     7. 16.100 /  7. 16.100
  libswscale      5.  1.100 /  5.  1.100
  libswresample   3.  1.100 /  3.  1.100
  libpostproc    55.  1.100 / 55.  1.100
C:\Users\Undead34\Music\281e7401-cd5b-4a23-a29f-07f6ead061b3: Invalid data found when processing input

    at ChildProcess.<anonymous> (C:\Users\Undead34\Videos\hola\.webpack\main\index.js:5896:22)
    at ChildProcess.emit (node:events:513:28)
    at ChildProcess._handle.onexit (node:internal/child_process:291:12)
Error en la transcodificaci├│n: Error: ffmpeg exited with code 1: pipe:1: Invalid argument

    at ChildProcess.<anonymous> (C:\Users\Undead34\Videos\hola\.webpack\main\index.js:7677:22)
    at ChildProcess.emit (node:events:513:28)
Bug-Reaper commented 1 year ago

For people with input mp4 file issues: Something like this seems to work.

ffmpeg().input("<direct https link to mp4 file>")

No idea if FFMPEG will stream urls intelligently behind the scenes or if it downloads the entire video and then converts... But atleast it works :)

pvd232 commented 10 months ago

@slushnys @Coriou Calling ffmpeg via CLI directly might be the solution. the following code is working for me (calling ffmpeg from Node server):

const bufferStream = new streamBuffers.ReadableStreamBuffer({
    initialSize: mp3Buffer.length,
  });
  bufferStream.put(mp3Buffer);
  bufferStream.stop();

  let wavBuffer = new streamBuffers.WritableStreamBuffer({
    initialSize: mp3Buffer.length,
  });

  const ffmpeg = spawn('ffmpeg', [
    '-i', 'pipe:0', 
    '-f', 'wav', 
    '-acodec', 'pcm_mulaw', 
    '-ar', '8000', 
    '-movflags', 'frag_keyframe+empty_moov',
    'pipe:1'
  ]);

  ffmpeg.stdout.pipe(wavBuffer);

  ffmpeg.on('close', (code) => {
    callback(null, wavBuffer.getContents());
  });

  ffmpeg.on('error', (err) => {
    callback(err, null);
  });

  bufferStream.pipe(ffmpeg.stdin);
portwatcher commented 9 months ago

@slushnys @Coriou Calling ffmpeg via CLI directly might be the solution. the following code is working for me (calling ffmpeg from Node server):

const bufferStream = new streamBuffers.ReadableStreamBuffer({
    initialSize: mp3Buffer.length,
  });
  bufferStream.put(mp3Buffer);
  bufferStream.stop();

  let wavBuffer = new streamBuffers.WritableStreamBuffer({
    initialSize: mp3Buffer.length,
  });

  const ffmpeg = spawn('ffmpeg', [
    '-i', 'pipe:0', 
    '-f', 'wav', 
    '-acodec', 'pcm_mulaw', 
    '-ar', '8000', 
    '-movflags', 'frag_keyframe+empty_moov',
    'pipe:1'
  ]);

  ffmpeg.stdout.pipe(wavBuffer);

  ffmpeg.on('close', (code) => {
    callback(null, wavBuffer.getContents());
  });

  ffmpeg.on('error', (err) => {
    callback(err, null);
  });

  bufferStream.pipe(ffmpeg.stdin);

This doesn't work for me. My case is trancoding video file into streaming chunks of dash protocol though.

aivils commented 6 months ago

In my case, only the old best file system helped.

const ffmpeg = require('fluent-ffmpeg')
const extractFrame = require('ffmpeg-extract-frame');
const { PassThrough } = require('stream');
const concat = require('concat-stream');
const tmp = require('tmp');
const fs = require('fs');

const streamToBuffer = stream => {
  return new Promise((resolve, reject) => {
    stream.pipe(concat({ encoding: 'buffer' }, (data) => resolve(data)));
  })
};

const videoProbe = file => {
  return new Promise((resolve, reject) => {
    ffmpeg(file)
    .ffprobe((err, data) => {
      if (err) {
        reject(err);
        return;
      }

      const videoMetadata = data.streams.find(item => item.codec_type === 'video');
      const metadata = {
        width: videoMetadata?.width,
        height: videoMetadata?.height,
      }

      resolve(metadata);
    });
  });
}

const videoExtractFrame = ({ opts, stream }) => {
  const input = new PassThrough();
  stream.pipe(input);

  return new Promise(async (resolve, reject) => {
    const videoTmp = tmp.fileSync();
    const posterTmp = tmp.fileSync({ postfix: `.${opts.fileFormat}` });
    const videoFilePath = videoTmp.name;
    const posterFilePath = posterTmp.name;

    const buf = await streamToBuffer(input);
    fs.writeSync(videoTmp.fd, buf);

    const metadata = await videoProbe(videoFilePath);

    await extractFrame({
      input: videoFilePath,
      output: posterFilePath,
      offset: opts.offset || 0,
    })

    const data = fs.readFileSync(posterFilePath);
    videoTmp.removeCallback();
    posterTmp.removeCallback();

    resolve({
      data,
      info: metadata,
    });
  });
}

module.exports = {
  videoExtractFrame,
};
exo-pla-net commented 5 months ago

In my case, only the old best file system helped.

This downloads the whole video into the buffer, so it's not useful for people trying to save memory by piping a remotely-hosted .mp4 into ffmpeg.

baotlake commented 3 months ago

Commenting out .inputFormat("mp4") might solve the problem.

lindionez commented 2 months ago

Converting the video using ffmpeg -i input.mp4 -movflags faststart out.mp4" and then using out.mp4 solved my problem completely