개발/AI

내 목소리로 문서를 읽어주는 로컬·무료 제로샷 TTS 설계

goldtagworks 2026. 7. 9. 21:45

아이를 재우면서 동화책을 읽어주다 목이 다 쉰 밤이었다. 문득 이런 생각이 들었다. 내가 없거나 지쳐 있을 때도 아이가 아빠 목소리로 책을 들을 수 있으면 좋겠다고. 내 목소리로.

시작은 사실 더 거슬러 올라간다. 원래는 마음이 어지러울 때 스님 서적을 읽고 싶었는데, 눈으로 읽는 것보다 귀로 듣고 싶었다. 스님께 동의를 구하고, 책을 텍스트로 옮긴 다음, 그 텍스트를 다시 스님 목소리로 되돌려 듣는 실험을 했다. 개인 프로젝트 castforge는 거기서 태어났다. "문서를 동의받은 화자의 목소리로 낭독한다"가 원류였고, 지금 이 시리즈에서 다루는 "내 목소리" 갈래는 거기서 갈라져 나온 가지다.

그래서 질문은 이거였다. 유료 API도, 서버도 없이, 내 컴퓨터 안에서만, 내 목소리로 문서를 읽어주는 걸 만들 수 있을까?

결론부터 말하면 된다. 다만 하나의 벽이 끝까지 남는다. 그 벽 이야기는 3편에서 정직하게 하겠다. 이번 편은 그 전에 필요한 것, 전체 그림부터 그린다. 코드 조각을 던지기 전에 지도가 먼저다. 지도가 있으면 2편에서 어떤 코드가 왜 거기 있는지 헤매지 않는다.

왜 굳이 로컬·무료인가

CLOVA, ElevenLabs, Supertone 같은 유료 API를 쓰면 훨씬 쉽고 품질도 좋다. 그런데 나는 세 가지 이유로 로컬·무료·오프라인만 쓰기로 못을 박았다.

첫째, 목소리는 개인 생체정보다. 특히 아이에게 들려줄 부모 목소리, 동의받은 스님 목소리는 절대 남의 서버에 올리고 싶지 않았다. 둘째, 문서 한 권을 통째로 낭독시키면 유료 API 과금이 만만치 않다. "무료로 아낀다"는 이 프로젝트의 정체성이다. 셋째, 오프라인으로 돌면 인터넷 없이도, 서비스가 종료돼도 계속 쓸 수 있다. 가중치(모델 파일)를 최초 1회만 받으면 그다음부터는 내 맥 안에서 끝난다.

이 세 제약이 프로젝트의 뼈대를 결정했다. 그래서 아래 설계 전체가 "유료·서버 금지"라는 전제 위에 서 있다.

전체 파이프라인: 다섯 단계로 쪼갠다

내 목소리로 문서를 읽히는 일은 하나의 마법이 아니라, 다섯 개의 독립된 단계를 이어 붙인 파이프라인이다.

 
문서(docx/hwpx/pptx/pdf)
      │
      ▼
① 텍스트 추출 ──▶ ② 한국어 전처리·청킹
                          │
   내 녹음 ──▶ ③ 참조 클립 준비      │
                          ▼
              ④ 로컬 TTS 모델로 청크별 합성 (자기 목소리 클로닝)
                          │
                          ▼
              ⑤ 조립(크로스페이드·음량 정규화) ──▶ mp3/wav

각 단계가 하는 일을 한 줄씩 보면 이렇다.

① 텍스트 추출. docx·hwpx·pptx·pdf에서 순수 텍스트만 뽑는다. 표, 머리말, 빈 문단 같은 잡음을 걸러내는 게 생각보다 일이다.

② 한국어 전처리·청킹. 한국어는 그냥 넣으면 안 된다. "3,250원"을 "삼천이백오십 원"으로 바꿔줘야 제대로 읽는다. 그리고 긴 글은 모델이 한 번에 못 먹으므로 문장·문단 경계로 잘라 청크(chunk, 덩어리)로 나눈다. 바로 이 "자르는" 순간에 3편에서 다룰 벽이 예약된다.

③ 참조 클립 준비. 내 목소리 3~30초짜리 깨끗한 샘플. 이게 "이 목소리로 읽어줘"의 기준이 된다. 제로샷(zero-shot, 재학습 없이 샘플 몇 초만으로 목소리를 흉내)의 핵심 입력이다.

④ 청크별 합성. 청크 하나하나를 참조 클립 목소리로 음성 합성한다. 여기가 클로닝이 실제로 일어나는 지점.

⑤ 조립. 잘라서 만든 오디오 조각들을 다시 이어 붙이고, 경계를 크로스페이드로 부드럽게 하고, 음량을 고르게 맞춰 최종 mp3/wav로 뽑는다.

이 구조에서 핵심은 ②에서 자르고 ⑤에서 다시 붙인다는 점이다. 자른 걸 붙이면 이음매가 생긴다. 그게 이 시리즈의 주제다. 하지만 그 전에, ④에서 쓸 모델부터 골라야 한다.

모델 선택: "클로닝 되는 것"만 남기면 후보는 확 줄어든다

