Skip to content

Commit 57d2ec3

Browse files
committed
[fix] tts session 기반 api 수정
1 parent 769a091 commit 57d2ec3

File tree

1 file changed

+151
-7
lines changed

1 file changed

+151
-7
lines changed

voice/eleven_labs/app/main.py

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,18 @@
4242

4343
# 오디오 저장 폴더
4444
OUTPUT_DIR = "generated_audio"
45-
SHARED_DIR = "shared/tts" # 다른 서비스와 공유되는 폴더
45+
SHARED_DIR = "shared" # 세션 기반 공유 폴더 (상위)
46+
SHARED_TTS_DIR = "shared/tts" # 일반 TTS 공유 폴더
4647
os.makedirs(OUTPUT_DIR, exist_ok=True)
4748
os.makedirs(SHARED_DIR, exist_ok=True)
49+
os.makedirs(SHARED_TTS_DIR, exist_ok=True)
50+
51+
52+
def get_session_dir(session_id: str) -> str:
53+
"""세션 디렉토리 경로 반환 (없으면 생성)"""
54+
session_dir = os.path.join(SHARED_DIR, session_id)
55+
os.makedirs(session_dir, exist_ok=True)
56+
return session_dir
4857

4958
# 파일 자동 삭제 설정 (시간 단위)
5059
FILE_MAX_AGE_HOURS = 3
@@ -72,7 +81,7 @@ async def periodic_cleanup():
7281
while True:
7382
await asyncio.sleep(1800) # 30분
7483
cleanup_old_files(OUTPUT_DIR)
75-
cleanup_old_files(SHARED_DIR)
84+
cleanup_old_files(SHARED_TTS_DIR)
7685

7786

7887
# Static 파일 서빙
@@ -85,12 +94,13 @@ async def startup_event():
8594
"""서버 시작 시 초기화"""
8695
# 시작 시 오래된 파일 정리
8796
cleanup_old_files(OUTPUT_DIR)
88-
cleanup_old_files(SHARED_DIR)
97+
cleanup_old_files(SHARED_TTS_DIR)
8998

9099
# 백그라운드 정리 태스크 시작
91100
asyncio.create_task(periodic_cleanup())
92101
print(f"[Cleanup] 자동 파일 정리 활성화 ({FILE_MAX_AGE_HOURS}시간 이상 파일 삭제)")
93-
print(f"[Shared] TTS 파일이 {SHARED_DIR}에도 저장됩니다")
102+
print(f"[Shared] 일반 TTS: {SHARED_TTS_DIR}")
103+
print(f"[Shared] 세션 기반 TTS: {SHARED_DIR}/{{session_id}}/")
94104

95105

96106
class TTSRequest(BaseModel):
@@ -110,10 +120,43 @@ class TTSResponse(BaseModel):
110120
shared_path: str # 다른 서비스에서 접근 가능한 경로
111121

112122

123+
class SessionTTSRequest(BaseModel):
124+
"""세션 기반 TTS 요청"""
125+
session_id: str # 세션 ID (필수)
126+
text: str # 생성할 텍스트 (필수)
127+
output_filename: Optional[str] = "tts_audio.mp3" # 저장할 파일명
128+
voice_id: Optional[str] = None
129+
model_id: Optional[str] = None
130+
stability: Optional[float] = 0.8
131+
similarity_boost: Optional[float] = 0.8
132+
style: Optional[float] = 0.4
133+
use_speaker_boost: Optional[bool] = True
134+
135+
136+
class SessionTTSResponse(BaseModel):
137+
"""세션 기반 TTS 응답"""
138+
success: bool
139+
session_id: str
140+
filename: str
141+
session_path: str # 세션 폴더 내 경로
142+
message: str
143+
144+
113145
@app.get("/")
114146
async def root():
115-
"""메인 페이지 - static/index.html 반환"""
116-
return FileResponse("static/index.html")
147+
"""API 루트"""
148+
return {
149+
"message": "ElevenLabs TTS API",
150+
"version": "1.0.0",
151+
"endpoints": {
152+
"POST /generate": "TTS 생성 (일반)",
153+
"POST /session/generate": "TTS 생성 (세션 기반)",
154+
"GET /audio/{filename}": "오디오 파일 다운로드",
155+
"GET /session/{session_id}/files": "세션 내 파일 목록",
156+
"GET /session/{session_id}/audio/{filename}": "세션 내 오디오 다운로드",
157+
"GET /health": "헬스 체크"
158+
}
159+
}
117160

118161

