ffmpegwasm / ffmpeg.wasm

FFmpeg for browser, powered by WebAssembly
https://ffmpegwasm.netlify.app
MIT License
13.93k stars 803 forks source link

ffprobe.wasm #121

Open loretoparisi opened 3 years ago

loretoparisi commented 3 years ago

Is your feature request related to a problem? Please describe. Implement ffprobe wasm version.

Describe the solution you'd like ffprobe is the necessary companion of ffmpeg, needed to analyze media file before processing .

Describe alternatives you've considered In this simple case I'm using the command line ffprobe via execFile to probe the file

probe = function (fpath) {
      var self = this;
      return new Promise((resolve, reject) => {
        var loglevel = self.logger.isDebug() ? 'debug' : 'warning';
        const args = [
          '-v', 'quiet',
          '-loglevel', loglevel,
          '-print_format', 'json',
          '-show_format',
          '-show_streams',
          '-i', fpath
        ];
        const opts = {
          cwd: self._options.tempDir
        };
        const cb = (error, stdout) => {
          if (error)
            return reject(error);
          try {
            const outputObj = JSON.parse(stdout);
            return resolve(outputObj);
          } catch (ex) {
            self.logger.error("MediaHelper.probe failed %s", ex);
            return reject(ex);
          }
        };
        cp.execFile('ffprobe', args, opts, cb)
          .on('error', reject);
      });
    }//probe

or in this case to seek to position in the media file:

seek = function (fpath, seconds) {
      var self = this;
      return new Promise((resolve, reject) => {
        var loglevel = self.logger.isDebug() ? 'debug' : 'panic';
        const args = [
          '-hide_banner',
          '-loglevel', loglevel,
          '-show_frames',//Display information about each frame
          '-show_entries', 'frame=pkt_pos',// Display only information about byte position
          '-of', 'default=noprint_wrappers=1:nokey=1',//Don't want to print the key and the section header and footer
          '-read_intervals', seconds + '%+#1', //Read only 1 packet after seeking to position 01:23
          '-print_format', 'json',
          '-v', 'quiet',
          '-i', fpath
        ];
        const opts = {
          cwd: self._options.tempDir
        };
        const cb = (error, stdout) => {
          if (error)
            return reject(error);
          try {
            const outputObj = JSON.parse(stdout);
            return resolve(outputObj);
          } catch (ex) {
            self.logger.error("MediaHelper.probe failed %s", ex);
            return reject(ex);
          }
        };
        cp.execFile('ffprobe', args, opts, cb)
          .on('error', reject);
      });
    }//seek

Additional context Probe media files before processing; seek to media position;

alexcarol commented 3 years ago

@loretoparisi have you tried using https://github.com/alfg/ffprobe-wasm ? It probably needs a little polishing, but it should get the job done.

jaefloo commented 3 years ago

I'm currently working on a NodeJS project that needs ffprobe. Thank you @loretoparisi for the workaround.

goatandsheep commented 3 years ago

You don't really need ffprobe to get the info. As long as we can get ffmpeg -i video.mp4, be able to write the output log to a txt file, then parse to JSON, that would be nice. Unfortunately I haven't even been able to do that.

loretoparisi commented 3 years ago

@goatandsheep I'm not sure that ffmpeg -i can output all info as ffprobe, may be yes.

captn3m0 commented 3 years ago

I was looking to get the "file checksum" for Audible AAX files and while it doesn't show up with ffmpeg -i, it does work with ffmpeg -v info -i *.aax (or other verbosity values - quiet,panic,fatal,error,warning,info,verbose,debug,trace). So if what you're looking for isn't in the default output, I'd suggest dialing it up.

Is there a way to get ffmpeg -i output as a nice JSON? That would be nice.

loretoparisi commented 3 years ago

@captn3m0 thanks I will have a look at -v info. For JSON, in ffprobe it's '-print_format', 'json', in ffmpeg I never tried id.

captn3m0 commented 3 years ago

-print_format is a ffprobe only option :(

brunomsantiago commented 2 years ago

Any plan on support that? It would be awesome to get format and stream metadata on browser. Something like that: ffprobe -hide_banner -loglevel fatal -show_error -show_format -show_streams -print_format json video.mp4 ffprobe is so much faster than ffmpeg because it don't try to read the entire file.

crazoter commented 2 years ago

Recently I had a use case where I needed to perform a duration check on media files on the browser before uploading to the server. I'll put my approach here while I built my POC as it is somewhat related.

Use case For context, my use case is as follows:

ffprobe-wasm is not the full ffprobe program

  1. First, I cloned https://github.com/alfg/ffprobe-wasm. If you're trying to replicate the steps, you should refer to my fork which includes the updated dockerfile.
  2. I noticed that the ffmpeg version was very old. I updated the dockerfile to resolve that and use the latest ffmpeg (now using git instead of the snapshotted tarball). I then built their wasm module via docker-compose run ffprobe-wasm make.
  3. I then jumped into the running docker container with an interactive bash. I navigated to the ffmpeg directory that was downloaded to the tmp file, and manually compiled ffprobe.o and fftools.cmdutils. If I remember correctly, I executed:
emmake make fftools/ffprobe.o fftools/cmdutils.o
emcc --bind \
    -O3 \
    -L/opt/ffmpeg/lib \
    -I/opt/ffmpeg/include/ \
    -s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, getValue, setValue, writeAsciiToMemory]" \
    -s INITIAL_MEMORY=268435456 \
    -lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 \
    -pthread \
    -lworkerfs.js \
    -o ffprobe.js \
        ffprobe.o cmdutils.o
  1. To test the files, I adapted emscripten's https://github.com/emscripten-core/emscripten/blob/main/src/shell_minimal.html to use the compiled ffprobe_g and made some modifications to the generated JS code to call the main function directly (ffprobe_g.max.js is beautified from ffprobe_g. As to why there's ffprobe_g and ffprobe, I did not investigate the reason). To run the file locally, I used Servez.

