Athspi commited on
Commit
cf53623
·
verified ·
1 Parent(s): 5eaef53

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -60
app.py CHANGED
@@ -13,14 +13,13 @@ from dotenv import load_dotenv
13
  load_dotenv()
14
 
15
  app = FastAPI(
16
- title="Google GenAI TTS API with Multiple API Keys",
17
- description="Text-to-Speech API using Google GenAI with multiple API keys fallback.",
18
- version="1.2.0",
19
  docs_url="/docs",
20
  redoc_url=None
21
  )
22
 
23
- # Pydantic model for request body
24
  class TTSRequest(BaseModel):
25
  text: str
26
  voice_name: Optional[str] = "Kore"
@@ -30,27 +29,12 @@ class TTSRequest(BaseModel):
30
  sample_width: Optional[int] = 2
31
 
32
  def get_api_keys() -> List[str]:
33
- """Retrieve list of API keys from environment variable"""
34
  api_keys = os.getenv("GEMINI_API_KEYS")
35
  if not api_keys:
36
  raise ValueError("No API keys found in GEMINI_API_KEYS environment variable.")
37
  return [key.strip() for key in api_keys.split(",") if key.strip()]
38
 
39
- def initialize_genai_client():
40
- """Initialize the GenAI client by trying multiple API keys"""
41
- api_keys = get_api_keys()
42
- for key in api_keys:
43
- try:
44
- print(f"Trying API key: {key[:5]}...") # Only show part for safety
45
- client = genai.Client(api_key=key)
46
- return client
47
- except Exception as e:
48
- print(f"Failed with key {key[:5]}... : {e}")
49
-
50
- raise ValueError("No valid API key could initialize the GenAI client.")
51
-
52
  def generate_wave_bytes(pcm_data: bytes, channels: int, rate: int, sample_width: int) -> bytes:
53
- """Convert PCM audio data into WAV bytes."""
54
  with io.BytesIO() as wav_buffer:
55
  with wave.open(wav_buffer, "wb") as wf:
56
  wf.setnchannels(channels)
@@ -59,54 +43,73 @@ def generate_wave_bytes(pcm_data: bytes, channels: int, rate: int, sample_width:
59
  wf.writeframes(pcm_data)
60
  return wav_buffer.getvalue()
61
 
62
- @app.post("/api/generate-tts/")
63
- async def generate_tts(request: TTSRequest):
64
- """
65
- Convert text to speech audio using Google GenAI.
66
- """
67
- try:
68
- client = initialize_genai_client()
69
-
70
- text_to_speak = f"Say cheerfully: {request.text}" if request.cheerful else request.text
71
-
72
- response = client.models.generate_content(
73
- model="gemini-2.5-flash-preview-tts",
74
- contents=text_to_speak,
75
- config=types.GenerateContentConfig(
76
- response_modalities=["AUDIO"],
77
- speech_config=types.SpeechConfig(
78
- voice_config=types.VoiceConfig(
79
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
80
- voice_name=request.voice_name,
81
- )
82
  )
83
- ),
84
- )
85
  )
 
86
 
87
- if not response.candidates or not response.candidates[0].content.parts:
88
- raise HTTPException(status_code=500, detail="No audio data received from GenAI.")
89
 
90
- audio_data = response.candidates[0].content.parts[0].inline_data.data
91
 
92
- wav_bytes = generate_wave_bytes(
93
- audio_data,
94
- channels=request.channels,
95
- rate=request.sample_rate,
96
- sample_width=request.sample_width
97
- )
98
 
99
- return StreamingResponse(
100
- io.BytesIO(wav_bytes),
101
- media_type="audio/wav",
102
- headers={"Content-Disposition": "attachment; filename=generated_audio.wav"}
103
- )
104
 
