FastAPI 기반의 음성 처리 서버입니다. 업로드한 음성을 STT로 텍스트화하고, 목표 언어로 번역한 뒤, 사용자 음성으로 TTS 결과를 생성합니다. 현재 구현은 endpoint에서 직접 비즈니스 로직을 처리하지 않고 endpoint -> runner -> LangGraph -> node -> service 계층으로 분리되어 있습니다.
Stateapp/graphs/state.py workflow 전체에서 공유하는 상태 필드 정의Nodeapp/graphs/nodes.py STT, 번역, 역번역, voice 조회/생성, TTS 같은 실제 단계 함수Edgeapp/graphs/voice_process_graph.py/processgraph의 node 연결과 흐름 정의Edgeapp/graphs/text_translate_graph.py/text/translategraph의 node 연결과 흐름 정의Conditional Routingapp/graphs/routing.py 조건 분기 시 다음 edge가 어디로 갈지 결정하는 함수Runnerapp/graphs/runner.py endpoint가 graph를 직접 실행하지 않고 공통 진입점으로 감싸는 계층
POST /api/v1/voice/process음성 업로드 후 STT -> 번역 -> 역번역 -> voice clone/reuse -> TTS -> 결과 저장POST /api/v1/voice/text등록된 voice_id를 사용해 입력 텍스트를 바로 음성으로 변환POST /api/v1/voice/text/translate입력 텍스트를 번역한 뒤 등록된 voice_id로 음성 생성- JWT Bearer 토큰 기반 사용자 인증
- LangGraph 기반 workflow orchestration
/process요청 fingerprint 캐시 재사용- 결과 메타데이터 및 오디오 파일 저장
- Python 3.12+
- FastAPI
- Uvicorn
- Poetry
- LangGraph
- OpenAI API
- ElevenLabs API
- SQLite
Client
-> FastAPI Endpoint
-> Runner
-> LangGraph
-> Graph Nodes
-> Services
-> OpenAI / ElevenLabs / SQLite / File System
역할 구분은 다음과 같습니다.
endpointHTTP 요청 파싱, JWT 인증, 초기 state 구성, HTTP 응답 변환runnergraph lazy loading, workflow 실행, 공통 예외 처리, cleanup 보조graph노드 간 상태 전이와 분기 정의serviceOpenAI, ElevenLabs, SQLite, 파일 시스템 같은 실제 작업 수행
GET /health
응답 예시:
{
"status": "ok",
"service": "FastAPI AI Server"
}POST /api/v1/voice/processAuthorization: Bearer <JWT>Content-Type: multipart/form-data
폼 필드:
target_lang(string): 번역 대상 언어domain(string, optional): 번역 도메인 힌트glossary_json(string, optional): 용어집 JSON 배열 문자열audio(file): 입력 음성 파일
처리 흐름:
- 업로드 파일을
uploads/에 임시 저장합니다. - 요청 내용으로 fingerprint를 계산해 이전 처리 결과가 있으면 재사용합니다.
- 필요한 경우 WebM을 WAV로 변환합니다.
- STT로 원문 텍스트와 감지 언어를 추출합니다.
- 목표 언어로 번역하고 역번역으로 품질 확인용 텍스트를 만듭니다.
- 사용자 voice_id를 조회하고, 없으면 음성 클로닝을 수행합니다.
- TTS 결과를 생성하고 결과 리소스를 저장합니다.
- JSON 메타데이터를 반환합니다.
응답 예시:
{
"requestId": "12345678",
"voiceId": "voice-abc",
"detectedSourceLanguage": "ko",
"sourceText": "안녕하세요",
"translatedText": "Hello",
"backTranslatedText": "안녕하세요",
"audio": {
"url": "/internal/results/12345678/audio",
"mimeType": "audio/mpeg"
}
}특징:
- 동일한 사용자와 동일한 입력 오디오/옵션 조합이면 캐시된 결과를 재사용할 수 있습니다.
- stale
voice_id가 감지되면/process경로에서는 voice를 재생성한 뒤 TTS를 재시도할 수 있습니다.
GET /internal/results/{requestId}/audioAuthorization: Bearer <JWT>
동작:
- 저장된 결과 메타데이터를 조회합니다.
- 요청 사용자와 결과 생성 사용자가 같은지 검사합니다.
- 권한이 맞으면 저장된
audio.mp3를 반환합니다.
POST /api/v1/voice/textAuthorization: Bearer <JWT>Content-Type: application/json
요청 바디:
{
"text": "원하는 문장을 입력하세요."
}동작:
- JWT의 사용자 ID로 저장된
voice_id를 조회합니다. - voice가 있으면 ElevenLabs TTS를 생성하고
audio/mpeg응답으로 반환합니다. - voice가 없거나 stale voice면
409를 반환하고/process재등록을 유도합니다.
응답 헤더:
X-Voice-ID
POST /api/v1/voice/text/translateAuthorization: Bearer <JWT>Content-Type: application/json
요청 바디 예시:
{
"text": "안녕하세요",
"target_lang": "English",
"domain": "casual conversation",
"glossary": [
{
"source": "보이스 트윈",
"target": "VoiceTwin"
}
]
}동작:
- 기존
voice_id가 있는지 확인합니다. - 입력 텍스트를 번역하고 역번역을 수행합니다.
- 번역 결과를 사용자의 voice로 TTS 생성합니다.
- 결과를
audio/mpeg로 바로 반환합니다.
특징:
- 이 경로는 입력 음성이 없기 때문에 stale
voice_id가 감지되면 voice를 재생성하지 않습니다. - stale voice면 저장된
voice_id를 지우고409응답으로 다시/process등록을 유도합니다.
응답 헤더:
X-Voice-ID
initialize
-> detect audio format
-> convert if needed
-> transcribe
-> translate
-> back translate
-> resolve voice
-> clone if needed
-> synthesize speech
-> recreate stale voice if needed
-> finalize
-> cleanup
initialize
-> require existing voice
-> translate
-> back translate
-> synthesize speech
-> clear stale voice or finalize
- 업로드 임시 파일:
uploads/ - voice 매핑 DB:
data/voice_map.db - 처리 결과 디렉터리:
internal_results/<request_id>/ - 저장 오디오:
internal_results/<request_id>/audio.mp3 - 저장 메타데이터:
internal_results/<request_id>/metadata.json /process캐시 인덱스 DB:internal_results/process_cache.db
process_cache.db는 fingerprint와 request_id를 연결해서 동일 요청 재실행을 줄이는 용도입니다.
app/
main.py
api/v1/endpoints/voice_translation.py
core/
config.py
security.py
graphs/
runner.py
voice_process_graph.py
text_translate_graph.py
nodes.py
routing.py
state.py
errors.py
services/
audio_service.py
stt_service.py
translation_service.py
voice_service.py
voice_store.py
result_service.py
tests/
api/
test_voice_process_endpoint.py
core/
test_security_and_app.py
graphs/
test_graph_nodes.py
services/
test_stt_service.py
poetry install.env 파일에 아래 값을 설정하세요.
OPENAI_API_KEY=your_openai_api_key
ELEVENLABS_API_KEY=your_elevenlabs_api_key
JWT_SECRET_KEY=your_jwt_secret_key
JWT_ALGORITHMS=HS256,HS512
JWT_SECRET_IS_BASE64=false
RESULTS_DIR=internal_results
VOICE_DB_PATH=data/voice_map.dbpoetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload- JWT secret은 토큰 발급 서버와 동일해야 합니다.
- CORS 허용 origin은 현재
http://localhost:5173,https://voicetwin-front.vercel.app입니다. uploads/,internal_results/,data/디렉터리는 서버 시작 시 자동 생성됩니다.uploads/는 임시 업로드 작업용이며 외부 정적 경로로 공개하지 않습니다.- legacy
uploads/user_voice_map.json이 있으면 SQLite voice store로 마이그레이션됩니다. - 이 저장소에서는 문서 원본
.md는 추적하고, 산출 이미지 같은 문서 부속 파일은 기본적으로 Git에서 제외하도록 설정되어 있습니다.