본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 07. 19:42

몇 초 만에 무료로 비디오 자막 생성하기 — 가입 불필요, 워터마크 없음, 업로드 없음

요약

Transformers.js와 WebGPU를 활용하여 브라우저 내에서 서버 없이 비디오 자막을 생성하는 Captionly 프로젝트를 소개합니다. WebCodecs와 WebGPU를 결합해 개인정보 보호와 비용 문제를 해결하며, 클라이언트 측에서 모든 연산을 처리하는 아키텍처를 구현했습니다.

핵심 포인트

  • WebGPU와 WebCodecs를 활용한 클라이언트 측 비디오 처리
  • 서버 업로드 없는 개인정보 보호 및 비용 절감 아키텍처
  • WebGPU 지원 여부에 따른 WASM 폴백 전략 구현
  • Transformers.js를 이용한 Whisper 모델의 브라우저 포팅

요약 (TL;DR) — 저는 @huggingface/transformers를 사용하여 브라우저 내부에서 Whisper를 실행하는 자막 생성기인 Captionly를 제작했습니다 (WebGPU 사용 가능 시 WebGPU 사용, 불가 시 WASM을 대체제로 사용). 파일은 절대 기기를 떠나지 않습니다. 각 요소가 어떻게 결합되는지, 그리고 제가 파악하는 데 가장 오래 걸렸던 주의사항(gotchas)들을 소개합니다.

왜 굳이 브라우저 내부인가?

다른 모든 자막 도구들은 사용자의 비디오를 서버로 전송합니다. 이는 다음과 같은 문제를 의미합니다:

  • 업로드 시간이 전체 경험의 병목 구간이 됩니다 (200MB MP4 파일의 경우, 시작하기도 전에 30초 이상 소요됩니다).
  • 개인정보 보호: 사용자의 원본 영상이 타인의 S3에 저장됩니다.
  • 비용: 누군가는 GPU 사용 시간을 지불해야 하며 — 대개 프리미엄(freemium) 유료 결제 장벽 뒤에 있는 사용자가 그 비용을 부담하게 됩니다.

지난 18개월 동안 브라우저는 부족했던 요소들을 갖추게 되었습니다:

  • WebGPU — 탭 내부에서 실제 GPU 연산 수행 가능 (Chrome 113+, Safari 18).
  • WebCodecs<video> 요소 없이 하드웨어 가속 비디오 디코딩/인코딩 수행.
  • Transformers.js 3+ — ONNX Runtime Web을 통해 Whisper, Llama 등을 WebGPU로 포팅.

이들을 결합하면 완전히 클라이언트 측에서 실행되는 "진정한" 비디오 도구를 얻을 수 있습니다.

아키텍처

파일 드롭 → WebCodecs 디코딩 → 16 kHz 모노로 리샘플링
        → Whisper (WebGPU/WASM) → 타임스탬프가 찍힌 단어들
        → 사용자가 인라인 편집 → WebCodecs 인코딩 + 자막 삽입(burned-in subs)
...

이 흐름에는 서버가 없습니다. 유일한 네트워크 호출은 첫 번째 전사(transcription) 시에 발생하며, 이때 Hugging Face Hub에서 Whisper 가중치(Xenova/whisper-small 기준 약 60MB)를 가져옵니다. 이후 실행 시에는 브라우저의 로컬 캐시에서 제공됩니다 (transformers.js가 처리하며, 네트워크 왕복이 발생하지 않음).

주의사항 (Gotchas)

1. WebGPU는 훌륭하지만, 그렇지 않을 때도 있습니다