105
- except Exception as e:
106
- return JSONResponse(
107
- {"status": "error", "message": str(e)},
108
- status_code=500
109
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  @app.get("/")
112
  async def root():
 
13
  load_dotenv()
14
 
15
  app = FastAPI(
16
+ title="Google GenAI TTS API with Multi-Key Quota Fallback",
17
+ description="TTS API using Google GenAI with multiple API keys and 429 quota handling.",
18
+ version="1.3.0",
19
  docs_url="/docs",
20
  redoc_url=None
21
  )
22
 
 
23
  class TTSRequest(BaseModel):
24
  text: str
25
  voice_name: Optional[str] = "Kore"
 
29
  sample_width: Optional[int] = 2
30
 
31
  def get_api_keys() -> List[str]:
 
32
  api_keys = os.getenv("GEMINI_API_KEYS")
33
  if not api_keys:
34
  raise ValueError("No API keys found in GEMINI_API_KEYS environment variable.")
35
  return [key.strip() for key in api_keys.split(",") if key.strip()]
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  def generate_wave_bytes(pcm_data: bytes, channels: int, rate: int, sample_width: int) -> bytes:
 
38
  with io.BytesIO() as wav_buffer:
39
  with wave.open(wav_buffer, "wb") as wf:
40
  wf.setnchannels(channels)
 
43
  wf.writeframes(pcm_data)
44
  return wav_buffer.getvalue()
45
 
46
+ def try_generate_tts(api_key, request):
47
+ client = genai.Client(api_key=api_key)
48
+ text_to_speak = f"Say cheerfully: {request.text}" if request.cheerful else request.text
49
+
50
+ response = client.models.generate_content(
51
+ model="gemini-2.5-flash-preview-tts",
52
+ contents=text_to_speak,
53
+ config=types.GenerateContentConfig(
54
+ response_modalities=["AUDIO"],
55
+ speech_config=types.SpeechConfig(
56
+ voice_config=types.VoiceConfig(
57
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
58
+ voice_name=request.voice_name,
 
 
 
 
 
 
 
59
  )
60
+ )
61
+ ),
62
  )
63
+ )
64
 
65
+ if not response.candidates or not response.candidates[0].content.parts:
66
+ raise HTTPException(status_code=500, detail="No audio data received from GenAI.")
67
 
68
+ audio_data = response.candidates[0].content.parts[0].inline_data.data
69
 
70
+ wav_bytes = generate_wave_bytes(
71
+ audio_data,
72
+ channels=request.channels,
73
+ rate=request.sample_rate,
74
+ sample_width=request.sample_width
75
+ )
76
 
77
+ return wav_bytes
 
 
 
 
78
 
79
+ @app.post("/api/generate-tts/")
80
+ async def generate_tts(request: TTSRequest):
81
+ api_keys = get_api_keys()
82
+ last_error = None
83
+
84
+ for api_key in api_keys:
85
+ try:
86
+ print(f"Trying API key: {api_key[:5]}...")
87
+ wav_bytes = try_generate_tts(api_key, request)
88
+ return StreamingResponse(
89
+ io.BytesIO(wav_bytes),
90
+ media_type="audio/wav",
91
+ headers={"Content-Disposition": "attachment; filename=generated_audio.wav"}
92
+ )
93
+
94
+ except Exception as e:
95
+ error_msg = str(e)
96
+ print(f"Key {api_key[:5]} failed: {error_msg}")
97
+
98
+ if "RESOURCE_EXHAUSTED" in error_msg or "quota" in error_msg.lower() or "429" in error_msg:
99
+ # Quota exhausted, try next key
100
+ last_error = error_msg
101
+ continue
102
+ else:
103
+ # Some other error, don't continue
104
+ return JSONResponse(
105
+ {"status": "error", "message": error_msg},
106
+ status_code=500
107
+ )
108
+
109
+ return JSONResponse(
110
+ {"status": "error", "message": f"All API keys exhausted or invalid. Last error: {last_error}"},
111
+ status_code=429
112
+ )
113
 
114
  @app.get("/")
115
  async def root():