yukee1992 commited on
Commit
0ae19be
Β·
verified Β·
1 Parent(s): bd750a0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +212 -41
app.py CHANGED
@@ -31,7 +31,7 @@ app.add_middleware(
31
  )
32
 
33
  # Configuration
34
- OCI_UPLOAD_API_URL = os.getenv("OCI_UPLOAD_API_URL", "https://yukee1992-oci-video-storage.hf.space")
35
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
36
 
37
  print(f"βœ… Using device: {DEVICE}")
@@ -47,6 +47,7 @@ current_model = ""
47
  voice_cloning_supported = False
48
  model_loading = False
49
  model_load_attempts = 0
 
50
 
51
  # Pydantic models
52
  class TTSRequest(BaseModel):
@@ -54,18 +55,23 @@ class TTSRequest(BaseModel):
54
  project_id: str
55
  voice_name: Optional[str] = "default"
56
  language: Optional[str] = "en"
 
57
 
58
  class BatchTTSRequest(BaseModel):
59
  texts: List[str]
60
  project_id: str
61
  voice_name: Optional[str] = "default"
62
  language: Optional[str] = "en"
 
63
 
64
  class VoiceCloneRequest(BaseModel):
65
  project_id: str
66
  voice_name: str
67
  description: Optional[str] = ""
68
 
 
 
 
69
  # Helper functions
70
  def clean_text(text):
71
  """Clean text for TTS generation"""
@@ -98,8 +104,10 @@ def clean_text(text):
98
  def upload_to_oci(file_path: str, filename: str, project_id: str, file_type="voiceover"):
99
  """Upload file to OCI using your existing API with subfolder support"""
100
  try:
101
- if not OCI_UPLOAD_API_URL:
102
- return None, "OCI upload API URL not configured"
 
 
103
 
104
  url = f"{OCI_UPLOAD_API_URL}/api/upload"
105
 
@@ -110,7 +118,8 @@ def upload_to_oci(file_path: str, filename: str, project_id: str, file_type="voi
110
  "subfolder": "voiceover"
111
  }
112
 
113
- response = requests.post(url, files=files, data=data, timeout=30)
 
114
 
115
  if response.status_code == 200:
116
  result = response.json()
@@ -121,6 +130,10 @@ def upload_to_oci(file_path: str, filename: str, project_id: str, file_type="voi
121
  else:
122
  return None, f"Upload failed with status {response.status_code}"
123
 
 
 
 
 
124
  except Exception as e:
125
  return None, f"Upload error: {str(e)}"
126
 
@@ -223,9 +236,9 @@ def save_wav(audio, file_path):
223
  print(f"❌ Failed to save WAV: {e}")
224
  return False
225
 
226
- def load_tts_model(voice_style="default"):
227
  """Load TTS model with different voice options"""
228
- global tts, model_loaded, current_model, voice_cloning_supported, model_loading, model_load_attempts
229
 
230
  if model_loading:
231
  print("⏳ Model is already being loaded...")
@@ -280,6 +293,7 @@ def load_tts_model(voice_style="default"):
280
  }
281
 
282
  selected_model = model_options.get(voice_style, model_options["default_female"])
 
283
 
284
  print(f"πŸš€ Loading {selected_model['description']}...")
285
 
@@ -319,6 +333,7 @@ def load_tts_model(voice_style="default"):
319
  model_loaded = True
320
  current_model = "tts_models/en/ljspeech/tacotron2-DDC"
321
  voice_cloning_supported = False
 
322
  return True
323
 
324
  finally:
@@ -335,9 +350,9 @@ def load_tts_model(voice_style="default"):
335
  async def generate_tts(request: TTSRequest):
336
  """Generate TTS for a single text with lazy model loading"""
337
  try:
