LLM 자막 번역 — 줄 밀림과 문장 그룹핑

만든 것3분조회 –

유튜브 볼 때 번역 자막을 띄워 주는 확장은 이미 여럿 있지만, 쓰다 보면 결국 유료로 넘어가고 원하는 대로 손보기도 어려워서 직접 만들어 쓰기로 했습니다. 번역은 여러 LLM 을 API 하나로 부르는 AWS Bedrock 에 맡겼고, 자막 한 줄짜리를 수없이 부르는 용도라 작은 모델을 골랐습니다.

유튜브 자막은 한 줄씩 시작·종료 시각을 달고 내려옵니다. 확장은 그 줄들을 받아 번역해 두고, 원문 줄이 화면에 뜨는 시각에 맞춰 번역문을 같은 자리에 겹쳐 놓습니다. 그러니 번역문과 원문 줄의 짝이 한 칸이라도 어긋나면 화면에는 엉뚱한 문장이 뜹니다.

자막을 넘기고 번역문을 받아 화면에 얹는 것까지는 금방 됐습니다. 그런데 영상을 틀어 보니 말하는 내용과 아래에 뜬 한국어가 계속 한 박자씩 어긋나 있었습니다.

여러 줄을 한 번에 시키면 번역이 밀림

한 줄에 한 번씩 부르면 호출이 수백 번이 되니, 처음에는 자막 여러 줄을 묶어서 한 번에 넘겼습니다.

그런데 묶어 보내면 LLM 이 줄 경계를 흐립니다. 3번 줄 번역이 4번 자리에 들어가는 한 줄 밀림이 생기고, 한 번 밀리면 그 뒤로 계속 밀린 채로 갑니다.

줄마다 번호를 붙인 JSON 으로 줘도 실제 영상에서는 계속 재현됐고, 줄 번호를 끝까지 맞춰 돌려주는 걸 이 모델이 생각보다 못합니다.

결국 앞뒤 문맥은 같이 넘기되 결과는 그 한 줄만 받는 줄 단위 번역으로 갔고, 그러면 애초에 밀릴 자리가 없어집니다.

영상 전체를 미리 번역하면 느리고 낭비

줄 단위로 바꾸고 나니 이번엔 호출이 늘어난 만큼 느려졌습니다. 영상 전체를 미리 번역하면 3시간짜리는 십수 분이 걸리고, 사용자가 보지도 않을 뒷부분까지 번역해서 그만큼 요금이 나갑니다.

그래서 재생 위치를 따라가며 번역하기로 했습니다. 지금 재생 지점 앞 14줄만 번역해 두고, 재생이 진행되면 그 창도 같이 움직입니다. 사용자가 앞으로 건너뛰면 창도 그쪽으로 옮겨갑니다.

몰입형 번역기가 “즉시” 로 느껴지는 것도 전부 빠르게 번역해서가 아니라, 지금 볼 부분만 미리 번역해 두기 때문입니다.

한 문장이 자막 세 조각에 걸침

앞의 둘보다 이게 까다로웠습니다. 자막은 말하는 속도에 맞춰 끊기니 한 문장이 조각 여럿에 걸쳐 내려오고, 어디서 끊을지는 번역과 아무 상관 없는 기준으로 정해집니다.

조각 1: "I went to the store"
조각 2: "because I needed"
조각 3: "some milk"

이걸 조각별로 번역하면 영어와 한국어의 어순이 반대라 동사가 갈 곳이 없어 사라지거나, 조각마다 “~습니다” 가 하나씩 붙습니다. 조각 하나만 보고 자연스러운 한국어를 만드는 건 애초에 불가능합니다.

그래서 번역에 넘기기 전에 조각을 문장 단위로 다시 합치는 문장 그룹핑을 넣었고, 어디까지가 한 문장인지는 이렇게 봤습니다.

  • 문장부호로 끝나면 한 문장
  • 침묵이 2.5초 이상이면 끊긴 것으로 봄
  • 아무리 길어도 6조각을 넘지 않게 상한

합쳐진 문장을 통째로 번역하고, 그 문장이 재생되는 동안에는 같은 한국어 줄을 계속 띄워 둡니다. 영어 자막은 원래 타이밍 그대로 흐르고 한국어만 문장 단위로 붙어 있는 모양이 됩니다.

조각마다 부르던 걸 문장마다 한 번씩 부르게 됐으니, 덤으로 호출 수도 약 40% 줄었습니다.

자막의 줄 단위와 언어의 문장 단위가 안 맞음

세 문제 모두 뿌리가 같은데, 자막이 나뉘는 단위는 화면에 띄울 길이와 말하는 속도가 정하는 반면 번역이 기대는 단위는 문장이라, 둘이 안 맞는 자리마다 문제가 났습니다.

그래서 셋 다 자막 쪽 단위를 번역 쪽 단위에 맞춰 주는 걸로 풀렸습니다. 밀림은 한 줄씩만 받아서, 속도는 지금 볼 구간만 잘라서, 어순은 조각을 문장으로 되돌려서 맞췄습니다.

LLM 에 통짜로 맡기면 편할 것 같지만, 번역 품질은 모델보다 입력을 어떤 단위로 잘라 주느냐에서 갈렸습니다.

  1. 불러오는 중