본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 24. 17:12

브라우저에서 100% 실행되는 자막 편집기를 만들었습니다 - WebGPU 기반 Whisper, WebCodecs를 이용한 MP4 내보내기

요약

WebGPU와 WebCodecs를 활용하여 서버 비용 없이 브라우저 내에서 모든 전사 및 렌더링을 수행하는 자막 편집기 CapStudio 개발 사례를 소개합니다. WebGPU 기반 Whisper 모델 실행 시 발생하는 타임스탬프 문제와 WebGPU 가속 실패 시 WASM으로의 안정적인 폴백 전략을 다룹니다.

핵심 포인트

  • WebGPU 기반 Whisper를 사용하여 클라이언트 측에서 로컬 전사 수행
  • 단어 단위 타임스탬프를 위해 _timestamped 모델 사용 필요
  • WebGPU 지원 여부를 직접 검증하여 WASM 폴백 안정성 확보
  • 단일 드로우 패스 방식을 통해 미리보기와 내보내기 결과 일치 구현

모든 "쇼츠에 자막 추가" 도구들은 동일한 방식으로 작동합니다. 클립을 서버에 업로드하면, 서버에서 전사(transcribe) 및 렌더링을 클라우드에서 수행하고, 내보내기 횟수에 따라 비용을 부과합니다. 이는 업로드 대기 시간, 대기열, 파일 크기 제한, 내보내기당 비용 발생, 그리고 당신의 영상이 타인의 디스크에 저장되어 있음을 의미합니다.

저는 이 모든 과정을 브라우저에서 직접 수행할 수 있는지 알고 싶었습니다. 결과적으로 가능했으며, 그 결과물인 CapStudio는 비디오 도구로서는 특이한 속성을 가집니다. 렌더링 팜(render farm)과 전사 API가 없기 때문에 운영 비용이 거의 들지 않는다는 점입니다. 유일한 서버는 인증(auth), 결제(billing), 그리고 아주 작은 프로젝트 파일을 동기화하는 용도로만 사용됩니다. 이것이 한 사람이 이 서비스를 운영할 수 있는 온전한 이유입니다.

각 구성 요소가 어떻게 결합되는지 설명하겠습니다.

전사(Transcription): 탭 내에서 실행되는 WebGPU 기반 Whisper
전사는 @huggingface/transformers (transformers.js v4)를 사용하여 로컬에서 실행되며, 이는 WebGPU 상에서 Whisper를 실행할 수 있습니다. 클립의 오디오는 decodeAudioData와 OfflineAudioContext를 통해 16kHz 모노 Float32 버퍼로 디코딩된 후 파이프라인에 입력됩니다.

이 과정에서 두 가지 문제에 직면했습니다:

  1. 모든 모델이 출력할 수 있는 것은 아닌 단어 단위 타임스탬프(word-level timestamps)가 필요합니다. return_timestamps: "word"를 요청하면 기본 Whisper 내보내기에서 오류가 발생합니다 ("Model outputs must contain cross attentions"). 해결 방법은 cross-attention 출력을 포함하는 _timestamped 모델 내보내기를 사용하는 것입니다. 경험칙상, 단어 단위 시간 정보가 포함된 자막을 위해서는 모델 ID가 _timestamped로 끝나야 합니다.

  2. navigator.gpu가 존재한다고 해서 WebGPU가 작동한다는 의미는 아닙니다. 많은 기기(하드웨어 가속 꺼짐, 차단된 GPU, RDP/VM 환경)에서 navigator.gpu는 존재하지만 requestAdapter()가 null을 반환합니다. transformers.js는 객체의 존재 여부만 확인하고 WebGPU를 시도하다가 실패하면, WASM 폴백(fallback)까지 오염시켜 device: "wasm" 설정마저 작동하지 않게 만듭니다. 해결 방법은 직접 requestAdapter()를 먼저 호출하여 유효한 어댑터를 얻었을 때만 WebGPU를 선택하고, 그렇지 않으면 즉시 깨끗한 WASM 전용 경로로 이동하는 것입니다. 또한 중단 감시 장치(stall watchdog)를 추가했습니다. 만약 WebGPU가 모델을 다운로드했지만 45초 동안 진전이 없다면, 요청을 거부하고 폴백합니다.

