Dev

동영상 압축을 브라우저에서 구현한 이야기 — FFmpeg.wasm 적용기

toolzip 2026. 9. 25. 08:00

동영상 압축은 서버에서 하는 것이라는 고정관념이 있었습니다. 그런데 브라우저에서 직접 동영상을 압축할 수 있다는 것을 알게 됐습니다.


FFmpeg.wasm을 알게 된 계기

이미지 압축 도구를 먼저 만들었습니다. Canvas API로 브라우저에서 이미지를 처리하는 것은 비교적 간단했습니다.

동영상은 달랐습니다. 동영상은 수십~수백 개의 이미지 프레임에 오디오까지 합쳐진 복잡한 데이터입니다. Canvas API로 처리하기에는 한계가 있었습니다.

조사하다가 FFmpeg.wasm을 발견했습니다. FFmpeg를 WebAssembly로 컴파일해서 브라우저에서 실행할 수 있게 만든 라이브러리입니다.


설치와 첫 번째 벽

설치는 간단합니다.

npm install @ffmpeg/ffmpeg @ffmpeg/util

그런데 실행하면 바로 에러가 납니다.

SharedArrayBuffer is not defined

FFmpeg.wasm은 멀티스레딩을 위해 SharedArrayBuffer가 필요합니다. 이 API는 보안상의 이유로 특정 HTTP 헤더가 있어야만 활성화됩니다.

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Next.js 설정 파일에 이 헤더를 추가했습니다.

// next.config.js
headers: [
  {
    source: "/(.*)",
    headers: [
      { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
      { key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
    ],
  },
]

로컬에서는 됩니다. 그런데 Vercel에 배포하면 또 안 됩니다. Vercel의 경우 vercel.json에도 동일한 헤더 설정이 필요했습니다.


초기 로딩 시간 문제

FFmpeg.wasm의 코어 파일은 약 30MB입니다. 처음 사용할 때 이 파일을 다운로드해야 합니다.

사용자가 동영상 압축 버튼을 처음 누르면 30초 가까이 기다려야 합니다. 이건 UX 문제입니다.

해결책은 두 가지였습니다.

로딩 상태 표시: 진행 상황을 알 수 있게 프로그레스바와 "FFmpeg 로딩 중 (28MB)..." 같은 메시지를 표시했습니다.

캐싱 활용: 한 번 다운로드하면 브라우저 캐시에 저장됩니다. 다음 방문부터는 즉시 로드됩니다.


가상 파일시스템 이해하기

FFmpeg는 파일을 읽고 씁니다. 브라우저에는 파일시스템이 없습니다. FFmpeg.wasm은 메모리 안에 가상 파일시스템을 제공합니다.

// 파일을 가상 파일시스템에 쓰기
await ffmpeg.writeFile('input.mp4', await fetchFile(file));

// FFmpeg 명령 실행
await ffmpeg.exec([
  '-i', 'input.mp4',
  '-crf', '28',      // 압축 품질 (0=무손실, 51=최대압축)
  '-preset', 'fast', // 인코딩 속도
  'output.mp4'
]);

// 결과 파일 읽기
const data = await ffmpeg.readFile('output.mp4');

// 다운로드 링크 생성
const blob = new Blob([data.buffer], { type: 'video/mp4' });
const url = URL.createObjectURL(blob);

처리 시간 문제

100MB 동영상을 압축하는 데 얼마나 걸릴까요?

서버에서 FFmpeg를 돌리면 1

2분 정도입니다. 브라우저에서는? 기기마다 다르지만 5

15분이 걸릴 수 있습니다.

서버는 고성능 CPU를 사용하지만 브라우저는 사용자 기기의 CPU를 사용합니다. 구형 노트북이라면 더 오래 걸립니다.

이 한계를 솔직하게 UI에 표시했습니다. "파일 크기에 따라 수 분이 걸릴 수 있습니다. 탭을 닫지 마세요."


진행 상황 표시하기

FFmpeg.wasm은 처리 진행 상황을 알 수 있는 콜백을 제공합니다.

ffmpeg.on('progress', ({ progress }) => {
  setProgress(Math.round(progress * 100));
});

0에서 100까지 프로그레스바가 채워지는 것을 보여주면 사용자가 기다리기 훨씬 쉬워집니다.


결과

동영상 압축, 포맷 변환(MP4·MOV·AVI·WebM), GIF 변환, 오디오 추출까지 구현했습니다. 모두 브라우저에서 처리되고 파일이 서버로 전송되지 않습니다.

처음 FFmpeg.wasm을 접했을 때 "이게 가능하다고?" 했는데, 실제로 돌아가는 것을 보면 아직도 신기합니다.