What I ended up using

// Toy example
const maxSliceLength = Math.min(1024*1024*5, oFiles[nFileId].size);
const slicedData = oFiles[nFileId].slice(0, maxSliceLength);
(async () => {
  ffmpeg.FS('writeFile', 'testfile', await fetchFile(slicedData));
  await ffmpeg.run('-i', 'testfile', '-hide_banner');
  ffmpeg.FS('unlink', 'testfile');
})();

Hopefully this write-up will benefit someone looking for a similar solution, or someone hoping to port ffprobe to wasm.

brunomsantiago commented 2 years ago

@crazoter What amazing post! Thank you so much. Got thrilled at each paragraph for in the end discover this amazing mediainfo.js, which apparently suits perfect for my needs. I am very happy now!

alfg commented 2 years ago

@crazoter Nice writeup! Thanks for checking out ffprobe-wasm:

Still, for anyone interested in porting ffprobe to wasm, I think this is a step in the right direction and can be worth exploring. I am actually quite curious why the original authors of ffprobe-wasm didn't just compile the whole file.

I chose not to compile the FFprobe program, but instead to use libav directly to re-implement the functionality of FFprobe as an API via Wasm as an experiement, rather than the CLI through the browser. A different approach since you can interface with libavcodec and libavformat directly and provide minimal results. Though it's a bit more work to re-implement the full functionality of FFProbe, of course.

tfoxy commented 2 years ago

Hi everyone! I created an npm package a few months back. Repo is here: https://github.com/tfoxy/ffprobe-wasm . It comes with TS definitions.

I needed to use ffprobe in browser and Node.js so that I could read metadata without being affected by file size, so I tried to package the code at https://github.com/alfg/ffprobe-wasm so that it could be used as a library. The output tries to mimic the command

ffprobe -hide_banner -loglevel fatal -show_format -show_streams -show_chapters -show_private_data -print_format json

I don't know much about Emscripten or libavcodec/libavformat, so there are some properties that are missing. But hopefully this can be enough for some people.

EDIT: @crazoter thanks for providing those alternatives. In one project I only need the duration, so that solution of using HTMLMediaElement.duration is great! Also didn't know about mediainfo.js. Only thing that is not clear to me is if it needs to read the whole file to extract some of the metadata.

jaruba commented 1 year ago

@tfoxy mediainfo.js does not need to read the entire file, but imo acts strangely in regards to how much it needs to read, see: https://github.com/buzz/mediainfo.js/issues/108 I see that your ffprobe-wasm project only supports FS, but not HTTP(s)? Are there any plans to support HTTP(s) too? (my interest is in retrieving chapter data of videos in nodejs, not the browser)

piesuke commented 6 months ago

How is the progress of this issue?

loretoparisi commented 5 months ago

Hi everyone! I created an npm package a few months back. Repo is here: https://github.com/tfoxy/ffprobe-wasm . It comes with TS definitions.

I needed to use ffprobe in browser and Node.js so that I could read metadata without being affected by file size, so I tried to package the code at https://github.com/alfg/ffprobe-wasm so that it could be used as a library. The output tries to mimic the command

ffprobe -hide_banner -loglevel fatal -show_format -show_streams -show_chapters -show_private_data -print_format json

I don't know much about Emscripten or libavcodec/libavformat, so there are some properties that are missing. But hopefully this can be enough for some people.

EDIT: @crazoter thanks for providing those alternatives. In one project I only need the duration, so that solution of using HTMLMediaElement.duration is great! Also didn't know about mediainfo.js. Only thing that is not clear to me is if it needs to read the whole file to extract some of the metadata.

That's amazing! I was able to build and run the docker container and the application. But I'm struggling to import the generated module ffprobe-wasm.js:

.
├── ffprobe-wasm.js
├── ffprobe-wasm.wasm
├── ffprobe-wasm.worker.js

within NodeJS, In fact if I try to load the module as usal

const Module = require('./ffprobe-wasm.js');
    const versions = {
        libavutil:  Module.avutil_version(),
        libavcodec:  Module.avcodec_version(),
        libavformat:  Module.avformat_version(),
    };

I get an TypeError: Module.avutil_version is not a function error