whisper-small 모델은 M2 칩의 WebGPU 환경에서 실시간 대비 약 3배 속도로 실행됩니다. 동일한 모델을 WASM 백엔드에서 실행하면 실시간 대비 약 0.8배 속도가 나옵니다. 따라서 좋은 사용자 경험(UX)을 위해서는 WebGPU가 필수적입니다. 하지만:

  • Safari 18에 WebGPU가 탑재되었지만, transformers.js가 Whisper에서 사용하는 f16 연산을 수행하려면 Safari 18.4 이상 버전이 필요합니다.
  • Linux Chrome의 경우 일부 배포판(distros)에서는 플래그(flag)를 활성화해야 WebGPU를 사용할 수 있습니다.
  • 일부 Android 기기는 WebGPU를 지원한다고 보고하지만, 첫 번째 커널(kernel) 실행 시 충돌(crash)이 발생합니다.

저는 이를 감지하고, 시도해 본 뒤, 실패하면 WASM으로 폴백(fall back)하며, 이 과정이 발생했을 때 사용자에게 알립니다.
조용히 성능이 저하되게 두지 않습니다. 10배의 성능 저하는 숨기기에 너무 큰 차이이기 때문입니다.

2. 긴 비디오는 Whisper를 과부하시킵니다

Whisper는 기본적으로 30초 단위의 청크(chunk)를 처리합니다. 더 긴 오디오의 경우, 중첩(overlap)을 포함하여 수동으로 청크를 나누고 출력물을 다시 이어 붙여야(stitch) 합니다. transformers.jsreturn_timestamps: 'word' 옵션을 제공하여 이 이어 붙이기 과정을 처리하지만, 먼저 16 kHz 모노(mono)로 사전 리샘플링(pre-resample)을 해야 합니다. Whisper의 토크나이저(tokenizer)는 정확히 그 형식을 기대하며, 44.1 kHz 스테레오(stereo)를 입력하면 아무런 경고 없이 쓰레기 값(garbage)을 생성합니다.

저는 리샘플링을 위해 WebAudio의 OfflineAudioContext를 사용합니다. 메인 스레드에서 10분 길이의 파일을 처리하는 데 약 50ms가 소요됩니다. 그 후 이를 Web Worker로 옮겨 추론(inference) 중에 UI가 끊기는 현상(jank)이 발생하지 않도록 합니다.

3. 서버 없이 MP4에 자막 입히기 (Burning subtitles)

이 부분이 가장 까다로웠습니다. 표준 도구인 ffmpeg.wasm은 무겁고 느립니다. 그래서 저는 WebCodecs를 선택했습니다:

  1. VideoDecoder를 사용하여 소스 비디오를 프레임 단위로 디코딩(decode)합니다.
  2. 각 프레임을 OffscreenCanvas에 그립니다.
  3. 활성화된 자막 라인을 그 위에 합성(composite)합니다 (Canvas 2D API로 렌더링된 12가지 TikTok 스타일 프리셋 중 하나).
  4. VideoEncoder로 다시 인코딩(re-encode)합니다.
  5. mp4-muxer를 통해 MP4로 다시 멀티플렉싱(mux)합니다.

결과: 최신 노트북에서 짧은 1080p 클립이 서버 비용 없이 단 몇 초 만에 재인코딩됩니다 (소요 시간은 하드웨어 및 WebCodecs 인코더에 따라 달라집니다). 전체 멀티플렉싱 과정은 약 400줄의 코드로 구현되었습니다.

다르게 한다면 (What I'd do differently)

  • 실험실 수준의 정확도를 원하는 사용자를 위해 whisper-large-v3-turbo (여전히 약 800 MB)를 선택 사항(opt-in)으로 제공하겠습니다. 두 번째 방문 시 캐시 히트(cache hit)가 발생하므로, 이 정도 크기라도 실행 가능합니다.
  • 더 긴 비디오를 여러 코어에 분산 처리할 수 있도록 워커 풀(worker-pooled) 인코더를 추가하겠습니다.

직접 시도해보세요

Captionly — 무료이며, 가입이 필요 없고, 워터마크가 없습니다. 저장소(Repo)는 곧 공개할 예정입니다 (먼저 몇 가지 미흡한 부분들을 정리하고 싶습니다).

댓글을 통해 아키텍처(architecture) 관련 질문에 기꺼이 답변해 드리겠습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0