338
- # Lazy load model on first request
339
- if not model_loaded:
340
- if not load_tts_model():
341
  return {
342
  "status": "error",
343
  "message": "TTS model failed to load. Please check the logs.",
@@ -347,7 +362,7 @@ async def generate_tts(request: TTSRequest):
347
 
348
  print(f"πŸ“₯ TTS request for project: {request.project_id}")
349
  print(f" Text length: {len(request.text)} characters")
350
- print(f" Voice: {request.voice_name}")
351
  print(f" Language: {request.language}")
352
 
353
  # Check if voice cloning is requested but not supported
@@ -385,34 +400,47 @@ async def generate_tts(request: TTSRequest):
385
 
386
  # Generate TTS based on model capabilities - WITH ERROR HANDLING
387
  try:
388
- if supports_voice_cloning():
389
- # XTTS model with voice cloning support
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  tts.tts_to_file(
391
- text=cleaned_text, # Use cleaned text
392
- speaker_wav=speaker_wav,
393
- language=request.language,
394
- file_path=output_path
395
  )
396
  else:
397
- # Fallback model (Tacotron2)
398
  tts.tts_to_file(
399
- text=cleaned_text, # Use cleaned text
400
  file_path=output_path
401
  )
 
402
  except Exception as tts_error:
403
  print(f"❌ TTS generation failed: {tts_error}")
404
  # Try alternative approach
405
  try:
406
  print("πŸ”„ Trying alternative TTS generation method...")
407
  # Generate audio first, then save
408
- if supports_voice_cloning():
409
  audio = tts.tts(
410
- text=cleaned_text, # Use cleaned text
411
- speaker_wav=speaker_wav,
412
- language=request.language
413
  )
414
  else:
415
- audio = tts.tts(text=cleaned_text) # Use cleaned text
416
 
417
  # Save manually
418
  if not save_wav(audio, output_path):
@@ -442,7 +470,9 @@ async def generate_tts(request: TTSRequest):
442
  "message": f"TTS generated but upload failed: {error}",
443
  "local_file": output_path,
444
  "filename": filename,
445
- "file_size": file_size
 
 
446
  }
447
 
448
  print(f"βœ… Upload successful: {filename}")
@@ -460,6 +490,7 @@ async def generate_tts(request: TTSRequest):
460
  "filename": filename,
461
  "oci_path": upload_result.get("path", f"{request.project_id}/voiceover/{filename}"),
462
  "model_used": current_model,
 
463
  "voice_cloning": supports_voice_cloning() and request.voice_name != "default"
464
  }
465
 
@@ -469,6 +500,7 @@ async def generate_tts(request: TTSRequest):
469
  error_detail = {
470
  "error": str(e),
471
  "model": current_model,
 
472
  "voice_cloning_supported": supports_voice_cloning(),
473
  "device": DEVICE
474
  }
@@ -479,13 +511,13 @@ async def batch_generate_tts(request: BatchTTSRequest):
479
  """Generate TTS for multiple texts with sequential naming"""
480
  try:
481
  # Lazy load model on first request
482
- if not model_loaded:
483
- if not load_tts_model():
484
  raise HTTPException(status_code=500, detail="TTS model failed to load")
485
 
486
  print(f"πŸ“₯ Batch TTS request for project: {request.project_id}")
487
  print(f" Number of texts: {len(request.texts)}")
488
- print(f" Voice: {request.voice_name}")
489
  print(f" Language: {request.language}")
490
 
491
  # Check if voice cloning is requested but not supported
@@ -520,16 +552,26 @@ async def batch_generate_tts(request: BatchTTSRequest):
520
 
521
  # Generate TTS based on model capabilities - WITH ERROR HANDLING
522
  try:
523
- if supports_voice_cloning():
 
 
 
 
 
 
 
 
 
 
 
524
  tts.tts_to_file(
525
- text=cleaned_text, # Use cleaned text
526
- speaker_wav=speaker_wav,
527
- language=request.language,
528
- file_path=output_path
529
  )
530
  else:
531
  tts.tts_to_file(
532
- text=cleaned_text, # Use cleaned text
533
  file_path=output_path
534
  )
535
  except Exception as tts_error:
@@ -537,14 +579,13 @@ async def batch_generate_tts(request: BatchTTSRequest):
537
  # Try alternative approach
538
  try:
539
  print("πŸ”„ Trying alternative TTS generation method...")
540
- if supports_voice_cloning():
541
  audio = tts.tts(
542
- text=cleaned_text, # Use cleaned text
543
- speaker_wav=speaker_wav,
544
- language=request.language
545
  )
546
  else:
547
- audio = tts.tts(text=cleaned_text) # Use cleaned text
548
 
549
  # Save manually
550
  if not save_wav(audio, output_path):
@@ -611,6 +652,7 @@ async def batch_generate_tts(request: BatchTTSRequest):
611
  "project_id": request.project_id,
