외국어 영상에서 소리만 뽑아 내 컴퓨터 안에서 받아 적고 번역해 자막을 만듭니다. 영상은 어디에도 올라가지 않습니다. 그리고 "같은 소리면 자막은 한 번만 만든다"를 위해 소리를 알아보는 기술을 설계하고 실측했습니다.
역할 처리 파이프라인 아키텍처 · 측정 기반 품질 개선 · 크로스플랫폼 빌드·릴리스 자동화스택 TypeScript · Electron · whisper.cpp · ffmpeg · Ollama / OpenAI 호환 API · GitHub Actions · Cloudflare Workers
이 페이지는 이 분야를 모르는 분도 읽을 수 있게 썼습니다. 어쩔 수 없이 쓰는 기술 용어에는 각주 번호1를 붙였고, 배경지식이 필요한 대목은 청록색 "쉬운 말로" 상자로 풀었습니다. 숫자는 전부 실제로 잰 값입니다.
01문제의식 — 자막 하나 만들기가 왜 어려운가
외국어 영상에 자막을 붙이려면 보통 영상을 어딘가의 서버에 올려야 합니다. 개인이 소장한 영상을 남의 서버에 올리는 것은 꺼려지고, 분량이 쌓이면 비용도 붙습니다. 그렇다고 내 컴퓨터에서 처리하면 고성능 GPU2가 없는 환경에서는 음성 인식이 영상 재생 시간보다 오래 걸리고, 결과도 거칠어서 그대로 쓰기 어렵습니다.
그래서 두 가지를 목표로 잡았습니다. 하나는 영상은 절대 올리지 않고 전부 내 컴퓨터 안에서 끝내는 것, 다른 하나는 "같은 소리라면 자막은 세상에서 한 번만 만들어지게" 하는 것입니다. 두 번째 목표에서 자연스럽게 사업 모델이 나옵니다 — 좋은 GPU를 가진 사용자가 만든 결과를 다른 사용자가 포인트로 값싸게 받아 쓰고, 만든 사람은 보상을 받는 구조입니다. 이 구상의 전제는 두 가지 기술이었습니다: 같은 소리인지 알아보는 기술과, 재활용할 만한 품질.
02무엇을 만들었나
Windows·Linux용 데스크톱 앱과 명령줄 도구(CLI)입니다. 영상 파일을 넣으면 원어 자막과 한국어 번역 자막(SRT/VTT3)이 나오고, 앱 안에서 편집·내보내기까지 할 수 있습니다. mac 빌드도 자동으로 만들어지지만 코드 서명4이 없어 공식 지원은 보류했습니다.
설치하면 바로 동작
음성 인식 엔진(whisper.cpp)과 ffmpeg를 앱에 동봉해 별도 설치가 없습니다. NVIDIA GPU가 있으면 설정에서 클릭 한 번으로 GPU 가속 엔진을 내려받아 전환합니다(실측 약 10배 빠름).
같은 영상은 다시 계산하지 않음
전사·번역 결과를 소리 기준으로 저장해, 파일을 복사했거나 그릇(mp4↔mkv)만 바꾼 사본도 같은 소리로 알아봅니다. 취소 후 재실행해도 이미 끝난 번역은 다시 요청하지 않습니다.
번역은 취향대로
로컬 LLM(Ollama)·OpenAI 호환 API5·Gemini·Anthropic, 혹은 API 키 없이 웹 챗에 붙여넣는 "웹 LLM 모드"까지. 어느 쪽이든 타이밍은 앱이 원본 기준으로 다시 붙여 어긋나지 않습니다.
품질 옵션
고유명사 표기를 자동으로 통일하는 자동 용어집, 화자 경계 감지(실험적), 숨소리·감탄사가 많은 소재를 위한 프로파일, 원어+번역 병기 트랙, 배경음이 큰 영상용 센터 채널 추출.
그 외에 모델 매니저(용도별 추천 포함), 캐시 관리, 자동 업데이트, 가입 없이 보내는 문의·버그 신고 창구(Cloudflare Worker로 구현)가 있습니다.
실제 화면
아래는 이 프로젝트를 리눅스에서 실제로 실행해 자동으로 캡처한 것입니다 — 퍼블릭 도메인 일본어 낭독 90초를 넣어 원어 자막과 한국어 번역 자막을 만드는 전 과정입니다.
39초 데모(무음). 파일 추가 → 작업 설정 → 전사 → 번역 → 완료 → 편집기까지 실제 실행을 그대로 녹화한 것입니다. 소재는 퍼블릭 도메인 낭독(오가와 미메이 「赤い蝋燭と人魚」, LibriVox) 90초 발췌이고, 음성 인식은 내 PC의 GPU에서, 번역은 로컬 LLM(qwen3.5 9B)에서 돌았습니다.
실행 직후 준비 상태 점검 — ffmpeg·음성 인식 엔진·모델·안전 저장을 각각 확인해 무엇이 빠졌는지 먼저 알려줍니다.작업 설정 — 원어(ja)·모델·음성 구간 검출·번역 대상(ko)과 번역 프로바이더를 고릅니다. 여기서는 내 PC에서 도는 로컬 LLM을 골랐습니다.실행 중 — 전사가 끝나고 번역 단계가 진행되는 화면. 단계와 진행률이 함께 표시됩니다.완료 — 산출 파일 수와 함께 경고를 숨기지 않고 보여줍니다(이 실행에서는 번역이 원문으로 남은 문장 경고 5건).자막 편집기 — 영상을 재생하며 원어(ja)·번역(ko) 탭을 오가며 큐를 직접 고치고, 원하는 형식으로 내보냅니다.설정 — 모델 관리, GPU 가속 엔진 등록, 번역 프로바이더, 캐시 사용량·비우기, 문의 창구.
캡처 방법
이 화면들은 손으로 찍은 것이 아니라, 프로젝트의 E2E 테스트 하네스(Playwright로 Electron을 조종)를 그대로 재사용해 가상 디스플레이에서 앱을 띄우고 버튼을 순서대로 눌러 자동 수집했습니다 — 테스트 인프라가 그대로 데모 인프라가 된 셈입니다.
03어떻게 동작하나 — 여섯 단계 파이프라인
파이프라인 전체. 데스크톱 앱과 CLI가 같은 코드를 공유하고, 두 캐시가 "같은 소리"에서 비싼 단계를 건너뛰게 합니다.
쉬운 말로
전사(ASR)1는 소리를 글자로 받아 적는 일입니다. 여기서 쓰는 whisper는 공개된 음성 인식 모델이고, 가끔 아무 말도 없는 구간에서 "시청해 주셔서 감사합니다" 같은 문장을 지어냅니다 — 이것을 환각6이라 부르며, ③ 후처리의 주된 상대입니다.
③ 후처리에는 자막 규격(줄 길이, 읽기 속도, 자막 사이 최소 간격, 두 줄 배치)도 들어 있어 방송 자막에 가까운 형태로 나옵니다. ④ 번역은 문장 단위로 묶어 보내고, 응답의 문장 수가 어긋나면 원문을 그대로 남기고 경고하는 안전장치가 있습니다.
결과를 보장하지 않는 부품 위에서 멈추지 않기 — LLM 출력·비용 통제
음성 인식 모델과 LLM은 같은 입력에도 다른 답을 내고, 형식을 어기고, 가끔 응답하지 않습니다. 이 프로젝트의 번역 단계는 그 전제 위에서 설계했습니다.
출력 형식 계약 — 문장마다 번호를 붙여 보내고 같은 번호로 돌려받는 "번호 줄 프로토콜". 번호가 빠지거나 남으면(desync) 배치를 반으로 나눠 재요청하고, 끝내 못 맞추면 그 문장만 원문으로 남기고 경고합니다. 자막이 통째로 망가지는 일은 없습니다.
사후 검사 — 번역 결과에 원어(가나)나 엉뚱한 언어(중국어 간체) 문자가 남으면 경고로 드러냅니다. 자동으로 고치지 않고 사람이 확인할 자리를 남깁니다.
지연·장애 대응 — 첫 타임아웃은 1회만 재시도하고(반분 재시도는 타임아웃에 무의미하다는 실측), 연속 타임아웃이면 서버 장애로 보고 명확히 중단합니다. 죽은 서버를 20분 넘게 기다리던 것이 10초 종료로 바뀌었습니다.
비용 통제 — 번역 결과를 문장 배치 단위로 캐시해, 취소 후 재실행하거나 설정만 바꿔 다시 돌릴 때 LLM 비용이 다시 들지 않습니다. 로컬 모델을 고르면 비용은 0입니다.
04구조와 배포 — 한 번 짠 코드를 세 OS에서
TypeScript 모노레포7 10패키지. 기능은 아래층에서 작게 나누고, 데스크톱 앱과 CLI는 같은 core를 부르기만 합니다 — 그래서 1,403편 배치 실측을 CLI로 돌려도 앱과 정확히 같은 결과가 나옵니다.
쉬운 말로
사이드카8는 앱 옆에 실어 보내는 별도 실행 파일입니다. 음성 인식 엔진과 ffmpeg는 C/C++로 만든 프로그램이라 앱이 직접 품는 대신 옆에 두고 호출합니다. 사용자가 따로 설치할 필요가 없게 하는 대신, OS마다 그 실행 파일을 따로 준비해야 하는 일이 생깁니다 — 아래 CI가 그 일을 합니다.
GitHub Actions 3-OS 릴리스 CI. 사전 검사는 "6분짜리 빌드를 다 돌린 뒤 빈 릴리스 노트로 완주"하는 사고를 10초 만에 막기 위한 장치이고, 리눅스 잡은 시스템에 ffmpeg가 전혀 없는 컨테이너에서 동봉본만으로 자막을 실제로 만들어 봅니다. ffmpeg는 LGPL 소스 제공 의무까지 자산으로 함께 올립니다.
패키징에서 가장 많이 배운 것은 "테스트가 전부 통과해도 앱이 부팅에서 죽을 수 있다"는 점이었습니다. 번들러가 의존 패키지를 밖에 남기는 규칙과, 네이티브 모듈은 반대로 밖에 두어야 하는 규칙이 충돌해 두 번 크래시를 겪었고, 그 처방을 저장소 규약 문서에 실측과 함께 남겼습니다.
05품질을 숫자로 — 감이 아니라 측정으로 고치기
자막 품질은 "좋아진 것 같다"로는 개선할 수 없습니다. 사람이 만든 참조 자막이 있는 소재로 줄 단위 대조 회귀를 만들고, 보유 영상 1,403편을 CLI로 일괄 처리한 뒤 파이프라인 버전별로 결과를 비교하는 방식을 썼습니다. 개선 라운드마다 "설계 → 실측 게이트 → 통과해야 출하"를 지켰고, 게이트에 걸리면 스펙을 고쳤습니다.
"환각 지배" 판정. 대화가 거의 없는 영상에서 음성 인식이 방송 인사말을 영상 전체에 흩뿌리는 문제를, 상투구 사전과 "짧은 감탄사를 뺀 유효 세그먼트 비율"로 잡습니다. 문턱값 15%는 1,400편 분포를 분석해 정상 소재 대비 5.5배 여유가 있음을 확인한 값입니다.
개선 항목
방법
실측 결과
배경음악 위 대사 누락·시간 어긋남
무음 경계 기준 청크 전사로 재설계
수십 초 누락·선행 어긋남 해소, 참조 자막 줄 단위 대조 통과
대화 없는 구간의 반복 자막
반복 런 감지·접기 정책
반복 자막 대폭 감소, 실제 반복 연출은 보존
상투구 환각 도배
환각 지배 판정(위 그림) + 사전 4회 확장
대상 편의 17%에서 오염 자막 제거, 정상 대조군 오탐 0
번역 서버 일시 지연으로 원문 잔류
첫 타임아웃 1회 재시도 + 단문 백스톱
타임아웃 잔류 173건 → 0건, 하드 실패 5건 → 완주
고유명사 표기 흔들림
결정적 후보 추출 + LLM 1회 확정(자동 용어집)
한 작품에서 잘못된 표기 9 → 0
긴 파일명에서 출력 실패
파일명 바이트 캡 + 경고
전사·번역을 다 끝내고 저장에서 죽던 경로 봉합
쉬운 말로
세그먼트는 음성 인식이 내놓는 자막 조각 한 개입니다. 유효 세그먼트는 그중 "응", "네" 같은 세 글자 이하 감탄사를 뺀 것으로, 이걸 빼지 않으면 감탄사가 많은 영상에서 비율이 희석돼 판정을 놓칩니다 — 실제로 놓친 편들을 보고 추가한 보정입니다.
측정이 제 판단을 고친 일도 여러 번이었습니다. 게이트 수치가 예측과 어긋나 스펙을 세 차례 정정한 라운드가 있었고, 문턱값을 "조정해야 하는 결함"으로 등재했다가 분포를 재보니 어떤 값을 잡아도 경계 편은 생기는 구조적 성질이라 무조치로 종결한 건도 있습니다. 결함을 고치는 것만큼 고치지 않기로 근거를 남기는 것도 품질 작업이라고 배웠습니다.
06같은 소리 알아보기 — 재활용의 핵심 기술
"같은 소리면 자막은 한 번만"을 하려면 컴퓨터가 두 파일이 같은 소리인지 알아야 합니다. 이것을 세 층으로 설계했습니다 — 1층 해시(완전 동일, 출하됨) → 2층 VAD 서명(변형 대응의 1차 수단, 설계·실측) → 3층 랜드마크 지문(길이 기반 후보 추리기가 놓칠 때의 폴백, 구현·실측). 2·3층은 제품에 넣지 않고 직접 구현한 실험 하네스로 검증까지만 했습니다.
1층 — 소리의 해시 (출하됨)
영상에서 소리만 꺼내 정해진 규격으로 풀어낸 뒤, 그 데이터의 해시9를 열쇠로 씁니다. 파일 이름·날짜·그릇(mp4/mkv)이 달라도 안에 든 소리가 같으면 같은 열쇠가 나옵니다. 실측으로 복사본·리믹스본 모두 음성 인식 단계가 생략됐습니다(4.6초 → 0.3초). 한계도 실측했습니다 — 한 번이라도 다시 압축하거나 앞부분이 잘리면 소리 숫자열이 바뀌어 다른 열쇠가 됩니다(9가지 변형 전부 상이).
2층 — 변형을 견디는 "말의 리듬" 서명 (설계·실측)
VAD10 이진 서명과 상호상관11. 소리의 내용이 아니라 말의 리듬만 남기므로 화질·음질·크기가 달라도 거의 변하지 않고, 2시간이 9KB로 줄어듭니다. 9개 작품 교차 실측에서 다른 작품은 r ≤ 0.28, 같은 작품은 r ≥ 0.92로 넉넉히 갈렸고, 같은 시리즈의 다른 에피소드(같은 오프닝·성우·배경음악)도 ≤ 0.15였습니다.
함정 하나 — 더빙. 같은 영화의 일본어판과 영어 더빙판은 대사만 다르고 효과음·음악이 같아, 말의 리듬이 비슷하게 나옵니다(실측 r 0.30~0.45). 어떤 소리 지문으로도 원리적으로 못 거르기 때문에, 정렬 성공만으로는 재활용하지 않고 마지막에 실제로 짧게 들어 언어를 확인하는 검증 관문을 두었습니다. "정렬 성공 ≠ 재활용 승인"이 이 설계의 첫 규칙입니다.
3층 — Shazam 계열 랜드마크 지문 (구현·실측 후 폴백으로 배치)
스펙트로그램12의 국소 최댓값 쌍을 해시해 시간차 히스토그램 봉우리로 판정하는 랜드마크 방식. 정확도는 충분했지만, 우리 질의는 5초 조각이 아니라 항상 파일 전체라 2시간 영화 한 편이 18만 해시(1만 작품이면 18억 행)를 만드는 반면 VAD 서명은 9KB(1만 작품 90MB)입니다. 그래서 서명을 1차, 랜드마크는 길이 기반 후보 추리기가 놓치는 경우(긴 광고 삽입 등)의 폴백으로 두었습니다.
클라우드 설계 — 영상은 절대 올리지 않는 공유
서버는 연산이 아니라 데이터 서비스라는 결론 아래 Cloudflare Workers + KV/R2 구성으로 단계별 계획을 세웠습니다. 올라가는 것은 지문(서명)과 자막 텍스트뿐이고, 지문은 원래 소리로 되돌릴 수 없는 일방향 요약이라 프라이버시가 지켜집니다. 저장 규모와 비용도 셈했습니다 — 같은 1만 작품을 담을 때 랜드마크 색인은 11~14GB(전용 역색인)~70GB(범용 DB), VAD 서명은 90MB로 다섯 자릿수 차이가 났고, 이것이 서명을 본선으로 택한 근거의 절반입니다.
07법적 리스크 조사와 방향 전환
공유 기능을 만들기 전에 "소리 파일은 저장하지 않고 자막 텍스트만 저장·제공한다"는 구조가 법적으로 안전한지 EU·미국·일본·한국 네 법역에서 조사했습니다(판결문·법령 원문 기준, 핵심 주장은 반박 시도까지 거쳐 인용 오류 13건을 정정). 결론은 명확했습니다.
조사 전후. 항변의 핵심이었던 "오디오는 없다"가 저작권 논점을 비껴간다는 것이 확인되자, 공유 캐시는 배포하지 않고 세 방향의 재설계 과제로 남겼습니다.
쉬운 말로
영화의 대사는 영상·소리와 별개로 각본이라는 글로 보호됩니다. 그래서 소리를 글자로 받아 적은 것은 각본을 베낀 것이고, 그것을 번역한 자막은 2차적 저작물13입니다. 오디오를 올리지 않았다는 사실이 막아 주는 것은 음반·영상 자체에 대한 권리뿐이어서, 저장·전송하는 것이 정확히 보호 대상 텍스트인 이 구조에서는 방어가 되지 않습니다. 사용자가 만든 것을 그저 보관해 주는 서비스에 주어지는 면책도, 서비스가 스스로 텍스트를 만들어 매칭·제공하는 구조에는 적용되지 않습니다.
아쉬운 결과였지만 얻은 것이 큽니다. 기술로 풀 수 있는 문제와 구조로만 풀 수 있는 문제를 구분하게 됐고, 리스크 조사는 착수보다 앞서야 한다는 것을 실제 비용으로 배웠습니다. 조사 보고서와 대안 설계는 후속 과제로 문서화해 두었습니다.
08개발 방식 — 문서가 먼저, 측정이 심판
스펙 주도: 라운드마다 설계 스펙을 쓰고 승인 게이트를 거친 뒤 구현합니다. 실측이 스펙을 반증하면 스펙에 "정정" 절을 덧붙여 왜 바꿨는지 남깁니다(스펙 52건, 구현 계획 49건, 리서치 30건).
리뷰 게이트: 태스크마다 독립 리뷰, 라운드 끝에 전체 리뷰와 "반박 시도" 검증을 두어 스펙 오류·수치 오류를 출하 전에 잡았습니다 — 실제로 문턱값 계산 실수, 게이트 과차단, 문서 사실 오류가 이 단계에서 걸렸습니다.
후속 원장: 결함·관측·보류 항목을 번호 원장으로 관리해(항목 173번까지) 완료 시 원문을 지우지 않고 취소선과 완료 요지를 덧붙입니다. "고치지 않기로 한 이유"도 같은 원장에 남깁니다.
테스트 층: 단위·통합 1,880건과 Electron E2E(Playwright) — 시스템에 ffmpeg가 전혀 없는 컨테이너에서 동봉 엔진만으로 자막을 실제로 만드는 클린 E2E가 릴리스 CI에 들어 있습니다. 위 데모 캡처도 이 하네스를 재사용한 것입니다.
저장소 규약 문서: 검증 명령의 함정(부분 문자열 매치로 빈 테스트가 통과하는 문제 등)과 패키징 크래시 처방을 실측과 함께 기록해, 같은 실수를 두 번 하지 않게 합니다.
09타임라인
08.01착수 — 파이프라인 골격, CLI
08.03v0.1.0·v0.2.0 내부 릴리스 — 데스크톱 앱, 3-OS CI 골격
08.13v0.3.0 첫 공개 릴리스
08.17v0.3.1 — 문의 창구, 전사·번역 재사용(내용 주소화 캐시), 웹 LLM 모드, 센터 채널 추출
08.19v0.4.0 — 청크 전사로 누락·시간 어긋남 해소, 자동 용어집 / v0.5.0 — 3-OS 엔진 동봉, Windows GPU 자동 설치, 모델 추천
08.24v0.6.0 — 반복 자막 정리, 환각 지배 판정, 화자 경계 감지, 소재 프로파일, 자막 규격 정합, 번역 재시도
08.251,403편 배치 완주·3버전 비교 분석 → 사전 확장·긴 파일명 크래시 봉합 출하
08.26클라우드 공유 기능 법적 리스크 조사(4법역) → 방향 전환
각주
ASR / whisper.cpp — 자동 음성 인식(Automatic Speech Recognition). 소리를 글자로 받아 적는 프로그램. whisper는 OpenAI가 공개한 음성 인식 모델이고, whisper.cpp는 그것을 GPU 없이도 돌릴 수 있게 C/C++로 옮긴 구현입니다.↩
GPU — 원래 그래픽용 처리 장치인데, 같은 계산을 수천 개 동시에 하는 구조라 AI 모델 실행에 훨씬 빠릅니다. 없으면 CPU가 대신하는데 이 작업에서는 10배 가까이 느립니다.↩
SRT / VTT — 자막 파일의 두 표준 형식. "몇 초부터 몇 초까지 이 글자를 보여라"가 줄줄이 적힌 텍스트 파일입니다. 플레이어와 웹 브라우저가 각각 주로 씁니다.↩
코드 서명 — "이 프로그램은 누가 만들었고 변조되지 않았다"를 증명하는 전자 서명. 없으면 Windows는 경고를 띄우고 macOS는 실행을 막습니다. 애플 서명은 연 99달러의 개발자 계정이 필요합니다.↩
LLM / Ollama / OpenAI 호환 API — LLM은 ChatGPT 같은 대형 언어 모델. Ollama는 그런 모델을 내 컴퓨터에서 돌리는 도구이고, "OpenAI 호환 API"는 여러 회사가 같은 규격으로 제공하는 호출 방식이라 하나의 코드로 여러 서비스를 붙일 수 있습니다.↩
환각 — AI 모델이 실제로 없는 내용을 그럴듯하게 지어내는 현상. 음성 인식에서는 소리가 없는 구간에서 학습 데이터에 흔했던 문장("시청해 주셔서 감사합니다", "안녕히 주무세요")을 내놓는 형태로 나타납니다.↩
모노레포 — 여러 패키지(부품)를 한 저장소에서 함께 관리하는 방식. 부품 사이의 계약을 한곳에서 바꾸고 한 번에 테스트할 수 있습니다.↩
사이드카 — 앱 옆에 실어 보내는 별도 실행 파일. 앱이 직접 품기 어려운 네이티브 프로그램(ffmpeg, whisper.cpp)을 동봉해 호출합니다.↩
해시(sha256) — 아무리 긴 데이터라도 64자짜리 짧은 지문으로 요약하는 계산. 데이터가 한 비트라도 다르면 지문이 완전히 달라지고, 같으면 정확히 같습니다. 대신 "거의 같다"는 것은 모릅니다.↩
VAD — 음성 활동 감지(Voice Activity Detection). 지금 이 순간에 말소리가 있는지 없는지만 판정하는 기술입니다.↩
상호상관 — 두 신호를 한 칸씩 밀어 가며 "얼마나 잘 겹치나"를 점수(r, −1~1)로 매기는 계산. 가장 높은 점수가 나오는 밀림 양이 곧 "몇 초 어긋났나"의 답입니다.↩
스펙트로그램 — 소리를 "시간에 따라 어떤 높이의 소리가 얼마나 크게 났나"로 펼친 그림. 자동으로 그린 악보와 비슷합니다. 랜드마크는 그 그림에서 주변보다 두드러지게 큰 점(국소 최댓값)입니다.↩
2차적 저작물 — 원작을 번역·각색·편곡한 결과물. 만든 사람에게도 권리가 생기지만, 원작 권리자의 허락 없이 만들면 원작의 권리(번역권 등)를 침해합니다.↩