Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,129 +1,134 @@
|
|
1 |
-
import
|
2 |
-
import
|
|
|
3 |
import time
|
4 |
-
|
5 |
-
from fastapi import FastAPI, HTTPException, Body, Response
|
6 |
-
from fastapi.responses import StreamingResponse
|
7 |
-
from pydantic import BaseModel, Field # Field for adding validation/defaults
|
8 |
from gtts import gTTS, gTTSError
|
|
|
9 |
|
10 |
# --- Configuration ---
|
11 |
logging.basicConfig(level=logging.INFO)
|
12 |
logger = logging.getLogger(__name__)
|
13 |
|
14 |
-
#
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
"
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
500: {"description": "Internal Server Error (e.g., gTTS failed)"},
|
42 |
-
},
|
43 |
-
)
|
44 |
-
async def generate_speech_gtts_api(
|
45 |
-
tts_request: TTSRequest = Body(...)
|
46 |
-
):
|
47 |
"""
|
48 |
-
|
49 |
-
|
50 |
"""
|
51 |
-
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
54 |
|
55 |
-
|
56 |
-
# The pydantic model validation (min_length=1) should catch this,
|
57 |
-
# but belt-and-suspenders approach is fine.
|
58 |
-
raise HTTPException(status_code=400, detail="Input text cannot be empty.")
|
59 |
|
60 |
-
logger.info(f"
|
61 |
start_synth_time = time.time()
|
62 |
|
63 |
try:
|
64 |
-
# --- Generate Audio using gTTS ---
|
65 |
# Create gTTS object
|
66 |
-
tts = gTTS(text=
|
67 |
|
68 |
-
#
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
|
|
73 |
|
74 |
synthesis_time = time.time() - start_synth_time
|
75 |
-
logger.info(f"gTTS audio
|
76 |
|
77 |
-
#
|
78 |
-
|
79 |
-
|
80 |
-
media_type="audio/mpeg", # Standard MIME type for MP3
|
81 |
-
headers={'Content-Disposition': 'attachment; filename="speech.mp3"'} # Suggest filename
|
82 |
-
)
|
83 |
|
84 |
except gTTSError as e:
|
85 |
-
logger.error(f"gTTS Error: {e}", exc_info=True)
|
86 |
-
|
87 |
-
if "Language not supported" in str(e):
|
88 |
-
raise HTTPException(status_code=400, detail=f"Language '{lang}' not supported by gTTS. Error: {e}")
|
89 |
-
else:
|
90 |
-
raise HTTPException(status_code=500, detail=f"gTTS failed to generate speech. Error: {e}")
|
91 |
except Exception as e:
|
92 |
-
logger.error(f"An unexpected error occurred
|
93 |
-
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
|
|
|
|
|
|
95 |
|
96 |
-
# ---
|
97 |
-
@app.get("/health", tags=["System"]
|
98 |
async def health_check():
|
99 |
-
"""
|
100 |
-
|
101 |
-
"""
|
102 |
-
# Can add a quick gTTS test here if needed, but might slow down health check
|
103 |
-
# try:
|
104 |
-
# gTTS(text='test', lang='en').save('test.mp3') # Dummy generation
|
105 |
-
# os.remove('test.mp3')
|
106 |
-
# except Exception as e:
|
107 |
-
# return {"status": "unhealthy", "reason": f"gTTS basic test failed: {e}"}
|
108 |
-
return {"status": "ok"}
|
109 |
-
|
110 |
-
# --- Root Endpoint (Optional Information) ---
|
111 |
-
@app.get("/", tags=["System"], summary="API Information")
|
112 |
-
async def read_root():
|
113 |
-
"""
|
114 |
-
Provides basic information about the API.
|
115 |
-
"""
|
116 |
-
return {
|
117 |
-
"message": "Welcome to the gTTS API Service!",
|
118 |
-
"tts_engine": "gTTS (Google Text-to-Speech)",
|
119 |
-
"tts_endpoint": "/api/tts",
|
120 |
-
"health_endpoint": "/health",
|
121 |
-
"expected_request_body": {"text": "string", "lang": "string (optional, default 'en')"},
|
122 |
-
"response_content_type": "audio/mpeg",
|
123 |
-
"documentation": "/docs" # Link to FastAPI auto-generated docs
|
124 |
-
}
|
125 |
|
126 |
# --- How to Run Locally (for testing) ---
|
127 |
# if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
128 |
# import uvicorn
|
129 |
-
# uvicorn.run(
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import os
|
3 |
+
import uuid
|
4 |
import time
|
5 |
+
import logging
|
|
|
|
|
|
|
6 |
from gtts import gTTS, gTTSError
|
7 |
+
from fastapi import FastAPI # Import FastAPI for mounting
|
8 |
|
9 |
# --- Configuration ---
|
10 |
logging.basicConfig(level=logging.INFO)
|
11 |
logger = logging.getLogger(__name__)
|
12 |
|
13 |
+
# Define a temporary directory for audio files if needed
|
14 |
+
# Gradio often handles temporary files well, but explicit control can be useful.
|
15 |
+
TEMP_DIR = "temp_audio_gradio"
|
16 |
+
os.makedirs(TEMP_DIR, exist_ok=True)
|
17 |
+
|
18 |
+
# Supported languages for the dropdown (add more as needed)
|
19 |
+
# You can find codes here: https://gtts.readthedocs.io/en/latest/module.html#languages-gtts-lang
|
20 |
+
SUPPORTED_LANGUAGES = {
|
21 |
+
"English": "en",
|
22 |
+
"Spanish": "es",
|
23 |
+
"French": "fr",
|
24 |
+
"German": "de",
|
25 |
+
"Italian": "it",
|
26 |
+
"Portuguese": "pt",
|
27 |
+
"Dutch": "nl",
|
28 |
+
"Russian": "ru",
|
29 |
+
"Japanese": "ja",
|
30 |
+
"Korean": "ko",
|
31 |
+
"Chinese (Mandarin/Simplified)": "zh-cn",
|
32 |
+
"Chinese (Mandarin/Traditional)": "zh-tw",
|
33 |
+
"Hindi": "hi",
|
34 |
+
"Arabic": "ar",
|
35 |
+
}
|
36 |
+
LANGUAGE_NAMES = list(SUPPORTED_LANGUAGES.keys())
|
37 |
+
|
38 |
+
# --- Core gTTS Function for Gradio ---
|
39 |
+
def generate_gtts_audio(text_input: str, language_name: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
"""
|
41 |
+
Takes text and language name, generates MP3 using gTTS, saves it temporarily,
|
42 |
+
and returns the filepath for the Gradio Audio component.
|
43 |
"""
|
44 |
+
if not text_input or not text_input.strip():
|
45 |
+
# Raise a Gradio-specific error to show in the UI
|
46 |
+
raise gr.Error("Please enter some text to synthesize.")
|
47 |
+
|
48 |
+
if not language_name or language_name not in SUPPORTED_LANGUAGES:
|
49 |
+
raise gr.Error(f"Invalid language selected: {language_name}")
|
50 |
|
51 |
+
lang_code = SUPPORTED_LANGUAGES[language_name]
|
|
|
|
|
|
|
52 |
|
53 |
+
logger.info(f"Gradio request: lang='{lang_code}', text='{text_input[:50]}...'")
|
54 |
start_synth_time = time.time()
|
55 |
|
56 |
try:
|
|
|
57 |
# Create gTTS object
|
58 |
+
tts = gTTS(text=text_input, lang=lang_code, slow=False)
|
59 |
|
60 |
+
# Generate a unique filename for the temporary MP3 file
|
61 |
+
filename = f"gtts_speech_{uuid.uuid4()}.mp3"
|
62 |
+
filepath = os.path.join(TEMP_DIR, filename)
|
63 |
+
|
64 |
+
# Save the audio file
|
65 |
+
tts.save(filepath)
|
66 |
|
67 |
synthesis_time = time.time() - start_synth_time
|
68 |
+
logger.info(f"gTTS audio saved to '{filepath}' in {synthesis_time:.2f} seconds.")
|
69 |
|
70 |
+
# Return the path to the generated audio file
|
71 |
+
# Gradio's Audio component with type="filepath" will handle serving this file.
|
72 |
+
return filepath
|
|
|
|
|
|
|
73 |
|
74 |
except gTTSError as e:
|
75 |
+
logger.error(f"gTTS Error during generation: {e}", exc_info=True)
|
76 |
+
raise gr.Error(f"gTTS failed to generate speech. Error: {e}")
|
|
|
|
|
|
|
|
|
77 |
except Exception as e:
|
78 |
+
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
|
79 |
+
raise gr.Error(f"An unexpected server error occurred. Error: {str(e)}")
|
80 |
+
|
81 |
+
# --- Create Gradio Interface ---
|
82 |
+
iface = gr.Interface(
|
83 |
+
fn=generate_gtts_audio,
|
84 |
+
inputs=[
|
85 |
+
gr.Textbox(
|
86 |
+
label="Text to Synthesize",
|
87 |
+
placeholder="Enter the text you want to convert to speech...",
|
88 |
+
lines=4
|
89 |
+
),
|
90 |
+
gr.Dropdown(
|
91 |
+
label="Language",
|
92 |
+
choices=LANGUAGE_NAMES,
|
93 |
+
value="English", # Default language
|
94 |
+
info="Select the language for the speech."
|
95 |
+
)
|
96 |
+
],
|
97 |
+
outputs=gr.Audio(
|
98 |
+
label="Generated Speech (MP3)",
|
99 |
+
type="filepath" # Gradio handles serving the file from the returned path
|
100 |
+
),
|
101 |
+
title="Text-to-Speech with gTTS",
|
102 |
+
description="Enter text and select a language to generate an MP3 audio file using Google Text-to-Speech.",
|
103 |
+
examples=[
|
104 |
+
["Hello, this is a demonstration of the gTTS library.", "English"],
|
105 |
+
["Bonjour le monde, ceci est un test.", "French"],
|
106 |
+
["Hola mundo, esto es un ejemplo en español.", "Spanish"],
|
107 |
+
],
|
108 |
+
allow_flagging="never", # Disable the flagging feature if not needed
|
109 |
+
# You can add custom CSS or themes here if desired
|
110 |
+
# theme=gr.themes.Default()
|
111 |
+
)
|
112 |
+
|
113 |
+
# --- Setup FastAPI App (Optional, but standard for Spaces) ---
|
114 |
+
# This allows Gradio to be served alongside other potential FastAPI endpoints.
|
115 |
+
app = FastAPI()
|
116 |
|
117 |
+
# --- Mount the Gradio Interface onto the FastAPI app ---
|
118 |
+
# The Gradio UI will be available at the '/ ' route of your Space URL
|
119 |
+
app = gr.mount_gradio_app(app, iface, path="/")
|
120 |
|
121 |
+
# --- Optional: Add a simple health check for FastAPI ---
|
122 |
+
@app.get("/health", tags=["System"])
|
123 |
async def health_check():
|
124 |
+
return {"status": "ok", "message": "Gradio service running"}
|
125 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
|
127 |
# --- How to Run Locally (for testing) ---
|
128 |
# if __name__ == "__main__":
|
129 |
+
# # When running locally, Gradio's launch() is often simpler
|
130 |
+
# # iface.launch(server_name="127.0.0.1", server_port=7860)
|
131 |
+
|
132 |
+
# # Or, if you want to test the FastAPI mounting locally:
|
133 |
# import uvicorn
|
134 |
+
# uvicorn.run(app, host="127.0.0.1", port=8000)
|