back

Posts

video-encoder 라이브러리 개발기

2026-05-01

시작은 임시방편이었다

이전 글(모바일 WebView에서 video 태그를 다루며 겪은 것들)에서 iOS WebView 크래시 문제를 다룬 적이 있습니다. 20MB 영상 여러 개를 동시에 핸들링하는 환경이 문제였고, 근본 해결책인 서버 사이드 압축 파이프라인이 준비되기 전까지 급한 불을 꺼야 했습니다.

그래서 직접 만들었습니다. 브라우저에서 바로 동작하고, 서버 없이 영상을 5MB 이하로 줄여주는 도구. 그게 video-encoder입니다.

Claude Code로 만들기로 했다

사실 사내에선 무료툴을 찾아보자는 얘기도 있었고 실제로 몇몇 후보도 있었습니다. 하지만 무료툴보단 사내에서 직접 관리할 수 있는게 좋기도 하고 새로운 첼린지도 하고 싶어서 퇴근하고 사이드프로젝트로 비디오 인코더를 만들기로 결정했습니다.

초기 시행착오: "만들어줘"의 함정

처음엔 단순하게 접근했습니다.

"WebCodecs로 영상 압축 기능 만들어줘"

결과물은 빠르게 나왔습니다. 그런데 실제로 돌려보면 버그가 자주 나왔습니다. 고쳐달라고 하면 고쳐지는데, 다른 곳에서 사이드이펙트가 터졌습니다. 그걸 또 고치면 처음 버그가 다시 살아났습니다.

원인은 단순했습니다. Claude Code가 코드를 작성하는 기준이 없었습니다. 전체 구조 합의 없이 기능 단위로 요청하니, 그때그때 최적인 방식으로 코드를 뽑아냈고 결과적으로 일관성 없는 코드베이스가 됐습니다.

설계를 먼저, 구현은 그 다음에

방법을 바꿨습니다. 코드 요청 전에 전체 기능과 기술 스택을 CLAUDE.md에 먼저 정리했습니다.

# video-encoder

## 기능 목록
- 영상 파일 드래그 앤 드롭 업로드
- CRF 기반 품질 조절 슬라이더
- 해상도 선택 (원본 / 1080p / 720p / 480p)
- 오디오 제거 옵션
- 실시간 압축 진행률
- 압축 전후 파일 크기 비교 및 다운로드

## 기술 스택
- Vite + TypeScript
- 인코더 1순위: WebCodecs API
- 인코더 폴백: FFmpeg.wasm
- 디먹싱: mp4box.js
- 먹싱: mp4-muxer

이 파일을 기반으로 "CLAUDE.md 참고해서 엔진 선택 로직부터 구현해줘"처럼 기능 단위로 순서대로 요청했습니다.

결과는 달랐습니다. 구조에 대한 합의가 먼저 있으니 맥락을 유지하면서 코드를 작성했습니다. 사이드이펙트도 줄었고, 의도한 구조대로 코드가 쌓였습니다.

기술 구현

하이브리드 엔진 구조

핵심은 WebCodecs API와 FFmpeg.wasm을 조합한 하이브리드 인코딩 엔진입니다.

브라우저가 WebCodecs(H.264)를 지원하는가?
    ├── YES → WebCodecs 엔진 (GPU 가속)
    └── NO  → FFmpeg.wasm 엔진 (CPU)

WebCodecs 실패 시 → 자동으로 FFmpeg.wasm 폴백

WebCodecs API는 Chrome 94+ / Edge 94+에서 지원하는 저수준 영상 인코딩/디코딩 API입니다. GPU를 직접 활용하기 때문에 속도가 빠르고 메인 스레드 블로킹이 적습니다.

FFmpeg.wasm은 FFmpeg를 WebAssembly로 컴파일한 라이브러리입니다. WebCodecs를 지원하지 않는 환경(Firefox, Safari)에서도 동작합니다. 멀티스레드 모드(@ffmpeg/core-mt)를 사용해 CPU 연산 속도를 높였습니다.

영상 처리 파이프라인

WebCodecs 엔진 기준 처리 흐름입니다.

1. mp4box.js → 입력 영상 디먹싱, 비디오 트랙 샘플 추출

2. VideoDecoder → 각 프레임 디코딩, VideoFrame 시퀀스 생성

3. VideoEncoder → H.264 High Profile 재인코딩
                  (B-프레임 + CABAC으로 압축 효율 극대화)

4. mp4-muxer → 인코딩된 프레임을 MP4로 패키징 → Blob → 다운로드

CRF 기반 품질 제어

압축 품질은 CRF(Constant Rate Factor) 방식으로 조절합니다. 18(최고 품질)~40(최대 압축) 슬라이더로 조정할 수 있습니다.

고정 비트레이트 방식과 달리 CRF는 영상 복잡도에 따라 비트를 유동적으로 배분합니다. 동일 화질 대비 파일 크기가 더 효율적으로 나오는 이유입니다.

해상도 스케일링

원본 유지 또는 1080p / 720p / 480p로 다운스케일할 수 있습니다. 해상도를 낮추면 인코딩 비트레이트도 자동으로 재계산해 화질과 파일 크기의 균형을 맞춥니다.

마치며

사실 처음엔 1~2시간이면 완성할 수 있지 않을까란 생각으로 도전했지만 완성까지는 5시간이 걸렸습니다.

만약 처음에 정확히 설계하고 프롬프트를 입력했다면 저 시간은 더 단축될 수 있었겠지만 AI에게 요청만 하는 게 만능이 아니라는 걸 확인할 수 있었던 시간이라 나쁘지 않았습니다.

물론 이 라이브러리는 임시방편으로 만들어서 보완 및 개선점이 많지만, 추후 비슷한 기능을 구현하는 데 도움이 많이 될 것이라 생각됩니다.