612
  "results": results,
613
  "model_used": current_model,
 
614
  "voice_cloning": supports_voice_cloning() and request.voice_name != "default"
615
  }
616
 
@@ -755,6 +797,7 @@ async def health_check():
755
  "status": "healthy",
756
  "tts_loaded": model_loaded,
757
  "model": current_model,
 
758
  "voice_cloning_supported": voice_cloning_supported,
759
  "device": DEVICE,
760
  "load_attempts": model_load_attempts,
@@ -773,15 +816,137 @@ async def reload_model():
773
  voice_cloning_supported = False
774
 
775
  # Try to reload
776
- success = load_tts_model()
777
 
778
  return {
779
  "status": "success" if success else "error",
780
  "message": "Model reloaded successfully" if success else "Failed to reload model",
781
  "model_loaded": model_loaded,
782
- "model": current_model
 
783
  }
784
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
785
  @app.get("/")
786
  async def root():
787
  """Root endpoint with API information"""
@@ -794,10 +959,15 @@ async def root():
794
  "POST /api/clone-voice": "Clone a voice from multiple samples",
795
  "GET /api/voices": "List available voices",
796
  "GET /api/health": "Health check",
797
- "POST /api/reload-model": "Reload TTS model"
 
 
 
 
798
  },
799
  "model_loaded": model_loaded,
800
  "model_name": current_model if model_loaded else "None",
 
801
  "voice_cloning_supported": supports_voice_cloning()
802
  }
803
 
@@ -806,5 +976,6 @@ if __name__ == "__main__":
806
  print("πŸš€ Starting TTS API with Coqui TTS and Voice Cloning...")
807
  print("πŸ“Š API endpoints available at: http://localhost:7860/")
808
  print("πŸ’‘ Model will be loaded on first request to save memory")
 
809
  print("πŸ”„ Use /api/reload-model to force reload if needed")
810
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
31
  )
32
 
33
  # Configuration
34
+ OCI_UPLOAD_API_URL = os.getenv("OCI_UPLOAD_API_URL", "http://localhost:7860")
35
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
36
 
37
  print(f"βœ… Using device: {DEVICE}")
 
47
  voice_cloning_supported = False
48
  model_loading = False
49
  model_load_attempts = 0
50
+ current_voice_style = "default_female"
51
 
52
  # Pydantic models
53
  class TTSRequest(BaseModel):
 
55
  project_id: str
56
  voice_name: Optional[str] = "default"
57
  language: Optional[str] = "en"
58
+ voice_style: Optional[str] = "default_female" # Add voice style selection
59
 
60
  class BatchTTSRequest(BaseModel):
61
  texts: List[str]
62
  project_id: str
63
  voice_name: Optional[str] = "default"
64
  language: Optional[str] = "en"
65
+ voice_style: Optional[str] = "default_female"
66
 
67
  class VoiceCloneRequest(BaseModel):
68
  project_id: str
69
  voice_name: str
70
  description: Optional[str] = ""
71
 
72
+ class ChangeVoiceRequest(BaseModel):
73
+ voice_style: str
74
+
75
  # Helper functions
76
  def clean_text(text):
77
  """Clean text for TTS generation"""
 
104
  def upload_to_oci(file_path: str, filename: str, project_id: str, file_type="voiceover"):
105
  """Upload file to OCI using your existing API with subfolder support"""
106
  try:
107
+ if not OCI_UPLOAD_API_URL or OCI_UPLOAD_API_URL == "http://localhost:7860":
108
+ # If OCI API is not configured or is localhost, skip upload
109
+ print("⚠️ OCI upload skipped - no valid OCI_UPLOAD_API_URL configured")
110
+ return {"status": "skipped", "message": "OCI upload disabled"}, None
111
 
112
  url = f"{OCI_UPLOAD_API_URL}/api/upload"
113
 
 
118
  "subfolder": "voiceover"
119
  }
120
 
121
+ # Increase timeout and add better error handling
122
+ response = requests.post(url, files=files, data=data, timeout=60)
123
 
124
  if response.status_code == 200:
125
  result = response.json()
 
130
  else:
131
  return None, f"Upload failed with status {response.status_code}"
132
 