119162
@app.get("/health")
@@ -157,7 +200,7 @@ async def generate_tts(request: TTSRequest):
157200
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
158201
filename = f"tts_{timestamp}.mp3"
159202
filepath = os.path.join(OUTPUT_DIR, filename)
160-
shared_filepath = os.path.join(SHARED_DIR, filename)
203+
shared_filepath = os.path.join(SHARED_TTS_DIR, filename)
161204

162205
# 오디오 데이터를 메모리에 먼저 저장
163206
audio_data = b""
@@ -233,6 +276,107 @@ async def serve_audio(filename: str):
233276
raise HTTPException(status_code=404, detail="File not found")
234277

235278

279+
# ============================================
280+
# 세션 기반 엔드포인트
281+
# ============================================
282+
@app.post("/session/generate", response_model=SessionTTSResponse, tags=["Session"])
283+
async def session_generate_tts(request: SessionTTSRequest):
284+
"""
285+
세션 기반 TTS 생성
286+
287+
결과 오디오가 세션 폴더에 저장됩니다.
288+
다른 서비스(z_image, i2v 등)에서 동일한 session_id로 접근 가능합니다.
289+
"""
290+
try:
291+
if not request.text:
292+
raise HTTPException(status_code=400, detail="텍스트가 비어있습니다")
293+
294+
session_dir = get_session_dir(request.session_id)
295+
296+
voice_id = request.voice_id or VOICE_ID
297+
model_id = request.model_id or MODEL_ID
298+
299+
# ElevenLabs 클라이언트
300+
client = ElevenLabs(api_key=API_KEY)
301+
302+
# Voice Settings
303+
voice_settings = VoiceSettings(
304+
stability=request.stability,
305+
similarity_boost=request.similarity_boost,
306+
style=request.style,
307+
use_speaker_boost=request.use_speaker_boost
308+
)
309+
310+
# TTS 생성
311+
audio_stream = client.text_to_speech.convert(
312+
text=request.text,
313+
voice_id=voice_id,
314+
model_id=model_id,
315+
voice_settings=voice_settings,
316+
)
317+
318+
# 오디오 데이터를 메모리에 먼저 저장
319+
audio_data = b""
320+
for chunk in audio_stream:
321+
audio_data += chunk
322+
323+
# 파일명 설정
324+
output_filename = request.output_filename or "tts_audio.mp3"
325+
if not output_filename.endswith(".mp3"):
326+
output_filename += ".mp3"
327+
328+
# 세션 폴더에 저장
329+
session_filepath = os.path.join(session_dir, output_filename)
330+
with open(session_filepath, "wb") as f:
331+
f.write(audio_data)
332+
333+
return SessionTTSResponse(
334+
success=True,
335+
session_id=request.session_id,
336+
filename=output_filename,
337+
session_path=f"/app/shared/{request.session_id}/{output_filename}",
338+
message=f"세션 '{request.session_id}'에 TTS 저장 완료"
339+
)
340+
341+
except HTTPException:
342+
raise
343+
except Exception as e:
344+
raise HTTPException(status_code=500, detail=str(e))
345+
346+
347+
@app.get("/session/{session_id}/files", tags=["Session"])
348+
async def list_session_files(session_id: str):
349+
"""세션 폴더 내 파일 목록 조회"""
350+
session_dir = os.path.join(SHARED_DIR, session_id)
351+
352+
if not os.path.exists(session_dir):
353+
return {"session_id": session_id, "files": [], "count": 0, "exists": False}
354+
355+
files = []
356+
for f in Path(session_dir).glob("*"):
357+
if f.is_file():
358+
files.append({
359+
"filename": f.name,
360+
"size_mb": round(f.stat().st_size / (1024 * 1024), 2),
361+
"created": datetime.fromtimestamp(f.stat().st_ctime).isoformat()
362+
})
363+
files.sort(key=lambda x: x["created"], reverse=True)
364+
365+
return {"session_id": session_id, "files": files, "count": len(files), "exists": True}
366+
367+
368+
@app.get("/session/{session_id}/audio/{filename}", tags=["Session"])
369+
async def get_session_audio(session_id: str, filename: str):
370+
"""세션 폴더 내 오디오 파일 다운로드"""
371+
session_dir = os.path.join(SHARED_DIR, session_id)
372+
filepath = os.path.join(session_dir, filename)
373+
374+
if not os.path.exists(filepath):
375+
raise HTTPException(status_code=404, detail="파일을 찾을 수 없습니다")
376+
377+
return FileResponse(filepath, media_type="audio/mpeg", filename=filename)
378+
379+
236380
if __name__ == "__main__":
237381
import uvicorn
238382
print("=" * 60)

0 commit comments

Comments
 (0)