본문으로 건너뛰기
5분 AI.
TRENDSHIFT WEEKLY #18 딥다이브 · 2026-10-23 분석

VoiceStudio 딥다이브
— 목소리 AI를 내 컴퓨터의 제품으로 만드는 설계

데스크탑 앱

빠른 판단

무엇
여러 로컬 음성 AI 엔진을 복제·합성·전사·더빙·오디오북 작업으로 묶는 음성 제작 앱
이럴 때
참고 녹음과 대본을 로컬 모델로 처리하며 목소리 생성부터 다국어 영상 더빙까지 한 작업실에서 이어갈 때
입력
참고 녹음·대본·더빙할 영상과 선택한 음성 엔진
결과
복제·합성 음성, 전사문·더빙 오디오·오디오북
글을 소리로 바꾸는 모델 하나가 어떻게 음성 복제·영상 더빙·받아쓰기·오디오북 제작 도구가 될까요? VoiceStudio는 Python 추론 서버, React 화면, Electron 데스크톱 셸을 연결해 그 답을 보여줍니다. 이 글은 모델의 성능 순위보다 여러 AI 엔진을 하나의 안정적인 제품으로 묶는 방법을 따라갑니다. 저장소 debpalash/VoiceStudio · ⭐82,109 · 포크 9,065 · 주 언어 Python · AGPL-3.0. 수치는 2026-10-23 GitHub 기준이며, 소스 설명은 커밋 38740bf 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

음성 모델을 쓰는 앱이면서, 여러 모델을 운영하는 작은 작업실

VoiceStudio는 내 하드웨어에서 음성을 생성하고 가공하는 오픈소스 제작 도구입니다. 참고 녹음으로 목소리를 복제하고, 글로 목소리의 성격을 지정하고, 영상의 음성을 다른 언어로 다시 입힐 수 있습니다. README는 받아쓰기·전사·오디오북까지 묶어 “로컬 ElevenLabs 대안”으로 소개합니다.

이 저장소의 핵심 학습 대상은 음성 모델 자체와 그 모델을 오래 안전하게 돌리는 제품 코드 사이의 연결입니다. 화면에서 생성 버튼을 누르는 일과 GPU에서 신경망을 계산하는 일은 서로 다른 프로그램에서 일어납니다. 그 사이에는 입력 검증, 모델 선택, 메모리 관리, 진행 상황 전달, 파일 저장이 필요합니다.

비유로 시작하기

음성 AI 모델이 가수라면, VoiceStudio는 녹음실 전체입니다

모델은 노래하는 사람에 가깝습니다. 하지만 녹음실에는 접수하는 직원, 녹음 순서를 정하는 사람, 음향을 보정하는 장비, 결과를 보관하는 서랍도 있어야 합니다.

React가 주문을 받고, FastAPI가 작업을 배분하며, TTS 엔진이 소리를 만듭니다. FFmpeg와 오디오 처리 코드가 결과를 다듬고, SQLite와 파일 시스템이 작업을 기억합니다.

서로 다른 네 가지 입력을 구분하자

기능넣는 것 → 받는 것코드에서 볼 경계
음성 합성글 + 선택한 목소리 → 음성backend/api/routers/generation.py
음성 복제참고 녹음 + 글 → 참고 목소리를 닮은 음성backend/services/tts_backend.py의 참고 음성 처리
영상 더빙영상 + 목표 언어 → 시간에 맞춘 새 음성·영상backend/api/routers/dub_core.py, backend/api/routers/dub_translate.py, backend/api/routers/dub_generate.py
전사·받아쓰기녹음 또는 마이크 음성 → 글backend/api/routers/dictation.py 등
용어를 잡고 가기
TTS와 ASR은 방향이 반대다
TTS는 글을 소리로 바꾸는 기술입니다. ASR은 소리를 글로 바꾸는 음성 인식입니다. 더빙은 ASR → 번역 → TTS를 이어 붙이므로 모델 하나만으로 끝나지 않습니다.

제품 이름은 VoiceStudio지만 기본 모델과 Python 패키지에는 OmniVoice라는 이름이 남아 있습니다. pyproject.toml의 배포 이름은 omnivoice이고, 기본 모델 어댑터는 OmniVoiceBackend입니다. 이름이 다르다고 별도 서버 두 개로 오해하지 않는 것이 첫 번째 독해 요령입니다.

현재 데스크톱과 웹 화면은 모두 electron/에 있습니다. README는 v0.5.3을 마지막 Tauri 릴리스로 안내합니다. Rust 코드가 보이더라도 현재 앱 셸을 Tauri라고 설명하면 틀립니다.

2왜 주목받는가

로컬 음성 AI 수요와, 모델을 실제 작업 도구로 바꾸는 구현

TrendShift Weekly #18의 분석 대상으로 선정되었고, 2026-10-23 GitHub 기준 스타는 82,109개, 포크는 9,065개입니다. 2026-04-09 생성 이후 빠르게 관심을 모은 프로젝트입니다. 다만 이 시점의 누적 수치만으로 주간 증가량이나 특정 홍보 효과를 계산할 수는 없습니다.

다음 세 가지는 기능과 코드에서 읽어낼 수 있는 관심 요인에 대한 해석입니다. 실제 이용자 설문이나 트래픽 분석으로 입증한 유행의 원인은 아닙니다.

VoiceStudio의 매력은 모델 하나의 승부보다, 사용자가 자기 장비와 목적에 맞게 음성 작업 경로를 고를 수 있다는 데 있습니다.

“대안”은 무엇을 바꾼다는 뜻일까?

아래는 저장소의 제품 지향과 구조를 비교한 표입니다. ElevenLabs 등 특정 서비스의 현재 가격·요금제·기능 전체를 대조한 벤치마크는 아닙니다. 클라우드 음성 서비스를 이용하는 방식과 로컬 제작 도구를 운영하는 방식의 차이에 초점을 맞춥니다.

비교 관점클라우드 음성 서비스 이용 방식VoiceStudio의 선택과 대가
계산 위치제공자가 운영하는 서버에 작업 요청로컬 엔진 사용 시 내 CPU·GPU에서 처리. 장비 관리가 필요합니다.
자료 흐름서비스에 전달하는 자료와 정책을 확인로컬 경로를 선택하면 원본을 외부 추론 서버로 보낼 필요가 없습니다.
비용 구조각 서비스의 사용량·구독 조건에 따름장비·전기·저장 공간 비용이 듭니다. 선택한 유료 외부 공급자 비용은 별도입니다.
모델 선택제공자가 노출하는 모델·기능 사용TTSBackend 어댑터로 엔진 교체. 설치·호환성 차이도 함께 다뤄야 합니다.
개발 학습주로 API 호출과 결과 처리 학습API 뒤의 수명 관리·오류 복구·미디어 파이프라인까지 읽을 수 있습니다.
품질·속도제공 환경과 모델에 좌우됨모델·언어·참고 녹음·하드웨어에 좌우됩니다. 일괄 우위를 뜻하지 않습니다.
이 레포가 좋은 교재인 이유
정상 동작보다 실패 처리에서 더 많이 배운다