133
+ except requests.exceptions.Timeout:
134
+ return None, "OCI upload timeout - server took too long to respond"
135
+ except requests.exceptions.ConnectionError:
136
+ return None, "Cannot connect to OCI API - check if the server is running"
137
  except Exception as e:
138
  return None, f"Upload error: {str(e)}"
139
 
 
236
  print(f"❌ Failed to save WAV: {e}")
237
  return False
238
 
239
+ def load_tts_model(voice_style="default_female"):
240
  """Load TTS model with different voice options"""
241
+ global tts, model_loaded, current_model, voice_cloning_supported, model_loading, model_load_attempts, current_voice_style
242
 
243
  if model_loading:
244
  print("⏳ Model is already being loaded...")
 
293
  }
294
 
295
  selected_model = model_options.get(voice_style, model_options["default_female"])
296
+ current_voice_style = voice_style
297
 
298
  print(f"πŸš€ Loading {selected_model['description']}...")
299
 
 
333
  model_loaded = True
334
  current_model = "tts_models/en/ljspeech/tacotron2-DDC"
335
  voice_cloning_supported = False
336
+ current_voice_style = "default_female"
337
  return True
338
 
339
  finally:
 
350
  async def generate_tts(request: TTSRequest):
351
  """Generate TTS for a single text with lazy model loading"""
352
  try:
353
+ # Lazy load model on first request with voice style
354
+ if not model_loaded or current_voice_style != request.voice_style:
355
+ if not load_tts_model(request.voice_style):
356
  return {
357
  "status": "error",
358
  "message": "TTS model failed to load. Please check the logs.",
 
362
 
363
  print(f"πŸ“₯ TTS request for project: {request.project_id}")
364
  print(f" Text length: {len(request.text)} characters")
365
+ print(f" Voice style: {request.voice_style}")
366
  print(f" Language: {request.language}")
367
 
368
  # Check if voice cloning is requested but not supported
 
400
 
401
  # Generate TTS based on model capabilities - WITH ERROR HANDLING
402
  try:
403
+ print(f"πŸ”Š Attempting TTS generation with {current_model}...")
404
+
405
+ # Get the speaker for VITS models
406
+ speaker = None
407
+ if "vctk/vits" in current_model:
408
+ # Map voice styles to VITS speakers
409
+ speaker_map = {
410
+ "male_deep": "p225",
411
+ "male_medium": "p226",
412
+ "female_1": "p227",
413
+ "female_2": "p228"
414
+ }
415
+ speaker = speaker_map.get(request.voice_style)
416
+
417
+ if speaker:
418
+ # For VITS model with speaker selection
419
  tts.tts_to_file(
420
+ text=cleaned_text,
421
+ file_path=output_path,
422
+ speaker=speaker
 
423
  )
424
  else:
425
+ # For standard models
426
  tts.tts_to_file(
427
+ text=cleaned_text,
428
  file_path=output_path
429
  )
430
+
431
  except Exception as tts_error:
432
  print(f"❌ TTS generation failed: {tts_error}")
433
  # Try alternative approach
434
  try:
435
  print("πŸ”„ Trying alternative TTS generation method...")
436
  # Generate audio first, then save
437
+ if speaker:
438
  audio = tts.tts(
439
+ text=cleaned_text,
440
+ speaker=speaker
 
441
  )
442
  else:
443
+ audio = tts.tts(text=cleaned_text)
444
 
445
  # Save manually
446
  if not save_wav(audio, output_path):
 
470
  "message": f"TTS generated but upload failed: {error}",
471
  "local_file": output_path,
472
  "filename": filename,
473
+ "file_size": file_size,
474
+ "voice_style": request.voice_style,
475
+ "model_used": current_model
476
  }
477
 
478
  print(f"βœ… Upload successful: {filename}")
 
490
  "filename": filename,
491
  "oci_path": upload_result.get("path", f"{request.project_id}/voiceover/{filename}"),
492
  "model_used": current_model,
493
+ "voice_style": request.voice_style,
494
  "voice_cloning": supports_voice_cloning() and request.voice_name != "default"
495
  }
496
 
 
500
  error_detail = {
501
  "error": str(e),
502
  "model": current_model,
503
+ "voice_style": request.voice_style,
504
  "voice_cloning_supported": supports_voice_cloning(),
505
  "device": DEVICE
506
  }
 
511
  """Generate TTS for multiple texts with sequential naming"""
