← 이야기 목록
생산성·AI

💬 일본어 영상에 한국어 자막 자동으로 붙이기 — 3단계로 나눠야 했던 이유

Whisper 하나면 될 줄 알았는데 아니었습니다. 받아쓰기와 번역을 왜 갈라야 했는지, 빠른 모델이 무엇을 대신 내주는지, 그리고 메모리가 터진 두 군데를 실제로 만든 파이프라인 기준으로 정리했습니다.

2026년 8월 4일

집에 일본 드라마와 애니메이션 영상이 꽤 많이 쌓여 있습니다. 한국어 자막이 없는 것들이었고, 하나씩 구하는 건 현실적이지 않았어요. 서버에 음성 인식 모델이 이미 돌고 있었으니 자동으로 붙이면 되겠다 싶었습니다. 그런데 첫 시도에서 막혔습니다. 도구가 제가 원하는 걸 애초에 안 해주더라고요.

Whisper는 한국어 자막을 바로 만들어주지 않습니다

음성 인식 모델 Whisper에는 번역 기능이 있습니다. 그래서 일본어 영상을 넣으면 한국어 자막이 나올 거라 생각했어요. 아니었습니다. 번역 기능의 도착 언어는 영어 하나뿐입니다. 도착 언어를 다른 언어로 지정하면 가끔 되기도 하는데, 모델이 그렇게 학습된 게 아니라 신뢰할 수 없다는 게 개발자 쪽 답변입니다 (출처: OpenAI Whisper 저장소 Q&A).

일본어 → 영어 → 한국어로 두 번 돌리는 방법도 있습니다. 다만 존댓말과 반말, 인물 관계 같은 정보가 영어를 지나며 뭉개집니다. 일본어에는 있고 영어에는 없는 구분이라서요.

그래서 역할을 갈랐습니다.

받아쓰기 → Whisper   (일본어 음성을 일본어 텍스트로)
번역     → 로컬 LLM  (일본어 텍스트를 한국어로)

한 도구가 다 해주길 바랐지만, 각자 잘하는 걸 시키는 쪽이 결과가 나았습니다. 번역을 맡길 모델을 어떻게 골랐는지는 로컬 LLM 모델 고르기에 따로 정리해 뒀습니다.

받아쓰기보다 먼저 할 일이 있었습니다

영상 파일을 그대로 넣으면 정확도가 떨어집니다. 배경음악과 효과음이 대사와 함께 들어가거든요. 모델은 소리가 있으면 뭐라도 받아쓰려고 하니까, 음악 구간에서 엉뚱한 문장이 나옵니다.

5.1 채널 영상이라면 방법이 있습니다. 대사는 중앙 채널에 들어 있어요. 그것만 뽑으면 배경음이 상당 부분 빠집니다.

ffmpeg -i input.mkv -af "pan=mono|c0=FC" out.wav

문제는 모든 영상이 5.1이 아니라는 점입니다. 그래서 순서를 정해두고 실패하면 다음으로 넘어가게 했습니다.

중앙 채널 추출 시도 → 실패하면 다운믹스 → 그래도 안 되면 원본 그대로

여기서 배운 게 있습니다. 소리를 키우는 것잘 인식되게 만드는 것은 다릅니다. 조용한 대사를 살리겠다고 음량을 강하게 정규화했다가 더 나빠진 적이 있어요. 무음 구간의 잡음까지 대사 크기로 커지면서, 모델이 없는 말을 지어내기 시작했거든요. 지금은 음량 조정을 약하게만 걸고, 아예 끄는 선택지도 남겨뒀습니다.

빠른 모델은 시간을 아끼는 대신 싱크를 잃었습니다

받아쓰기에는 large-v3를 씁니다. 정확한 대신 느려요. 그래서 일본어에 특화된 경량 모델을 시험해봤습니다. 5~6배 빨랐습니다.

그런데 자막은 무엇을 말했는지만이 아니라 언제 말했는지가 같이 맞아야 하는 물건입니다. 이쪽이 어긋났어요. 대사는 그럭저럭 맞는데 화면과 자막이 따로 놉니다.

원인은 구조였습니다. 이 모델은 원본에서 디코더 층을 크게 덜어내 가볍게 만든 버전이라, 단어 하나하나가 몇 초에 나왔는지를 잡아내지 못합니다. 단어 단위 타임스탬프 옵션을 켜자 아예 메모리 할당 오류로 죽었고요. 지금은 앱이 모델 이름을 보고 이 옵션을 자동으로 끄도록 해뒀습니다.