모델 로딩이 늦는 경우, GPU 메모리가 부족한 경우, 사용자가 요청을 취소한 경우가 별도 함수와 테스트로 남아 있습니다. “모델을 호출했다” 다음에 무엇을 해야 제품이 되는지 볼 수 있습니다.

표현을 정확히 읽기
“완전 로컬”과 “네트워크 요청이 전혀 없음”은 다릅니다

README도 원격 서비스는 선택 사항이고 분석 수집에는 동의가 필요하다고 설명합니다. 모델 다운로드, 업데이트, 선택적 외부 번역·LLM, 계정·동기화 코드가 존재합니다. 로컬 작업 가능성을 모든 설정에서의 무통신 보장으로 확대해서 읽으면 안 됩니다.

3기술 스택 전체 지도

의존성 선언, 잠금 파일, 플랫폼별 실행 환경을 나눠 읽기

버전 표에는 두 종류의 숫자가 나옵니다. >=는 허용하는 하한, ^는 호환되는 범위 선언입니다. uv.lock과 bun.lock은 재현 가능한 설치에 필요한 해석 결과를 보관합니다. 의존성 파일의 허용 범위와 실제 선택된 버전을 구분해야 같은 환경을 재현할 수 있습니다.

백엔드: HTTP 서버 + 추론 + 오디오 처리

기술실제 버전 근거이 프로젝트에서의 역할
Pythonpyproject.toml: >=3.11API, 모델 실행, 파일·프로세스 제어. CI 주 테스트는 Python 3.11입니다.
FastAPI·Pydantic선언 fastapi<0.137; 잠금 0.136.3, Pydantic 2.13.4폼·업로드 입력 검증, HTTP·WebSocket 라우트
Uvicorn잠금 0.52.4FastAPI의 ASGI 실행 서버
PyTorch 계열선언 torch>=2.4; 잠금 torch·torchaudio 2.8.0, torchvision 0.23.0텐서 계산과 신경망 추론. 잠금 파일에는 CPU·CUDA 변형도 있습니다.
Transformers선언 >=5.10.0; 잠금 5.15.1PreTrainedModel 기반 모델 구성·가중치 로딩
음성 인식whisperx>=3.1.0, faster-whisper>=1.0.0전사와 단어 시간 정렬. WhisperX 잠금에는 3.4.5와 3.7.4가 함께 존재합니다.
미디어 처리demucs>=4.0.1, imageio-ffmpeg>=0.6.0, soundfile 잠금 0.14.0음원 분리, FFmpeg 탐색, 오디오 읽기·쓰기
저장·이력표준 라이브러리 sqlite3; alembic>=1.16, 잠금 1.19.1프로필·생성 이력·작업 저장과 스키마 이행
번역·표식·연결Argos 잠금 1.11.0, AudioSeal 0.2.0, MCP 1.29.0로컬 번역, 합성 음성 표식, 에이전트 도구 연결

표의 잠금 버전은 uv.lock에 나타나는 항목 기준입니다. 플랫폼 조건이나 별도 엔진 환경에 따라 설치 조합은 달라집니다. 예를 들어 mlx-audio>=0.4.4는 sys_platform == 'darwin' and platform_machine == 'arm64' 조건으로 제한됩니다.

Gradio도 >=6.15.1 의존성에 남아 있지만, 현재 제품 화면은 React·Electron입니다. 패키지 목록에 있다는 이유만으로 메인 UI 기술이라고 판단하지 않는 것이 중요합니다.

프론트엔드와 데스크톱 셸

계층electron/package.json 선언읽어볼 구현
화면·타입React ^19.3.0, TypeScript ^7.0.2electron/src/renderer/src/features/
서버 상태·경로TanStack Query ^5.102.8, Router ^1.170.36요청 상태, 캐시, 기능별 화면 이동
스타일·상태Tailwind CSS ^4.3.3, Zustand ^5.0.15테마, 재사용 UI, 화면 상태
오디오 UIwavesurfer.js ^7.12.12음성 파형과 재생 인터페이스
데스크톱 실행Electron ^44.3.0, electron-updater ^6.8.9electron/src/main/, electron/src/preload/
번들·테스트Vite ^8.3.0, electron-vite 6.0.0-beta.1, Vitest 4.1.11웹 번들과 main·preload·renderer 빌드, 단위 테스트

인프라·빌드·네이티브 보조 기능

앱 버전의 기준은 루트 package.json의 0.5.7-beta.1입니다. electron/electron-builder.config.mjs가 이 값을 직접 읽습니다. 최신 안정 릴리스 v0.5.6과 현재 개발 커밋의 버전을 혼동하지 마세요.

모바일은 별도 네이티브 클라이언트다

iOS는 Swift·SwiftUI이며 mobile/ios/VoiceStudioKit/Package.swift에 Swift 도구 버전 6.0, iOS 최소 17이 선언되어 있습니다. Android의 mobile/android/gradle/libs.versions.toml에는 Kotlin 2.2.21, AGP 8.12.0, Compose BOM 2025.10.01이 있습니다.

docs/mobile.md와 mobile/contract/SPEC.md는 현재 경로를 컴퓨터의 백엔드에 연결하는 보조 앱으로 설명합니다. iOS mobile/ios/VoiceStudioKit/Sources/VoiceStudioKit/Networking/APIClient.swift에서도 HTTP 요청과 페어링된 서버 검증을 확인할 수 있습니다. 이 클라이언트의 음성 생성 요청은 페어링된 서버에서 처리됩니다.

4아키텍처 심화 분석

버튼 한 번 뒤에 있는 프로세스 경계와 네 가지 설계 패턴

전체를 “Python 프로그램에 화면을 붙였다”라고만 보면 중요한 경계를 놓칩니다. 데스크톱 셸, 화면, HTTP 서버, 모델 계산, 외부 엔진 프로세스가 서로 다른 책임을 가집니다. 아래 도식의 화살표는 대표적인 요청 경로이며 모든 라우트를 표시한 것은 아닙니다.