512
  try:
513
  # Lazy load model on first request
514
+ if not model_loaded or current_voice_style != request.voice_style:
515
+ if not load_tts_model(request.voice_style):
516
  raise HTTPException(status_code=500, detail="TTS model failed to load")
517
 
518
  print(f"πŸ“₯ Batch TTS request for project: {request.project_id}")
519
  print(f" Number of texts: {len(request.texts)}")
520
+ print(f" Voice style: {request.voice_style}")
521
  print(f" Language: {request.language}")
522
 
523
  # Check if voice cloning is requested but not supported
 
552
 
553
  # Generate TTS based on model capabilities - WITH ERROR HANDLING
554
  try:
555
+ # Get the speaker for VITS models
556
+ speaker = None
557
+ if "vctk/vits" in current_model:
558
+ speaker_map = {
559
+ "male_deep": "p225",
560
+ "male_medium": "p226",
561
+ "female_1": "p227",
562
+ "female_2": "p228"
563
+ }
564
+ speaker = speaker_map.get(request.voice_style)
565
+
566
+ if speaker:
567
  tts.tts_to_file(
568
+ text=cleaned_text,
569
+ file_path=output_path,
570
+ speaker=speaker
 
571
  )
572
  else:
573
  tts.tts_to_file(
574
+ text=cleaned_text,
575
  file_path=output_path
576
  )
577
  except Exception as tts_error:
 
579
  # Try alternative approach
580
  try:
581
  print("πŸ”„ Trying alternative TTS generation method...")
582
+ if speaker:
583
  audio = tts.tts(
584
+ text=cleaned_text,
585
+ speaker=speaker
 
586
  )
587
  else:
588
+ audio = tts.tts(text=cleaned_text)
589
 
590
  # Save manually
591
  if not save_wav(audio, output_path):
 
652
  "project_id": request.project_id,
653
  "results": results,
654
  "model_used": current_model,
655
+ "voice_style": request.voice_style,
656
  "voice_cloning": supports_voice_cloning() and request.voice_name != "default"
657
  }
658
 
 
797
  "status": "healthy",
798
  "tts_loaded": model_loaded,
799
  "model": current_model,
800
+ "voice_style": current_voice_style,
801
  "voice_cloning_supported": voice_cloning_supported,
802
  "device": DEVICE,
803
  "load_attempts": model_load_attempts,
 
816
  voice_cloning_supported = False
817
 
818
  # Try to reload
819
+ success = load_tts_model(current_voice_style)
820
 
821
  return {
822
  "status": "success" if success else "error",
823
  "message": "Model reloaded successfully" if success else "Failed to reload model",
824
  "model_loaded": model_loaded,
825
+ "model": current_model,
826
+ "voice_style": current_voice_style
827
  }
828
 