결국 large-v3를 유지합니다. 경량 모델은 내용만 빠르게 훑을 때 쓰고요. 속도는 대개 정확도를 담보로 얻는다고들 하는데, 여기서 담보로 잡힌 건 정확도가 아니라 시간 정보였습니다.

번역은 한 줄씩 하면 안 됩니다

자막 파일은 줄 단위로 쪼개져 있습니다. 그래서 한 줄씩 번역하기 쉬운데, 그렇게 하면 앞뒤가 끊깁니다. 같은 인물이 세 줄 전에는 반말을 쓰다가 지금은 존댓말을 씁니다.

두 가지를 붙였습니다.

① 앞 8줄을 함께 넘깁니다. 지금 줄만 주는 게 아니라 직전 맥락을 같이 줍니다. 말투와 호칭이 유지되는 데 이게 가장 크게 작용했어요.

② 작품 정보를 폴더에서 물려받게 했습니다. 등장인물 이름, 관계, 말투 같은 걸 매번 다시 입력하면 안 쓰게 됩니다. 그래서 폴더 계층을 따라 지침이 쌓이도록 만들었습니다.

/media/.subtitle-notes.txt              모든 작품 공통 지침
/media/작품A/.subtitle-notes.txt         작품 A 정보
/media/작품A/시즌1/.subtitle-notes.txt   시즌별 추가 정보

번역할 때 위에서 아래로 합쳐 프롬프트 앞에 붙입니다. 시즌 1에만 나오는 호칭은 시즌 1 폴더에만 적어두면 되니 관리가 편해졌어요.

번역 전용으로 나온 모델도 써봤습니다. 형식은 정확히 지키는데 반말을 전부 존댓말로 바꿔놓더군요. “바보 아니야?”가 “바보세요?”가 되는 식이라 자막에는 못 씁니다. 기술 문서 번역에는 오히려 좋아서 그 용도로만 남겨뒀습니다.

받아쓴 결과를 그대로 쓰면 한 줄이 너무 길어지는 경우도 있어서, 단어 단위 타임스탬프를 기준으로 최대 6초 / 42자로 다시 잘랐습니다.

터지는 곳이 두 군데였습니다

GPU는 한 장인데 올려야 할 모델은 셋입니다. 받아쓰기, 번역, 그리고 이미지 생성. 그래서 서로 비켜주게 만들었습니다.

자막 생성 시작 → 번역 모델 내림 + 이미지 모델 내림
번역 시작     → 받아쓰기 모델 내림 + 이미지 모델 내림

모든 방향을 다 만들어야 합니다. 한쪽만 구현해두면 반대 순서로 작업할 때 CUDA out of memory가 납니다. 실제로 그렇게 났고요.

그런데 이걸 다 해두고도 자막 작업이 계속 실패했습니다. 증상이 이상했어요. 작업이 멈추면서 화면이 저절로 새로고침됐습니다. VRAM은 넉넉했고요.

이번엔 시스템 램이었습니다. 이미지 생성 컨테이너가 아무 작업을 하지 않는 동안에도 램 6.7GB를 붙들고 있었고, 커널이 메모리 부족을 판단해 자막 프로세스를 죽이고 있었습니다. 화면이 새로고침되는 건 죽은 컨테이너가 자동으로 다시 뜨는 모습이었어요.

dmesg -T | grep -i "killed process"

지금은 이미지 생성을 자동 시작에서 빼두고 필요할 때만 켭니다. GPU 메모리만 들여다보고 있으면 놓치는 층이 하나 더 있었습니다.

정리하며 — 어디까지 되고, 어디부터 안 되는지

지금은 영상을 넣으면 파일명.ko.srt가 나옵니다. 미디어 서버가 자동으로 인식하니 재생만 누르면 되고요.

다만 정직하게 적어둡니다. 배경음악이 많은 구간은 여전히 정확도가 떨어집니다. 고유명사는 자주 틀리고, 존댓말과 반말이 어긋나는 줄도 나와요. 같은 문장이 실행할 때마다 조금씩 다르게 번역되는 문제도 남아 있습니다. “감사합니다”였다가 “고마워요”가 되는 식인데, 번역 온도를 고정하지 않아서입니다. 아직 손대지 못했어요.

사람이 만든 자막을 대체하는 물건이 아니라 내용을 따라갈 수 있게 해주는 도구에 가깝습니다. 그래도 자막이 없어서 못 보던 걸 보게 됐으니 목적은 달성했습니다. 완벽하지 않은 자막과 아예 없는 자막의 차이는, 생각보다 큽니다.

다른 이야기 보기 홀로라도 도구 →