drrobot9 commited on
Commit
377c6b8
·
verified ·
1 Parent(s): 86ae64e

Initial commit

Browse files
Files changed (4) hide show
  1. Dockerfile +15 -0
  2. app/config.json +12 -0
  3. app/main.py +130 -0
  4. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+
6
+ WORKDIR /app
7
+
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ COPY app ./app
12
+
13
+ EXPOSE 7860
14
+
15
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "elevenlabs": {
3
+ "api_key": "sk_77685e3c0388b4c682b56861c13cb2b3c4017d5bc8edbc15",
4
+ "voice_id": "pqHfZKP75CvOlQylNhV4",
5
+ "model_id": "eleven_multilingual_v2",
6
+ "output_format": "mp3_44100_128"
7
+ },
8
+ "service": {
9
+ "host": "0.0.0.0",
10
+ "port": 7860
11
+ }
12
+ }
app/main.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/main.py
2
+
3
+ import json
4
+ import httpx
5
+ from fastapi import FastAPI, HTTPException
6
+ from fastapi.responses import StreamingResponse, Response
7
+ from pydantic import BaseModel, Field
8
+
9
+ CONFIG_PATH = "app/config.json"
10
+
11
+
12
+ def load_config() -> dict:
13
+ with open(CONFIG_PATH, "r") as f:
14
+ return json.load(f)
15
+
16
+
17
+ config = load_config()
18
+
19
+ EL_API_KEY = config["elevenlabs"]["api_key"]
20
+ VOICE_ID = config["elevenlabs"]["voice_id"]
21
+ MODEL_ID = config["elevenlabs"]["model_id"]
22
+
23
+ ELEVENLABS_STREAM_URL = (
24
+ f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}/stream"
25
+ )
26
+
27
+ HEADERS = {
28
+ "xi-api-key": EL_API_KEY,
29
+ "Content-Type": "application/json",
30
+ "Accept": "audio/mpeg",
31
+ }
32
+
33
+ app = FastAPI(
34
+ title="Production TTS Service",
35
+ version="1.0.2",
36
+ docs_url="/docs",
37
+ redoc_url="/redoc",
38
+ )
39
+
40
+
41
+ class TTSRequest(BaseModel):
42
+ text: str = Field(..., min_length=1, max_length=5000)
43
+ stability: float = Field(0.5, ge=0.0, le=1.0)
44
+ similarity_boost: float = Field(0.5, ge=0.0, le=1.0)
45
+
46
+
47
+ @app.get("/health")
48
+ async def health():
49
+ return {"status": "ok"}
50
+
51
+
52
+
53
+ @app.post("/tts")
54
+ async def text_to_speech(payload: TTSRequest):
55
+ body = {
56
+ "text": payload.text,
57
+ "model_id": MODEL_ID,
58
+ "voice_settings": {
59
+ "stability": payload.stability,
60
+ "similarity_boost": payload.similarity_boost,
61
+ },
62
+ }
63
+
64
+ async def audio_stream():
65
+ async with httpx.AsyncClient(timeout=None) as client:
66
+ async with client.stream(
67
+ method="POST",
68
+ url=ELEVENLABS_STREAM_URL,
69
+ headers=HEADERS,
70
+ json=body,
71
+ ) as response:
72
+
73
+ if response.status_code != 200:
74
+ error = await response.aread()
75
+ raise HTTPException(
76
+ status_code=502,
77
+ detail=error.decode(),
78
+ )
79
+
80
+ async for chunk in response.aiter_bytes():
81
+ yield chunk
82
+
83
+ return StreamingResponse(
84
+ audio_stream(),
85
+ media_type="audio/mpeg",
86
+ headers={
87
+ "Content-Disposition": "inline; filename=tts.mp3",
88
+ },
89
+ )
90
+
91
+
92
+
93
+ @app.post("/tts/buffered")
94
+ async def text_to_speech_buffered(payload: TTSRequest):
95
+ body = {
96
+ "text": payload.text,
97
+ "model_id": MODEL_ID,
98
+ "voice_settings": {
99
+ "stability": payload.stability,
100
+ "similarity_boost": payload.similarity_boost,
101
+ },
102
+ }
103
+
104
+ async with httpx.AsyncClient(timeout=30.0) as client:
105
+ response = await client.post(
106
+ ELEVENLABS_STREAM_URL,
107
+ headers=HEADERS,
108
+ json=body,
109
+ )
110
+
111
+ if response.status_code != 200:
112
+ raise HTTPException(
113
+ status_code=502,
114
+ detail=response.text,
115
+ )
116
+
117
+ if not response.content:
118
+ raise HTTPException(
119
+ status_code=500,
120
+ detail="Received empty audio buffer",
121
+ )
122
+
123
+ return Response(
124
+ content=response.content,
125
+ media_type="audio/mpeg",
126
+ headers={
127
+ "Content-Disposition": "attachment; filename=tts.mp3",
128
+ "Content-Length": str(len(response.content)),
129
+ },
130
+ )
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ httpx
4
+ pydantic