ffmpeg(wasm) in Firefox
Waveform Visualizer Add-on 개발 당시의 Firefox - ffmpeg/wasm - webpack 문제 해결 건을 기록한다. (React)
https://adobesparkpost.app.link/TR9Mb7TXFLb?addOnId=wjnlh62nj
해당 Add-on은 음원을 업로드 하면, 음원 볼륨을 트리거로 Canvas 애니메이션을 재생하고, 브라우저의 MediaRecorder를 활용하여 영상을 만든다.
여기서 문제발생
- Adobe Express 문서에는 일부 영상 코덱만을 지원한다.
- video/mp4;codecs=avc3 // 크롬에서 권장
- video/mp4;codecs=avc1 // 사파리는 아직 여기..
- Firefox의 MediaRecorder는 1번의 코덱을 지원하지 않는다.
- video/webm;codecs=vp8 // 지원하는 코덱 중 하나
- 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에서 제외하거나, 잘 ... 기억해뒀다가 새로 프로젝트 설치할 때마다 해당 파일을 수정해야 한다.