TTS 모델은 많다. 그런데 이 프로젝트의 목적은 고정된 성우 목소리가 아니라 내 목소리다. 그러면 조건이 하나 붙는다. 짧은 참조 클립만으로 임의의 목소리를 흉내 낼 수 있어야 한다(제로샷 클로닝). 이 조건 하나로 유명 모델 상당수가 탈락한다.

먼저 걸러낸 것: Kokoro · Piper · MeloTTS. 품질은 좋지만 고정 화자 모델이라 내 목소리를 만들 수 없다. 입문자가 "이거 좋다던데" 하고 붙잡기 쉬운 함정이라 미리 짚어둔다.

남은 후보를, 로컬·무료·한국어+영어·Apple Silicon 기준으로 정리하면 이렇다.

순위모델라이선스한국어참조음성이럴 때
1 GPT-SoVITS v2Pro MIT (상업 OK) 강함 ~5s 최고 한국어 품질, 세팅 수고 감수
2 CosyVoice2-0.5B Apache-2.0 공식 지원 ~3s 작고 라이선스 관대·공식 한국어 균형
3 XTTS-v2 CPML(개인용) 네이티브 ~6s 가장 쉬운 시작(pip install coqui-tts)
4 Chatterbox Multilingual MIT 약함 ~5–10s 영어 주력 + Mac 지원 최상
5 F5-TTS (MLX) MIT(코드) 비공식 ~3s 영어 전용·맥 최고속
6 OpenVoice v2 MIT 음색 전이 수초 가장 가벼움

입문자에게 내 추천은 이렇다. 일단 돌려보고 싶다면 XTTS-v2. pip install coqui-tts 한 줄이면 시작이라 진입장벽이 가장 낮다. 한국어 품질을 진지하게 원하면 GPT-SoVITS v2Pro. 세팅은 번거롭지만 한국어가 제일 자연스럽다. 그 중간이 CosyVoice2-0.5B. 모델이 작고 한국어를 공식 지원하며 라이선스도 관대하다. 나는 결국 한국어 문서를 다뤄야 해서 GPT-SoVITS와 CosyVoice2 쪽으로 기울었다.

라이선스 함정을 먼저 밟지 마라

무료라고 다 같은 무료가 아니다. XTTS는 CPML로 개인용이고, F5·Fish 계열 가중치는 CC-BY-NC로 비상업이다. 개인적으로 아이에게 들려주는 용도면 상관없지만, 나중에 이걸 서비스나 상업 프로젝트로 확장할 생각이라면 MIT/Apache 모델(GPT-SoVITS·CosyVoice2·Chatterbox·OpenVoice)만 안전하다. 처음 고를 때 이 갈림길을 모르면 나중에 모델을 통째로 갈아야 하는 일이 생긴다. 그래서 1편에서 못 박아둔다.

참조음성은 코드가 아니라 사람이다

기술 이야기 사이에 하나만 진지하게. 참조 클립으로 쓰는 목소리는 개인 생체정보다. 그래서 이 파이프라인은 처음부터 목소리 데이터를 로그에 남기지 않고, 깃(git)에 커밋하지 않고, 외부에 업로드하지 않도록 설계했다. 내 목소리든, 동의받은 스님 목소리든, 부모 목소리든 마찬가지다. 어느 갈래에서도 동의 없는 목소리 클로닝은 하지 않는다. 이건 기능이 아니라 이 프로젝트가 존재하는 방식이다.

시작 전 준비물

설계를 따라오려면 이 정도가 있으면 된다.

  • Python 3.10~3.11 (모델에 따라 3.12). 버전에 민감하니 가상환경 권장
  • ffmpeg / ffprobe. 오디오 조립에 필수. 맥은 brew install ffmpeg
  • Apple Silicon 맥 권장. M 시리즈(MPS/Metal) 기준으로 설계했고 CPU 폴백도 되지만 느리다
  • 조용한 환경에서 녹음한 내 목소리 3~5분. 참조 클립 재료. 에어컨·키보드 소리 없이

마지막에 남는 벽, 그리고 다음 편

여기까지가 지도다. 문서를 넣으면 텍스트가 나오고, 한국어를 다듬어 자르고, 내 목소리 샘플로 청크마다 합성해서, 다시 이어 붙인다. 논리적으로는 깔끔하다.

그런데 실제로 돌려보면, 이어 붙인 경계에서 목소리가 미세하게 튄다. 방금 전까지 나였던 목소리가 다음 문단에서 약간 다른 사람이 된다. 이 **청크 경계 seam(이음매)**이 오픈소스 제로샷 TTS의 구조적 한계다. 원류 프로젝트 castforge는 결국 이 벽 때문에 자기 목소리 클로닝을 포기하고 프리셋 음성으로 후퇴했다. 그 이야기는 이 시리즈의 마지막, 3편에서 과장 없이 하겠다.

그 벽으로 가기 전에, 2편에서는 이 지도를 실제 코드로 옮긴다. docx에서 텍스트를 뽑고, 한국어가 숫자를 제대로 읽게 다듬고, 긴 글을 청크로 자르는 것까지, 그대로 복붙해서 돌아가는 형태로.