렌더링: 미리보기와 내보내기를 위한 단일 드로우 패스 (one draw path)
각 자막 스타일(가라오케 하이라이트, 단어 팝, 클린 로워 서드(lower-third) 등)은 순수 함수(pure function)입니다: layout(StyleContext) -> CaptionLayout. 단일 페인터(painter)가 해당 레이아웃을 캔버스 드로우 콜(canvas draw calls)로 변환하며, 단일 drawCaptionFrame 함수가 라이브 미리보기(a <canvas> 위)와 내보내기(export) 양쪽 모두에서 사용하는 유일한 진입점입니다. 이것이 바로 "보는 그대로 내보내진다(what you see is what you export)"라는 말을 문자 그대로 실현하는 핵심입니다. 저는 동일한 프레임을 DOM 캔버스와 OffscreenCanvas에 각각 그리는 픽셀 차이 검증 도구(pixel-diff harness)를 통해 이를 증명했습니다: 불일치하는 채널은 0개였습니다.

새로운 스타일을 추가하는 것은 엔진의 변경 없이 새로운 모듈 하나와 레지스트리(registry) 한 줄만 추가하면 됩니다.

내보내기: WebCodecs 프레임 + 오디오 리먹싱 (audio remux)
내보내기는 동일한 drawCaptionFrame으로 모든 프레임을 그리고, 이를 VideoEncoder (WebCodecs)로 인코딩하며, mp4-muxer를 사용하여 원본 오디오 트랙을 그대로 복사하며 MP4로 멀티플렉싱(mux)합니다.

주의 사항:

  • B-프레임(B-frames)은 첫 번째 청크의 DTS를 0이 아니게 만드는데, 이는 뮤서(muxer)에서 거부됩니다. firstTimestampBehavior: "offset"으로 설정해야 합니다.
  • 백프레셔(backpressure) 제어가 없으면 긴 클립의 경우 탭이 종료됩니다. 스로틀(throttle) 없이 모든 샘플을 디코더/인코더에 밀어 넣으면 큐(queue)가 넘쳐나며, 약 70초 길이의 클립에서 50% 지점쯤 flush()가 중단(stall)됩니다. 해결책은 decodeQueueSize/encodeQueueSize > 16인 동안 루프를 일시 중지하고 제어권을 양보(yield)하여 코덱 콜백(codec callbacks)이 비워질 수 있도록 하는 것입니다. 짧은 클립에서는 이 문제가 나타나지 않았기에, 잠재적인 결함으로 남아 있었습니다.

지속성: 로컬 우선(local-first), 비디오는 사용자의 기기에 머무름
프로젝트는 OPFS에 자동 저장됩니다 (createWritable()close() 시 원자적으로 커밋되므로, 비디오 바이트를 먼저 쓰고 그 다음 매니페스트를 작성합니다). 로그인한 Pro 사용자는 클라우드 동기화 기능도 사용할 수 있지만, 프로젝트 JSON(전사 데이터, 스타일, 설정)만 동기화되며 비디오 바이트는 절대 동기화되지 않습니다. 설계상 비디오는 절대로 기기를 떠나지 않습니다.

왜 이런 방식을 택했을까요

이 아키텍처는 운영 비용을 거의 제로(0)에 가깝게 무너뜨리며, 이것이 바로 핵심입니다. 즉, ASR(자동 음성 인식) 비용도, GPU 렌더 팜(render farm)도, 분당 과금 압박도 없습니다. 또한 이는 사용자의 영상이 비공개로 유지됨을 의미하며, 내보내기 제한도 없습니다. 제한이 생긴다면 그것은 오직 타인의 서버 비용 문제일 뿐이기 때문입니다. 여기에 더해 제가 추구하는 차별점은 영어 우선의 기존 업체들이 취약한 체코어 및 슬라브어에 대한 강력한 지원입니다.

솔직한 한계점: Chrome 또는 Edge가 필요합니다. 작동 가능한 WebGPU 어댑터가 없으면 전사(transcription) 기능이 WASM으로 대체되는데, 이는 정확하지만 느리며, 첫 실행 시 Whisper 모델을 다운로드해야 합니다. 현재 베타 버전입니다.

직접 체험해보고 싶다면 https://capstudio.xyz에서 무료로(워터마크 포함) 이용할 수 있습니다. 시작하기 위해 회원가입을 할 필요도, 아무것도 업로드할 필요도 없습니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0