[사용자] | v [React renderer: electron/src/renderer/src/] | apiFetch / 생성 요청 | 제한된 OS 기능 요청 | v | [preload: contextBridge] | | IPC v v [Electron의 /api 프록시] <---- [Electron main] | 서버 시작·종료·상태 감시 | HTTP | v | Python 프로세스 실행 [FastAPI: backend/main.py] <------------+ | 설정 기본값: backend 포트 3900 +-- /generate -------> [generation.generate_speech] | | | [엔진 선택·입력 검증·텍스트 정규화] | | | [model_manager의 제한된 작업 풀] | | | [TTSBackend 공통 인터페이스] | / \ | [OmniVoice 모델] [별도 엔진 / sidecar] | \ / | [오디오 텐서] | | | [효과·표식·WAV 저장·이력] | +-- /dub/* ----------> [추출 → 분리 → 전사 → 번역 → 합성 → 조립] +-- /mcp ------------> [FastMCP 도구 → 같은 앱의 API] | +-- [SQLite: 프로필·이력·작업·설정] +-- [파일: 참고 녹음·출력 WAV·더빙 중간 결과] [웹 브라우저 / 모바일 앱] -- HTTP --> [같은 FastAPI 백엔드] 웹 배포에서는 FastAPI가 React 빌드 결과도 제공합니다.

config/app.json은 기본 포트를 백엔드 3900, 웹 개발 3901, Electron 개발 3902로 나눕니다. 실제 포트는 실행 설정에 따라 바뀔 수 있습니다. 웹 배포의 API 경로는 electron/src/renderer/src/lib/api/client.ts의 resolveApiBase()가 구분합니다.

패턴 1. 프로세스 분리와 제한된 다리

electron/src/main/backend.ts의 resolveSpawnPlan()은 패키지 앱인지 개발 실행인지에 따라 Python 실행 계획을 만듭니다. 패키지에서는 관리되는 런타임의 Python을 쓰며, 개발 환경에서 가상환경이 없으면 설정 안내를 반환합니다. 서버를 띄우는 순간에 무거운 의존성 설치까지 몰래 시작하지 않도록 구분한 구조입니다.

electron/src/preload/index.ts는 contextBridge와 ipcRenderer.invoke()로 기능별 통로를 노출합니다. 예를 들어 업데이트 확인과 파일 저장을 화면이 임의의 시스템 명령으로 실행하는 대신 이름과 자료형이 정해진 요청으로 전달합니다.

// electron/src/main/index.ts의 실제 창 설정 일부
webPreferences: {
  preload: PRELOAD_PATH,
  contextIsolation: true,
  sandbox: false,
  nodeIntegration: false,
}

contextIsolation과 nodeIntegration: false는 화면과 시스템 기능의 경계를 만듭니다. 동시에 sandbox: false도 실제 설정입니다. 따라서 “Electron의 모든 격리 장치가 켜져 있다”라고 해석해서는 안 됩니다. 코드 주석은 ESM preload 사용을 그 이유로 설명합니다.

비유

매장 손님이 창고의 모든 문 열쇠를 받는 대신, 접수 창구에 정해진 요청서를 내는 구조입니다. preload가 요청서의 형식을 정하고, main이 실제 OS 작업을 수행합니다.

패턴 2. 어댑터와 지연 레지스트리

backend/services/tts_backend.py의 TTSBackend는 엔진마다 달라지는 호출 방식을 공통 인터페이스로 감쌉니다. generate()의 계약은 형태가 (1, n_samples)인 텐서 반환입니다. 샘플레이트는 별도 속성이므로 결과 배열만 보고 재생 속도를 추측하지 않아도 됩니다.

# 실제 인터페이스의 핵심만 축약한 코드
class TTSBackend(ABC):
    @property
    @abstractmethod
    def sample_rate(self) -> int: ...

    @property
    @abstractmethod
    def supported_languages(self) -> list[str]: ...

    # generate(...) -> torch.Tensor
    # ensure_ready(): 계산 전에 모델을 준비
    # unload(): 사용이 끝난 모델 자원을 해제

_REGISTRY에는 omnivoice, kittentts, mlx-audio 등의 ID와 클래스가 연결됩니다. _LazyRegistry.__getitem__()은 일부 엔진 모듈을 처음 조회할 때 가져옵니다. 선택하지 않은 엔진 때문에 앱 시작부터 모든 무거운 모듈을 읽는 부담을 줄이는 방법입니다.

get_active_tts_backend()는 선택한 엔진 인스턴스를 재사용합니다. 엔진을 바꿀 때 이전 인스턴스의 사용이 끝나면 자원을 해제하고, MLX 모델은 엔진 ID가 같더라도 세부 모델 선택이 달라지면 교체합니다. get_backend_class()는 요청에서 지정한 엔진을 찾는 공통 진입점입니다.

설계 용어
어댑터는 호출 방식의 번역기다
엔진 A는 파일 경로를, 엔진 B는 배열을, 엔진 C는 외부 서버 요청을 요구할 수 있습니다. 상위 기능은 공통 계약만 알고, 각 어댑터가 구체적인 차이를 처리하도록 나누는 방식입니다. 공통 인터페이스가 있다고 모든 엔진의 지원 언어·기능까지 같아지는 것은 아닙니다.

패턴 3. 제한된 동시 실행과 수명 관리

backend/services/model_manager.py의 _workers_for_free_vram()은 남은 GPU 메모리로 작업자 수를 정합니다. 작업당 예산은 5.0 GiB, 자동 상한은 4입니다. 코드의 단위 표기는 GB이지만, 바이트 수를 1,073,741,824로 나누므로 실제 계산 단위는 GiB입니다. 이 GPU 작업 풀은 CPU·MPS 환경에서 자동으로 작업자 한 개를 선택합니다.

# backend/services/model_manager.py의 실제 정책
_GPU_VRAM_PER_JOB_GB = 5.0
_GPU_WORKER_CAP = 4

def _workers_for_free_vram(free_gb: float) -> int:
    return max(1, min(_GPU_WORKER_CAP,
                      int(free_gb // _GPU_VRAM_PER_JOB_GB)))

# 예: 여유 8 GiB → 1개, 16 GiB → 3개
# 카드 전체 용량이 아니라 조회 당시의 여유 메모리 기준

run_on_gpu_pool_guarded()는 두 개의 시계를 씁니다. queue_timeout은 대기 제한 시간이고, timeout은 실제 작업을 시작한 뒤의 실행 제한 시간입니다. 앞 작업 때문에 기다리기만 한 요청을 “계산이 너무 느리다”라고 잘못 진단하지 않으려는 설계입니다.

상황구분소스의 처리 단서
아직 순서를 기다림자원 혼잡GpuPoolBusyError, 대기열에서 취소
실행 후 예산 초과계산 지연·정지 가능성GpuJobTimeoutError, 풀 복구 처리
클라이언트가 떠남응답 대기 종료와 작업 종료가 다름on_abandon으로 입력 자원의 뒤늦은 해제 연결

backend/api/routers/generation.py의 _TempReferenceLease는 이 차이를 파일 수명에 적용합니다. 요청이 취소되어도 백그라운드 스레드가 참고 녹음을 읽고 있다면 곧바로 지우지 않습니다. 마지막 독자가 끝난 뒤 삭제하도록 참조 횟수를 관리합니다.

요청이 끝났다는 사실과 계산·파일 사용이 끝났다는 사실은 같지 않으며, 이 차이를 관리해야 취소 기능도 안전해집니다. Python 스레드는 Future를 취소했다고 강제로 멈추지 않습니다. 강제 종료가 필요한 엔진에 별도 프로세스 방식이 있는 이유도 여기에서 이해할 수 있습니다.

패턴 4. 단계별 파이프라인과 유효성을 검사하는 캐시

backend/services/dub_pipeline.py의 ingest_pipeline()은 입력 확보, 오디오 추출, 음원 분리, 장면 처리의 진행 이벤트를 내보내는 비동기 생성기입니다. 이후 전사·번역·합성은 backend/api/routers/dub_*.py 등에서 이어집니다. 한 파일이 더빙 전체를 모두 처리한다고 생각하지 마세요.

영상 입력
  -> 오디오 추출
  -> 음성 / 배경음 분리
  -> 전사와 구간별 시간 정보
  -> 목표 언어로 번역
  -> 구간별 음성 합성
  -> 길이 맞추기와 배경음 혼합
  -> 최종 영상 또는 음성 출력

compute_file_hash()는 파일을 256 KiB씩 읽어 SHA-256을 계산합니다. find_cached_job()은 같은 내용의 과거 작업을 최대 5개 찾고, 중간 파일이 실제로 남았는지 확인합니다. 특히 audio_hq.wav가 없는 예전 결과는 재사용하지 않습니다.

이 검사는 과거의 저품질 오디오 추출 결과가 새 파이프라인에도 계속 섞이는 일을 막습니다. 캐시는 “같은 입력이 있었나”뿐 아니라 지금 요구하는 품질의 결과인가도 확인해야 합니다. get_job()은 메모리 조회가 실패하면 SQLite의 dub_history.job_data를 읽어 복구합니다.

실제 생성 요청의 순서를 따라 읽기

  1. generate_speech()가 폼 입력을 받고, Unicode NFC 정규화를 적용합니다.
  2. 요청의 engine 또는 현재 설정으로 엔진을 찾습니다. 모델 이용 조건 확인과 참고 음성·설계 기능 검증은 무거운 로딩 전에 수행합니다.
  3. 다른 상주 엔진을 정리하고, 필요한 모델과 참고 음성을 준비합니다.
  4. normalize_for_tts() 다음에 발음 사전을 적용하고, 긴 글은 나눠 합성합니다.
  5. 출력 경로에서 mark_synthetic_async()를 거쳐 WAV와 생성 이력을 저장합니다.

정규화 뒤에 발음 사전이 오는 것은 우연이 아닙니다. 사용자가 지정한 발음 표기를 후속 정규화가 다시 바꾸지 않도록 순서를 정했습니다. 이런 처리 순서의 계약도 함수 이름만큼 중요한 설계 정보입니다.

5디렉토리 구조 해부

제품 코드, 모델 코드, 검증 코드를 따로 찾는 지도
VoiceStudio/ ├── package.json 앱 버전·Bun 작업 공간·공통 명령 ├── pyproject.toml Python 의존성·패키징 ├── uv.lock / bun.lock Python·JavaScript 의존성 잠금 ├── config/ │ └── app.json 제품 식별자와 기본 포트 ├── backend/ │ ├── main.py 서버 구성·시작·상태 확인 │ ├── api/routers/ HTTP·WebSocket 기능 진입점 │ │ ├── generation.py 생성·오디오 제공 │ │ └── dub_*.py 더빙 입력·번역·생성·내보내기 │ ├── services/ 모델·미디어·작업 처리 │ │ ├── tts_backend.py TTS 인터페이스·어댑터·엔진 관리 │ │ ├── model_manager.py 모델 로딩·장치·작업 풀 │ │ ├── chunked_tts.py 긴 글 분할·음성 연결 │ │ └── dub_pipeline.py 더빙 준비·작업·캐시 │ ├── core/ 설정·DB·권한·공통 기반 │ ├── engines/ 별도 프로세스 엔진 등 │ ├── migrations/ Alembic 스키마 변경 │ └── mcp_server.py 에이전트용 음성 도구 ├── omnivoice/ │ ├── models/omnivoice.py 기본 모델·생성 알고리즘 │ ├── utils/ 오디오·텍스트·언어 매핑 │ ├── training/ 학습 구성·체크포인트 │ ├── eval/ 전사 오류·음질·화자 유사도 평가 │ └── cli/ 모델 추론·학습 명령 진입점 ├── electron/ │ ├── src/main/ 창·Python 런타임·OS 연동 │ ├── src/preload/ renderer와 main의 제한된 통로 │ ├── src/renderer/src/ React 화면·기능·API 클라이언트 │ └── src/shared/ 재사용 중인 공통 모듈 ├── native/desktop-bridge/ Rust 단축키·받아쓰기 출력 보조 ├── mobile/ iOS·Android·공통 계약 ├── tests/ 회귀·계약·프론트엔드 테스트 ├── deploy/ Docker 이미지·배포 구성 ├── scripts/ 설치·개발·검증 보조 도구 └── docs/ 설치·엔진·설계·운영 문서

backend/와 omnivoice/를 구분하면 읽을 양이 줄어듭니다. 전자는 “사용자 요청을 어떻게 처리할까”, 후자는 “음성을 어떻게 계산할까”에 가깝습니다. 웹 개발 입문자는 라우터부터, 머신러닝에 관심 있는 독자는 모델의 generate()부터 시작할 수 있습니다.

electron/src/shared/는 독립 실행되는 옛 앱의 입구가 아닙니다. 현재 renderer가 가져다 쓰는 공통 모듈도 이곳에 있습니다. 디렉토리 이름에 “shared”가 있다고 데스크톱·웹·모바일의 UI가 모두 같은 코드인 것도 아닙니다.

언어별 코드 규모

언어파일 수물리적 줄 수
Python1,131282,303줄
TypeScript·TSX922129,521줄
JavaScript·JSX650113,004줄
Swift515,746줄
Kotlin455,052줄
Rust104,220줄
CSS245,272줄

커밋 38740bf의 Git 추적 파일 중 위 확장자를 가진 일반 파일을 세었습니다. 주석·공백·테스트·생성 파일을 포함하며, 마지막 개행이 없어도 마지막 줄은 한 줄로 셉니다. JSON·문서·이미지·바이너리는 이 표에서 제외했습니다. 따라서 “직접 작성한 제품 코드의 순수 줄 수”와는 다릅니다.

위 언어 합계는 545,118줄입니다. 같은 집계에서 tests/만 134,565줄이며, 각 기능 폴더 안에도 테스트가 더 있습니다. 큰 규모를 처음부터 정독하기보다 기능 하나와 그 테스트를 짝지어 읽는 편이 낫습니다.

처음 읽을 때의 세 가지 경로

  1. 서버 개발 경로: backend/api/routers/generation.py → backend/services/tts_backend.py → backend/services/generation_audio.py
  2. 화면 개발 경로: electron/src/renderer/src/lib/api/generate.ts → electron/src/renderer/src/lib/api/client.ts → electron/src/main/backend.ts
  3. 성능·미디어 경로: backend/services/chunked_tts.py → backend/services/dub_pipeline.py → tests/test_perf_operation_budgets.py

6학습 포인트 (기술별)

각 기술에서 가져갈 개념과, 작은 실습으로 확인할 질문

Python·FastAPI: 입력 형식도 API의 일부다

generate_speech()는 JSON 본문 대신 Form과 UploadFile을 받습니다. 글과 참고 녹음을 한 요청으로 보내기 위한 선택입니다. max_chunk_chars의 하한은 0, crossfade_ms는 0~1000으로 선언됩니다.

# backend/api/routers/generation.py의 실제 입력 선언 일부
text: str = Form(...),
ref_audio: Optional[UploadFile] = File(None),
max_chunk_chars: int = Form(800, ge=0),
crossfade_ms: int = Form(50, ge=0, le=1000),
stream: bool = Form(False),

배울 것: 자료형 검증과 “이 엔진에서 이 기능을 쓸 수 있는가”라는 의미 검증은 다릅니다. 후자는 _conditioning_refusal() 같은 코드가 맡습니다. 실습 아이디어: 각 입력이 빠졌을 때, 범위를 벗어났을 때, 엔진 능력과 충돌할 때를 표로 나눠보세요.

비동기 처리: 응답 대기와 실제 계산을 나누기

함수에 async를 붙여도 모델 추론이 자동으로 가벼워지지 않습니다. run_on_gpu_pool_guarded()는 막히는 계산을 제한된 풀에 맡기고, WAV 저장은 asyncio.to_thread()로 이벤트 루프 밖에서 처리합니다.

배울 것: 이벤트 루프는 요청을 조율하는 역할을 유지해야 합니다. 실습 아이디어: 종이 위에서 작업자 1명, 요청 3개를 배치하고 각 요청의 도착·계산 시작·완료 시각을 기록해 대기 시간과 계산 시간을 따로 구하세요.

PyTorch·Transformers: 파일이 되기 전에는 텐서다

omnivoice/models/omnivoice.py의 OmniVoice는 PreTrainedModel을 상속합니다. from_pretrained()는 모델과 토크나이저 등 필요한 자산을 준비하고, generate()가 입력과 생성 설정을 받아 음성 생성 흐름을 시작합니다.

_generate_iterative()는 가려진 오디오 토큰을 단계적으로 채웁니다. 실제 반복문은 range(gen_config.num_step)이며, 매 단계 모델의 점수와 토큰 선택 정책을 사용합니다. 따라서 단계 수는 단순한 화면 장식이 아니라 계산량에 직접 연결되는 설정입니다.

OmniVoiceGenerationConfig의 기본 단계 수는 32이지만, 앱의 backend/services/performance_profiles.py는 빠름 8, 균형 16, 품질 32, 최대 64를 정의합니다. 라이브러리 기본값과 제품의 실행 기본값이 다른 좋은 사례입니다.

실습 아이디어: 함수 인자가 설정 객체를 덮어쓰는 경로를 표시해보세요. 모델을 로딩하지 않고도 “화면에서 고른 값이 최종 반복 횟수가 되는 과정”을 추적할 수 있습니다.

오디오 처리: 글자 수와 음성 길이는 다르다

backend/services/chunked_tts.py는 기본 800자 단위로 글을 나누고, 기본 50 ms 크로스페이드로 음성을 잇습니다. 한글·한자·가나가 전체의 30% 이상이면 _effective_max_chars()가 글자 한 개당 발화량을 고려해 한도를 줄입니다.

# 실제 분할 한도 정책 일부
_DENSE_FRACTION_THRESHOLD = 0.3
_DENSE_SPEECH_FACTOR = 2.5

# 조건을 만족하면 적용되는 계산
return max(120, min(max_chars,
                    round(max_chars / _DENSE_SPEECH_FACTOR)))

# 기본 800자라면 계산된 한도는 320자

배울 것: 입력의 길이 제한은 문자 수만의 문제가 아닙니다. 최종 음성의 길이와 모델의 안정적인 처리 범위를 고려해야 합니다. 실습 아이디어: 영어·한국어·숫자가 섞인 글을 만들어 분할 규칙의 예상 결과를 적어보세요.

오디오의 기본
샘플레이트와 텐서 모양
샘플레이트 24,000 Hz는 1초를 24,000개 값으로 나타낸다는 뜻입니다. 모노 음성의 모양이 (1, 48,000)이고 샘플레이트가 24,000이면 길이는 2초입니다. 같은 값들을 다른 샘플레이트로 재생하면 시간과 음높이도 달라집니다.

SQLite·저장: 음성과 검색 가능한 기록을 나누기

backend/core/db.py의 get_db()는 PRAGMA journal_mode=WAL과 PRAGMA foreign_keys=ON을 켭니다. db_conn()은 정상 종료 시 커밋, 예외 시 롤백, 마지막에 연결 닫기를 담당합니다. 오디오 자체는 파일로, 프로필과 이력은 테이블로 관리하는 경계도 볼 수 있습니다.

실습 아이디어: voice_profiles, generation_history, dub_history 세 테이블에서 파일 경로와 작업 ID가 어떤 관계인지 그려보세요. 파일 저장 성공과 DB 저장 성공 사이에 앱이 종료되면 어떤 흔적이 남을지도 생각해보세요.

TypeScript·Electron: 요청 실패를 화면 언어로 번역하기

electron/src/renderer/src/lib/api/client.ts의 ApiError는 HTTP 상태, 설명, 파싱한 응답을 함께 보관합니다. electron/src/renderer/src/lib/api/generate.ts의 sanitizeInstruct()는 허용된 지시 태그, 중복, 충돌, 지원하지 않는 문장을 구분합니다. 모든 실패를 “오류 발생” 하나로 뭉개지 않는 설계입니다.

실습 아이디어: 같은 분류의 태그 두 개와 알 수 없는 설명을 넣었을 때 각각 어느 배열에 들어갈지 추적해보세요. 검증 결과를 사용자 안내로 바꾸는 과정을 배울 수 있습니다.

MCP·테스트: 입구는 늘려도 핵심 로직은 공유하기

backend/mcp_server.py의 create_mcp_server(app=None)는 앱이 주어지고 환경변수 OMNIVOICE_API_URL이 비어 있으면 httpx.ASGITransport로 같은 API를 프로세스 안에서 호출합니다. 독립 실행하거나 이 환경변수를 지정하면 HTTP로 백엔드와 통신합니다. 도구마다 별도의 합성 구현을 만드는 대신 기존 동작을 재사용합니다.

tests/test_perf_operation_budgets.py는 실행 시간만 재지 않고 합성·배치 호출 횟수 같은 연산 예산을 확인합니다. 테스트 장비마다 속도는 달라도, 불필요한 모델 호출이 추가되었는지는 일관되게 검사할 수 있습니다.

7하드웨어 / 시스템 요구사항

앱 실행 조건, 엔진 조건, 학습용 추천을 구분하기

저장소는 모든 엔진에 통하는 단일 RAM 최소치를 제시하지 않습니다. 아래 OS·디스크 조건은 설치 문서 기준이고, 메모리 여유에 관한 권장은 학습을 위한 선택 기준입니다. 설치가 가능하다는 것과 원하는 엔진을 충분한 속도로 돌릴 수 있다는 것은 서로 다른 조건입니다.

환경문서·소스에서 확인한 조건학습할 때의 판단
Apple SiliconmacOS 13.3+, MPS 가속; 설치 문서상 약 10 GB 여유 디스크통합 메모리를 다른 앱과 공유합니다. 무거운 전사·합성을 동시에 잡지 마세요.
Intel Mac현재 문서는 앱 UI + 원격 백엔드만 지원로컬 Python 백엔드 설치를 기본 실습으로 잡지 않습니다.
Windows x64Windows 10/11, 약 10 GB 여유 디스크; NVIDIA CUDA 경로CPU 엔진으로도 시작할 수 있습니다. GPU 이름보다 실제 실행 장치를 확인하세요.
Linux 데스크톱x86_64, X11 또는 Wayland; 약 10 GB 여유 디스크NVIDIA CUDA, 조건에 맞는 AMD ROCm을 검토합니다.
GPU 없는 PCCPU 실행 가능; CPU PyTorch 환경은 문서상 약 5 GiB 여유 공간 안내가벼운 엔진과 짧은 글부터 시작합니다. 모델·출력 공간은 별도입니다.
Windows ARM실험적 지원. 네이티브 셸 + x64 Python 에뮬레이션, CPU 전용 안내실기기 검증이 끝난 일반 지원 환경으로 취급하지 않습니다.

근거는 docs/install/macos.md, docs/install/windows.md, docs/install/linux.md입니다. 약 10 GB는 여러 엔진과 장시간 영상까지 포함한 상한이 아닙니다. 별도 엔진의 가상환경과 가중치가 수 GB씩 추가될 수 있습니다.

RAM과 VRAM을 같은 숫자로 보지 말자

하드웨어 용어
RAM, VRAM, 통합 메모리
RAM은 시스템 메모리, VRAM은 보통 그래픽카드의 전용 메모리입니다. Apple Silicon은 CPU·GPU가 통합 메모리를 공유합니다. GPU 카드의 8 GB와 통합 메모리 8 GB는 모델을 올리는 조건이 같지 않습니다.

OmniVoiceBackend.min_vram_gb는 6.0으로 선언됩니다. 이는 기본 엔진의 전용 GPU 메모리 판단에 쓰이는 기준이며, 모든 엔진의 최소 조건이나 “6 GB만 있으면 항상 안전하다”는 보장은 아닙니다. 자동 작업 수 정책의 5 GiB/작업과도 목적이 다릅니다.

학습용 권장: 가벼운 CPU 엔진으로 짧은 문장을 다루거나, 여유 있는 16 GB 이상 시스템에서 한 번에 한 작업부터 시작하세요. 이는 공식 최소 사양이 아닙니다. 더빙은 전사·분리·합성 모델과 중간 오디오가 필요하므로 메모리와 디스크를 더 넉넉히 잡는 편이 좋습니다.

Windows에서 AMD·Intel GPU가 감지되어도 기본 PyTorch 경로가 그 GPU를 쓰는 것은 아닙니다. 설치 문서는 Windows의 기본 PyTorch 가속을 NVIDIA/CUDA로 안내하며, audio.cpp의 Vulkan 경로와 별도 고급 설정을 구분합니다. get_best_device()에 여러 장치 분기가 있다는 사실을 모든 설치의 지원 보장으로 읽지 마세요.

패키지 사용자와 소스 개발자의 준비물은 다르다

성능을 읽는 비유

냉장고에서 재료를 꺼내는 시간과 요리하는 시간은 다릅니다. 첫 생성에는 모델 로딩과 가속 커널 준비가 섞일 수 있습니다. 첫 실행만 재서 모든 문장의 합성 속도라고 부르면 비교가 어긋납니다.

8직접 해볼 수 있는 실습 과제

모델 설치 없이 시작해, 작은 독립 코드와 설계 문서로 확장하기

처음에는 실제 녹음이나 GPU를 준비하지 않아도 됩니다. 아래 과제는 소스 읽기와 독립된 연습 코드로 진행할 수 있습니다. 좋은 실습의 목표는 기능을 크게 늘리는 것이 아니라, 하나의 계약을 설명하고 실패 조건까지 확인하는 것입니다.

과제 1 · 입문

생성 API의 요청 계약을 한 장으로 만들기 예상 30~45분

출발 파일: backend/api/routers/generation.py의 generate_speech().

할 일: text, engine, ref_audio, max_chunk_chars, crossfade_ms, stream을 골라 필수 여부·자료형·기본값·검증 조건을 표로 정리합니다.

완료 기준: “텍스트 누락”, “알 수 없는 엔진”, “음성 설계를 지원하지 않는 엔진”이 각각 어느 단계에서 거절되는지 설명합니다. 네트워크 요청을 보내지 않고 라우터와 검증 함수를 따라가면 됩니다.

과제 2 · 입문

문장 분할기를 작은 순수 함수로 만들기 예상 1시간

출발 파일: backend/services/chunked_tts.py의 split_text_into_chunks()와 _effective_max_chars().

할 일: 독립된 연습 파일에 마침표·물음표·느낌표를 기준으로 나누는 함수를 작성합니다. 먼저 단순 버전을 만든 뒤 약어, 한국어 문장, [pause 300ms] 같은 태그를 반례로 추가합니다.

완료 기준: 빈 문자열과 한 문장은 불필요하게 쪼개지지 않고, 태그 내부는 나뉘지 않아야 합니다. 영문 800자와 한국어 800자가 왜 같은 발화 길이가 아닌지도 설명합니다.

과제 3 · 중급

실제 모델 대신 가짜 엔진으로 어댑터 연습하기 예상 1~2시간

출발 파일: backend/services/tts_backend.py의 TTSBackend, generate_batch().

할 일: 표준 라이브러리만으로 작은 공통 인터페이스와 두 구현을 만듭니다. 하나는 길이 100의 0 목록, 다른 하나는 길이 200의 0 목록을 반환하게 합니다. 이는 계약을 익히기 위한 축소 모형이며 실제 앱의 텐서 계약과는 구분합니다.

# 독립 연습 코드의 골격: 실제 앱에 붙이는 엔진이 아님
class SilentEngine:
    sample_rate = 100

    def generate(self, text):
        return [0.0] * len(text)

engine = SilentEngine()
samples = engine.generate("가나다라")
duration = len(samples) / engine.sample_rate
assert duration == 0.04

완료 기준: 호출하는 쪽은 엔진 이름을 몰라도 길이를 계산할 수 있어야 합니다. 샘플레이트가 0이거나 결과가 비었을 때 누가 오류를 내야 하는지도 계약에 적습니다.

과제 4 · 중급

대기 시간·실행 시간·취소를 시퀀스 표로 검증하기 예상 1~2시간

출발 파일: backend/services/model_manager.py의 run_on_gpu_pool_guarded(), backend/api/routers/generation.py의 _TempReferenceLease.

할 일: 작업자 한 개에 A·B 두 요청이 들어오는 시간표를 그립니다. B가 대기 중 취소되는 경우와, 계산 시작 후 응답 대기가 끝나는 경우를 각각 표시합니다.

완료 기준: 참고 파일을 지울 수 있는 가장 이른 시각을 두 경우에 대해 답합니다. 지연·파일 읽기를 흉내 낸 독립 코드로 확장할 때에도 “취소 = 스레드 종료”라고 가정하면 안 됩니다.

과제 5 · 고급

더빙 캐시의 무효화 규칙과 재개 계획 설계하기 예상 반나절

출발 파일: backend/services/dub_pipeline.py의 find_cached_job(), backend/services/longform_resume.py.

할 일: 입력 내용, 엔진 ID, 모델 식별자, 목표 언어, 분할 정책 버전을 캐시 키에 넣는 설계안을 작성합니다. 긴 글 재개용 resume.json에서 계획을 보존하는 방식도 비교합니다.

완료 기준: “엔진만 바뀜”, “중간 파일 삭제”, “합성 도중 종료” 세 경우에 재사용 가능한 단계와 다시 해야 하는 단계를 표로 제시합니다. 설계안의 키 구성이 현재 더빙 캐시와 똑같이 구현되어 있다고 주장하지 않습니다.

실습을 확장할 때는 tests/test_perf_operation_budgets.py의 test_dub_remix_budget_zero_tts_calls()를 읽어보세요. 이미 생성된 음성을 다시 섞는 작업에서 TTS 호출이 0회여야 한다는 식의 검증 가능한 목표를 세우는 방법을 배울 수 있습니다.

9관련 기술 심화 학습 로드맵

6주 동안 기능 하나를 끝까지 따라가며 기반을 쌓기

주당 3~5시간을 기준으로 잡은 학습 제안입니다. Python의 함수·클래스, JavaScript의 객체·비동기 문법이 낯설다면 1주차를 더 길게 잡으세요. 한 기능의 입력부터 저장까지 설명할 수 있게 된 뒤 다른 엔진과 화면으로 범위를 넓히면, 큰 저장소도 읽을 수 있는 크기로 줄어듭니다.

주차배울 주제·읽을 곳남길 결과물
1주차Python 자료형·함수·예외, HTTP·폼 입력. backend/api/routers/generation.py의 선언과 검증을 읽습니다.생성 요청 계약 표, 오류 사례 5개
2주차추상 클래스·어댑터·레지스트리. TTSBackend와 두 엔진 구현을 비교합니다.가짜 엔진 2개와 공통 호출 코드
3주차asyncio·스레드·취소·자원 수명. run_on_gpu_pool_guarded()와 lease를 읽습니다.대기·실행·취소 시퀀스 표
4주차샘플레이트·텐서·WAV·크로스페이드. backend/services/chunked_tts.py, backend/services/generation_audio.py를 읽습니다.분할기 반례 모음, 음성 길이 계산기
5주차React 상태·HTTP 오류·Electron IPC. electron/src/renderer/src/lib/api/client.ts, electron/src/preload/index.ts를 연결합니다.오류 상태별 UI 설계, 요청 경로 도식
6주차캐시·SQLite·재개·CI 계약. backend/services/dub_pipeline.py, backend/services/longform_resume.py, CI 설정을 읽습니다.캐시 키 설계와 회귀 테스트 계획

그 다음은 관심 분야별로 갈라진다

학습 진도 확인
함수 이름보다 책임과 불변 조건을 설명해보자

“이 함수는 어떤 입력을 믿는가”, “누가 자원을 해제하는가”, “어떤 결과를 재사용해도 되는가”에 답할 수 있으면 구조를 이해한 것입니다. 추론 수식 전체를 먼저 이해해야만 앱 코드를 읽을 수 있는 것은 아닙니다.

10핵심 키워드 사전

소스에서 자주 만나는 단어를 쉬운 말로 다시 붙이기
용어쉬운 설명
TTS글을 음성으로 바꾸기. 이 프로젝트에서는 여러 엔진을 공통 인터페이스로 호출합니다.
ASR소리를 글로 바꾸기. 더빙의 출발점이며 참고 녹음의 전사에도 쓰입니다.
음성 복제참고 녹음의 목소리 특성을 조건으로 새 글을 읽게 하는 일입니다.
제로샷대상 목소리를 위해 별도 재학습하지 않고 참고 자료로 생성하는 방식입니다.
음성 설계참고 녹음 대신 성별·음높이·스타일 같은 설명으로 목소리를 지정하는 기능입니다.
텐서숫자를 여러 축으로 담은 배열입니다. 오디오는 채널과 샘플 축으로 표현할 수 있습니다.
오디오 토큰음성을 모델이 다룰 수 있는 이산 기호로 나타낸 값입니다. 텍스트의 글자와 같은 단위는 아닙니다.
샘플레이트1초에 들어가는 오디오 값의 수입니다. 배열 길이를 시간으로 바꾸는 데 필요합니다.
크로스페이드앞 소리를 줄이며 뒤 소리를 키워 짧게 겹치는 연결 방식입니다.
화자 분리녹음의 어느 구간에서 누가 말했는지 구분하는 작업입니다. 음성·배경음 분리와 다릅니다.
강제 정렬전사한 단어가 음성의 어느 시간에 놓이는지 맞추는 작업입니다.
어댑터서로 다른 엔진 호출을 같은 모양으로 바꾸는 연결 코드입니다.
레지스트리엔진 ID와 구현을 연결한 명부입니다. 지연 방식이면 필요할 때 구현을 가져옵니다.
지연 로딩앱 시작 때가 아니라 실제로 필요해질 때 모델이나 모듈을 읽는 방식입니다.
작업 풀정해진 수의 작업자가 요청을 나눠 처리하는 구조입니다. 무제한 동시 실행을 막습니다.
lease자원을 쓰는 동안 유효함을 보장하는 사용권입니다. 마지막 사용자가 끝날 때 해제할 수 있습니다.
sidecar주 프로그램 옆에서 별도 프로세스로 돌아가는 보조 실행체입니다.
IPC·preloadIPC는 프로세스 사이 통신입니다. Electron preload는 화면에 제한된 시스템 기능 통로를 제공합니다.
SSE·WebSocketSSE는 서버가 이벤트를 이어 보내는 방식, WebSocket은 연결을 유지하며 양방향으로 주고받는 방식입니다.
NDJSON한 줄에 JSON 하나를 보내는 형식입니다. 생성 미리 듣기 이벤트를 순서대로 읽을 수 있습니다.
WALSQLite가 변경 내용을 로그에 먼저 기록하는 모드입니다. 읽기·쓰기 동시 처리에 도움을 줍니다.
캐시 무효화예전에 만든 결과를 더 이상 재사용하면 안 되는 조건을 정하는 일입니다.
MCPAI 도구와 자료를 공통 방식으로 노출하는 프로토콜입니다. 음성 생성도 도구 호출로 연결합니다.
워터마크합성 여부를 식별하도록 음성에 넣는 표식입니다. 구현이 있다는 사실과 항상 삽입된다는 보장은 다릅니다.

11참고 링크

현재 상태와 한계를 확인한 뒤, 필요한 소스로 돌아가기

현재 상태 (2026-10-23 확인)

항목상태·확인 기준
저장소debpalash/VoiceStudio
선정 근거TrendShift Weekly #18 · 2026-10-23 분석
스타 / 포크82,109 / 9,065 · 2026-10-23 GitHub 기준
생성일 / 최근 push2026-04-09 / 2026-10-23 · GitHub 기준
주 언어Python · GitHub 기준
최신 안정 릴리스v0.5.6 · 2026-09-23
분석 커밋38740bf · 2026-10-23
test(ios): expect the release triple as the bundle marketing version (#3203)
개발 코드 버전0.5.7-beta.1 · 루트 package.json, pyproject.toml
코드 규모주요 7개 언어 묶음 545,118줄. 테스트·생성 파일·주석·공백 포함. Git 추적 항목 총 3,615개.
라이선스앱은 AGPL-3.0-only. LICENSE-NOTICE.md는 omnivoice/의 별도 Apache-2.0 출처와 모델 자산의 별도 조건을 구분합니다.
현재 UIElectron 데스크톱·React 웹. Tauri는 종료된 배포 계열입니다.
쓰기 전에 알아둘 한계
로컬 실행·지원 언어·이용 권리를 따로 확인하세요

646개는 언어 매핑의 수: omnivoice/utils/lang_map.py의 LANG_NAME_TO_ID에 646개 항목이 있습니다. 모든 TTS·ASR·번역 엔진이 646개 언어에서 같은 품질을 낸다는 뜻은 아닙니다.

로컬 기능과 선택적 외부 연결: 다운로드·업데이트·외부 공급자·동기화·분석 설정을 구분해야 합니다. 사용할 경로와 설정을 확인하세요. 로컬 작업에 외부 계정이 일괄 필수인 구조는 아닙니다.

앱과 모델은 조건이 다름: 저장소의 LICENSE-NOTICE.md는 기본 모델 가중치의 CC-BY-NC 표기와 오디오 토크나이저의 별도 조건을 명시합니다. 앱 라이선스나 앱의 상용 라이선스만으로 모든 모델·목소리의 이용 조건을 대신할 수 없습니다. 목소리 복제에는 당사자의 허락을 확인하세요.

워터마크는 설정과 성공 여부에 좌우됨: mark_synthetic()는 표식 기능이 꺼졌거나 사용할 수 없거나 실패하면 원본 음성을 반환하도록 설계되어 있습니다. 함수 호출 경로가 있다는 이유로 모든 출력에 표식이 보장된다고 설명하면 안 됩니다.

모바일 설명의 차이: AGENTS.md는 휴대전화 자체 추론을 지원한다고 서술하지만, docs/mobile.md와 mobile/contract/SPEC.md는 보조 앱 단계로 설명합니다. 문서 간 설명이 일치하지 않습니다. iOS의 APIClient.generate()는 페어링된 서버의 /generate에 HTTP 요청을 보냅니다.

정적 구조와 실측은 다름: 코드 구조 설명은 실제 음질·처리 속도·모든 장치의 동작 검증을 대신하지 않습니다. 개발 커밋과 안정 릴리스 사이에도 기능·화면·설치 동작 차이가 있을 수 있습니다.

공식 저장소·문서

본문과 직접 연결되는 소스

관련 기술의 공식 학습 자료