829
+ @app.post("/api/change-voice")
830
+ async def change_voice(request: ChangeVoiceRequest):
831
+ """Change the TTS voice style"""
832
+ global tts, model_loaded, current_model, current_voice_style
833
+
834
+ try:
835
+ voice_options = {
836
+ "male_deep": "Deep male voice (VITS p225)",
837
+ "male_medium": "Medium male voice (VITS p226)",
838
+ "female_1": "Female voice 1 (VITS p227)",
839
+ "female_2": "Female voice 2 (VITS p228)",
840
+ "default_female": "Default female voice (Tacotron2)",
841
+ "clear_male": "Clear male voice (Tacotron2)"
842
+ }
843
+
844
+ if request.voice_style not in voice_options:
845
+ return {
846
+ "status": "error",
847
+ "message": f"Invalid voice style. Available: {list(voice_options.keys())}",
848
+ "available_voices": voice_options
849
+ }
850
+
851
+ print(f"πŸ”„ Changing voice to: {request.voice_style} - {voice_options[request.voice_style]}")
852
+
853
+ # Clear current model
854
+ tts = None
855
+ model_loaded = False
856
+
857
+ # Load new model with selected voice
858
+ success = load_tts_model(request.voice_style)
859
+
860
+ if success:
861
+ return {
862
+ "status": "success",
863
+ "message": f"Voice changed to {voice_options[request.voice_style]}",
864
+ "voice_style": request.voice_style,
865
+ "description": voice_options[request.voice_style]
866
+ }
867
+ else:
868
+ return {
869
+ "status": "error",
870
+ "message": "Failed to change voice"
871
+ }
872
+
873
+ except Exception as e:
874
+ raise HTTPException(status_code=500, detail=f"Voice change failed: {str(e)}")
875
+
876
+ @app.get("/api/available-voices")
877
+ async def get_available_voices():
878
+ """Get list of available voice options"""
879
+ voice_options = {
880
+ "male_deep": "Deep male voice (VITS p225)",
881
+ "male_medium": "Medium male voice (VITS p226)",
882
+ "female_1": "Female voice 1 (VITS p227)",
883
+ "female_2": "Female voice 2 (VITS p228)",
884
+ "default_female": "Default female voice (Tacotron2)",
885
+ "clear_male": "Clear male voice (Tacotron2)"
886
+ }
887
+
888
+ return {
889
+ "status": "success",
890
+ "available_voices": voice_options,
891
+ "current_voice": current_voice_style,
892
+ "current_model": current_model
893
+ }
894
+
895
+ @app.get("/api/download/{filename}")
896
+ async def download_file(filename: str):
897
+ """Download generated audio file directly"""
898
+ try:
899
+ file_path = f"/tmp/output/{filename}"
900
+
901
+ # Security check - only allow .wav files from output directory
902
+ if not filename.endswith('.wav') or '..' in filename or '/' in filename:
903
+ raise HTTPException(status_code=400, detail="Invalid filename")
904
+
905
+ if not os.path.exists(file_path):
906
+ raise HTTPException(status_code=404, detail="File not found")
907
+
908
+ # Get file info
909
+ file_size = os.path.getsize(file_path)
910
+ print(f"πŸ“₯ Serving download: {filename} ({file_size} bytes)")
911
+
912
+ # Return the audio file
913
+ from fastapi.responses import FileResponse
914
+ return FileResponse(
915
+ path=file_path,
916
+ media_type='audio/wav',
917
+ filename=filename
918
+ )
919
+
920
+ except Exception as e:
921
+ print(f"❌ Download failed: {str(e)}")
922
+ raise HTTPException(status_code=500, detail=f"Download failed: {str(e)}")
923
+
924
+ @app.get("/api/files")
925
+ async def list_files():
926
+ """List all generated audio files"""
927
+ try:
928
+ files_dir = Path("/tmp/output")
929
+ files = []
930
+
931
+ for file_path in files_dir.glob("*.wav"):
932
+ files.append({
933
+ "name": file_path.name,
934
+ "size": file_path.stat().st_size,
935
+ "created": datetime.fromtimestamp(file_path.stat().st_ctime).isoformat()
936
+ })
937
+
938
+ # Sort by creation time, newest first
939
+ files.sort(key=lambda x: x["created"], reverse=True)
940
+
941
+ return {
942
+ "status": "success",
943
+ "files": files,
944
+ "count": len(files)
945
+ }
946
+
947
+ except Exception as e:
948
+ raise HTTPException(status_code=500, detail=f"Failed to list files: {str(e)}")
949
+
950
  @app.get("/")
951
  async def root():
952
  """Root endpoint with API information"""
 
959
  "POST /api/clone-voice": "Clone a voice from multiple samples",
960
  "GET /api/voices": "List available voices",
961
  "GET /api/health": "Health check",
962
+ "POST /api/reload-model": "Reload TTS model",
963
+ "POST /api/change-voice": "Change voice style",
964
+ "GET /api/available-voices": "Get available voice options",
965
+ "GET /api/download/{filename}": "Download generated audio",
966
+ "GET /api/files": "List generated files"
967
  },
968
  "model_loaded": model_loaded,
969
  "model_name": current_model if model_loaded else "None",
970
+ "current_voice_style": current_voice_style,
971
  "voice_cloning_supported": supports_voice_cloning()
972
  }
973
 
 
976
  print("πŸš€ Starting TTS API with Coqui TTS and Voice Cloning...")
977
  print("πŸ“Š API endpoints available at: http://localhost:7860/")
978
  print("πŸ’‘ Model will be loaded on first request to save memory")
979
+ print("πŸŽ™οΈ Voice selection feature enabled")
980
  print("πŸ”„ Use /api/reload-model to force reload if needed")
981
  uvicorn.run(app, host="0.0.0.0", port=7860)