Merge file without data loss using FFmpeg inside of WASM

215 Views Asked by At

Edit: I'm rewriting this entire question

Goal: To reconstruct a video from its pieces/chunks from a network stream inside of an @ffmpeg/ffmpeg worker

Problems:

  1. Video chunks/pieces which come after the first piece/chunk are reported by @ffmpeg/ffmpeg to have invalid data, as seen in the log below:
{
  "type": "stderr",
  "message": "video-0_chunk-1.part: Invalid data found when processing input"
}
  1. How would I merge these chunks/pieces to reconstruct the full video using @ffmpeg/ffmpeg (after solving the first issue above)

My current code situation:

  1. For merging the video pieces
const constructFile = async (chunks: Uint8Array[], queueId: number) => {
  await Promise.all(
    chunks.map(async (chunk, index) => {
      const chunkFile = `video-${queueId}_chunk-${index}`;
      await ffmpeg.writeFile(chunkFile, chunk);

      // Return information about newly created file
      ffmpeg.exec(["-i", chunkFile]);
    })
  );
};

I'm reading the logs/output for

ffmpeg.exec(['-i', chunkFile])

using

ffmpeg.on('log', (log) => console.log(log))
  1. For fetching the videos using streams
await useFetch(Capacitor.convertFileSrc(file.path), {
  responseType: "stream",

  onResponse: async ({ response }) => {
    if (response.body) {
      const reader = response.body.getReader();

      while (true) {
        const { done, value } = await reader.read();

        if (done) break;
        file.chunks.push(value);
      }
      reader.releaseLock();
    }
  },
});

Note: file.chunks is linked to a reactive value which is passed to constructFile() when initialized

These are the logs I get from the code currently above:

