Turn an FFmpeg command into an FFmpeg wasm exec function

I have this gnarly FFmpeg command:

ffmpeg -i music.mp3 -i video.mp4 -i speech.mp3 -filter_complex "[0:a]atrim=0:$(ffprobe -i speech.mp3 -show_entries format=duration -v quiet -of csv=p=0),volume=0.08[trimmed_music]; [2:a]volume=2[speech]; [1:v]loop=-1,trim=duration=$(ffprobe -i speech.mp3 -show_entries format=duration -v quiet -of csv=p=0),setdar=9/16[vout]; [trimmed_music][speech]amix=inputs=2:duration=first[aout]" -map "[vout]" -map "[aout]" -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k final_output.mp4

What it does is:

  • Get the duration of a speech.mp3 file
  • Crop a video.mp4 to portrait dimensions
  • Trim a longer music.mp3 file to the duration of the speech
  • Loop a the video and trim the final loop so that the whole thing matches the duration of the speech
  • Adjust the volume of the music and speech
  • Combine them all into a single video with talking (speech) and music

I can’t figure out how to run it using the FFmpeg wasm. I realise ffprobe isn’t a thing with the wasm so we’ll have to find a different way to get the duration of the speech.mp3 by probably breaking it up into 2 or more exec functions, but I have no idea how to do that, which is why I’m here asking for help.

For reference, here’s the function into which I want to insert this exec function, but feel free to change it however needed. And let me know if I need to provide more information.

  const processVideo = async (speech, video, music) => {
    const ffmpeg = new FFmpeg();

    // ffmpeg loading code goes here, assume that part works without issue

    await ffmpeg.writeFile("video.mp4", new Uint8Array(video));
    await ffmpeg.writeFile("speech.mp3", new Uint8Array(speech));
    await ffmpeg.writeFile("music.mp3", new Uint8Array(music));

    await ffmpeg.exec([
      // command(s) should go here
    ]);

    const fileData = await ffmpeg.readFile("final_output.mp4");
    const blob = new Blob([fileData.buffer], { type: "video/mp4" });
    const blobUrl = URL.createObjectURL(blob);

    return blobUrl;
  };