ffmpeg(wasm) in Firefox

Waveform Visualizer Add-on 개발 당시의 Firefox - ffmpeg/wasm - webpack 문제 해결 건을 기록한다. (React)

https://adobesparkpost.app.link/TR9Mb7TXFLb?addOnId=wjnlh62nj

해당 Add-on은 음원을 업로드 하면, 음원 볼륨을 트리거로 Canvas 애니메이션을 재생하고, 브라우저의 MediaRecorder를 활용하여 영상을 만든다.

여기서 문제발생

  1. Adobe Express 문서에는 일부 영상 코덱만을 지원한다.
    1. video/mp4;codecs=avc3 // 크롬에서 권장
    2. video/mp4;codecs=avc1 // 사파리는 아직 여기..
  2. Firefox의 MediaRecorder는 1번의 코덱을 지원하지 않는다.
    1. video/webm;codecs=vp8 // 지원하는 코덱 중 하나
    2. 2.a 의 영상을 mp4로 컨버팅 필요

그리하여 컨버팅에 필요한 ffmpeg를 사용.

버전은 아래와 같이 사용했다

"@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2",

wasm 방식을 사용하려면 load 할 때 core, wasm, worker 파일 URL을 연결시켜줘야 하는데, 나는 외부에서 받는 형식이 아닌 asset 에 넣은 후 불러오는 방식으로 연결했다.

webpack.config.js

module: {
    rules: [
      ...
      // WASM 파일 처리
      {
        test: /\.wasm$/,
        type: "asset/resource",
      },
      {
        test: /ffmpeg-core\.worker\.js$/,
        include: path.resolve(__dirname, "src/assets/ffmpeg"),
        type: "asset/resource", // 워커 파일을 정적 자산으로 처리
      },
      // ffmpeg core 파일 등 Node 전용 모듈 구문이 포함된 파일들을 raw-loader로 처리
      {
        test: /ffmpeg-core\.js$/,
        include: path.resolve(__dirname, "src/assets/ffmpeg"),
        type: "asset/resource", // 워커 파일을 정적 자산으로 처리
      },
      ...

App.tsx

import FFmpegWorker from "@assets/ffmpeg/ffmpeg-core.worker.js?url";
import FFmpegWASM from "@assets/ffmpeg/ffmpeg-core.wasm?url";
import FFmpegCore from "@assets/ffmpeg/ffmpeg-core.js?url";


  async function fetchScriptAsBlobUrl(url: string, mimeType: string) {
    const resp = await fetch(url);
    if (!resp.ok) throw new Error(`Fetch failed ${resp.status}`);
    let data: string | ArrayBuffer;
    if (mimeType === "application/wasm") {
      // ← pull down raw bytes
      data = await resp.arrayBuffer();
    } else {
      data = await resp.text();
    }
    const blob = new Blob([data], { type: mimeType });
    const blobUrl = URL.createObjectURL(blob);

    return blobUrl;
  }
  
  const loadFFmpegCore = async () => {
    if (ffmpegRef.current?.loaded) return;
    console.log("Loading FFmpeg core...");
    ffmpegRef.current = new FFmpeg();
    const ffmpeg = ffmpegRef.current;
    ffmpeg.on("log", ({ type, message }) => {
      console.log(type, message);
    });
    ffmpeg.on("progress", ({ progress, time }) => {
      console.log(progress, time);
    });

try {
      const ffmpegResult = await ffmpeg.load({
        coreURL: await fetchScriptAsBlobUrl(FFmpegCore, "text/javascript"),
        wasmURL: await fetchScriptAsBlobUrl(FFmpegWASM, "application/wasm"),
        workerURL: await fetchScriptAsBlobUrl(FFmpegWorker, "text/javascript"),
      });

      console.log("FFmpeg core loaded", ffmpegResult);
    } catch (error) {
      console.error("Error loading FFmpeg core:", error);
    }
  };
  

로드가 필요할 때 loadFFmpegCore를 실행한다.

결정적인 문제!!

ffmpeg 라이브러리에는 치명적이고 단순한 문제점이 있는데...

  • 라이브러리 내부에서 동적 import() importScript() 사용 (vite, webpack 에서는 기본적으로 제한됨)
  • 라이브러리를 통째로 바꿀 수는 없으니... 해당 구문에 ignore 표시를 해주면 되는데, ffmpeg 라이브러리에는 vite ignore만 적혀있다!!!

해결방법

ffmpeg 라이브러리 내부에 webpack ignore 추가 (로직도 좀 없앤 게 있지만 webpackIgnore를 봐주시라..)

node_modules / @ffmpeg / ffmpeg / dist / esm / worker.js

const load = async ({ coreURL: _coreURL, wasmURL: _wasmURL, workerURL: _workerURL, }) => {
    console.log('worker load start')
    const first = !ffmpeg;
    // try {
    //     if (!_coreURL)
    //         _coreURL = CORE_URL;
    //     // when web worker type is `classic`.
    //     importScripts(_coreURL);
    // }
    // catch(err) {
        console.log('importScripts', _coreURL);

        // if (!_coreURL || _coreURL === CORE_URL)
        //     _coreURL = CORE_URL.replace('/umd/', '/esm/');
        // when web worker type is `module`.
        self.createFFmpegCore = (await import(
        /* webpackIgnore: true */ _coreURL)).default;

        console.log('createFFmpegCore', self.createFFmpegCore);
        if (!self.createFFmpegCore) {
            throw ERROR_IMPORT_FAILURE;
        }
    // }

이렇게 문제없이 브라우저상에서 컨버팅을 진행했다.

참고: 진짜 오래걸림

유의할 점: git과 같은 버전관리 툴에서 대부분 node_modules는 제외하고 버전관리가 되므로, 해당 파일이 버전관리되도록 git ignore에서 제외하거나, 잘 ... 기억해뒀다가 새로 프로젝트 설치할 때마다 해당 파일을 수정해야 한다.