chunk-4OF65L5M.js:2710 <Suspense> is an experimental feature and its API will likely change.
(index):298 native App.addListener (#25407936)
(index):298 native FilePicker.pickVideos (#25407937)
(index):272 result FilePicker.pickVideos (#25407937)
(index):298 native VideoEditor.thumbnail (#25407938)
(index):272 result VideoEditor.thumbnail (#25407938)
Processing.vue:135 {type: 'stderr', message: 'ffmpeg version 5.1.3 Copyright (c) 2000-2022 the FFmpeg developers'}
Processing.vue:135 {type: 'stderr', message: '  built with emcc (Emscripten gcc/clang-like repla…3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)'}
Processing.vue:135 {type: 'stderr', message: '  configuration: --target-os=none --arch=x86_32 --…e-libfreetype --enable-libfribidi --enable-libass'}
Processing.vue:135 {type: 'stderr', message: '  libavutil      57. 28.100 / 57. 28.100'}
Processing.vue:135 {type: 'stderr', message: '  libavcodec     59. 37.100 / 59. 37.100'}
Processing.vue:135 {type: 'stderr', message: '  libavformat    59. 27.100 / 59. 27.100'}
Processing.vue:135 {type: 'stderr', message: '  libavdevice    59.  7.100 / 59.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libavfilter     8. 44.100 /  8. 44.100'}
Processing.vue:135 {type: 'stderr', message: '  libswscale      6.  7.100 /  6.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libswresample   4.  7.100 /  4.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libpostproc    56.  6.100 / 56.  6.100'}
Processing.vue:135 {type: 'stderr', message: "Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'video-0_chunk-0':"}
Processing.vue:135 {type: 'stderr', message: '  Metadata:'}
Processing.vue:135 {type: 'stderr', message: '    major_brand     : mp42'}
Processing.vue:135 {type: 'stderr', message: '    minor_version   : 0'}
Processing.vue:135 {type: 'stderr', message: '    compatible_brands: isommp42'}
Processing.vue:135 {type: 'stderr', message: '    creation_time   : 2022-11-29T14:46:32.000000Z'}
Processing.vue:135 {type: 'stderr', message: '  Duration: 00:00:51.50, start: 0.000000, bitrate: 81 kb/s'}
Processing.vue:135 {type: 'stderr', message: '  Stream #0:0[0x1](und): Video: h264 (High) (avc1 …6], 259 kb/s, 30 fps, 30 tbr, 15360 tbn (default)'}
Processing.vue:135 {type: 'stderr', message: '    Metadata:'}
Processing.vue:135 {type: 'stderr', message: '      creation_time   : 2022-11-29T14:46:32.000000Z'}
Processing.vue:135 {type: 'stderr', message: '      handler_name    : ISO Media file produced by Google Inc. Created on: 11/29/2022.'}
Processing.vue:135 {type: 'stderr', message: '      vendor_id       : [0][0][0][0]'}
Processing.vue:135 {type: 'stderr', message: '  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0…706D), 44100 Hz, stereo, fltp, 127 kb/s (default)'}
Processing.vue:135 {type: 'stderr', message: '    Metadata:'}
Processing.vue:135 {type: 'stderr', message: '      creation_time   : 2022-11-29T14:46:32.000000Z'}
Processing.vue:135 {type: 'stderr', message: '      handler_name    : ISO Media file produced by Google Inc. Created on: 11/29/2022.'}
Processing.vue:135 {type: 'stderr', message: '      vendor_id       : [0][0][0][0]'}
Processing.vue:135 {type: 'stderr', message: 'At least one output file must be specified'}
Processing.vue:135 {type: 'stderr', message: 'Aborted()'}
Processing.vue:135 {type: 'stderr', message: 'ffmpeg version 5.1.3 Copyright (c) 2000-2022 the FFmpeg developers'}
Processing.vue:135 {type: 'stderr', message: '  built with emcc (Emscripten gcc/clang-like repla…3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)'}
Processing.vue:135 {type: 'stderr', message: '  configuration: --target-os=none --arch=x86_32 --…e-libfreetype --enable-libfribidi --enable-libass'}
Processing.vue:135 {type: 'stderr', message: '  libavutil      57. 28.100 / 57. 28.100'}
Processing.vue:135 {type: 'stderr', message: '  libavcodec     59. 37.100 / 59. 37.100'}
Processing.vue:135 {type: 'stderr', message: '  libavformat    59. 27.100 / 59. 27.100'}
Processing.vue:135 {type: 'stderr', message: '  libavdevice    59.  7.100 / 59.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libavfilter     8. 44.100 /  8. 44.100'}
Processing.vue:135 {type: 'stderr', message: '  libswscale      6.  7.100 /  6.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libswresample   4.  7.100 /  4.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libpostproc    56.  6.100 / 56.  6.100'}
Processing.vue:135 {type: 'stderr', message: 'video-0_chunk-1: Invalid data found when processing input'}
Processing.vue:135 {type: 'stderr', message: 'Aborted()'}
Processing.vue:135 {type: 'stderr', message: 'ffmpeg version 5.1.3 Copyright (c) 2000-2022 the FFmpeg developers'}
Processing.vue:135 {type: 'stderr', message: '  built with emcc (Emscripten gcc/clang-like repla…3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)'}
Processing.vue:135 {type: 'stderr', message: '  configuration: --target-os=none --arch=x86_32 --…e-libfreetype --enable-libfribidi --enable-libass'}
Processing.vue:135 {type: 'stderr', message: '  libavutil      57. 28.100 / 57. 28.100'}
Processing.vue:135 {type: 'stderr', message: '  libavcodec     59. 37.100 / 59. 37.100'}
Processing.vue:135 {type: 'stderr', message: '  libavformat    59. 27.100 / 59. 27.100'}
Processing.vue:135 {type: 'stderr', message: '  libavdevice    59.  7.100 / 59.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libavfilter     8. 44.100 /  8. 44.100'}
Processing.vue:135 {type: 'stderr', message: '  libswscale      6.  7.100 /  6.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libswresample   4.  7.100 /  4.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libpostproc    56.  6.100 / 56.  6.100'}
Processing.vue:135 {type: 'stderr', message: 'video-0_chunk-2: Invalid data found when processing input'}
Processing.vue:135 {type: 'stderr', message: 'Aborted()'}
Processing.vue:135 {type: 'stderr', message: 'ffmpeg version 5.1.3 Copyright (c) 2000-2022 the FFmpeg developers'}
Processing.vue:135 {type: 'stderr', message: '  built with emcc (Emscripten gcc/clang-like repla…3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)'}
Processing.vue:135 {type: 'stderr', message: '  configuration: --target-os=none --arch=x86_32 --…e-libfreetype --enable-libfribidi --enable-libass'}
Processing.vue:135 {type: 'stderr', message: '  libavutil      57. 28.100 / 57. 28.100'}
Processing.vue:135 {type: 'stderr', message: '  libavcodec     59. 37.100 / 59. 37.100'}
Processing.vue:135 {type: 'stderr', message: '  libavformat    59. 27.100 / 59. 27.100'}
Processing.vue:135 {type: 'stderr', message: '  libavdevice    59.  7.100 / 59.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libavfilter     8. 44.100 /  8. 44.100'}
Processing.vue:135 {type: 'stderr', message: '  libswscale      6.  7.100 /  6.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libswresample   4.  7.100 /  4.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libpostproc    56.  6.100 / 56.  6.100'}
Processing.vue:135 {type: 'stderr', message: 'video-0_chunk-3: Invalid data found when processing input'}
Processing.vue:135 {type: 'stderr', message: 'Aborted()'}
Processing.vue:135 {type: 'stderr', message: 'ffmpeg version 5.1.3 Copyright (c) 2000-2022 the FFmpeg developers'}
Processing.vue:135 {type: 'stderr', message: '  built with emcc (Emscripten gcc/clang-like repla…3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)'}
Processing.vue:135 {type: 'stderr', message: '  configuration: --target-os=none --arch=x86_32 --…e-libfreetype --enable-libfribidi --enable-libass'}
Processing.vue:135 {type: 'stderr', message: '  libavutil      57. 28.100 / 57. 28.100'}
Processing.vue:135 {type: 'stderr', message: '  libavcodec     59. 37.100 / 59. 37.100'}
Processing.vue:135 {type: 'stderr', message: '  libavformat    59. 27.100 / 59. 27.100'}
Processing.vue:135 {type: 'stderr', message: '  libavdevice    59.  7.100 / 59.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libavfilter     8. 44.100 /  8. 44.100'}
Processing.vue:135 {type: 'stderr', message: '  libswscale      6.  7.100 /  6.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libswresample   4.  7.100 /  4.  7.100'}
Processing.vue:135 {type: 'stderr', message: '  libpostproc    56.  6.100 / 56.  6.100'}
Processing.vue:135 {type: 'stderr', message: 'video-0_chunk-4: Invalid data found when processing input'}
Processing.vue:135 {type: 'stderr', message: 'Aborted()'}

Notes:

  1. The sections which start with Processing.vue come from the logging system I've setup.
  2. The pieces/chunks gotten from the network where stored in exactly the same order in which they came
  3. If you've seen the old question, the ReferenceError happens as a result of HMR by Vite
    1. Similar to this, some logs were repeated twice because I was actively changing some things and the component had to rerun from the start

Summary: If my problem is still not clear, you could provide another way of fetching a large file (video) from a network, loading the file into memory and passing the file data to @ffmpeg/ffmpeg for further processing

0

There are 0 best solutions below