Athspi commited on
Commit
ae19a5c
·
verified ·
1 Parent(s): 0dfa504

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -100
app.py CHANGED
@@ -1,129 +1,134 @@
1
- import io
2
- import logging
 
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
- # --- Pydantic Model for Request Body ---
15
- class TTSRequest(BaseModel):
16
- text: str = Field(..., min_length=1, description="The text to be converted to speech.")
17
- lang: str = Field("en", description="Language code for the speech (e.g., 'en', 'es', 'fr'). See gTTS documentation for supported languages.")
18
- # Optional: Add tld if you need specific accents tied to Google domains
19
- # tld: str = Field("com", description="Top-level domain for Google TTS endpoint (e.g., 'com', 'co.uk', 'com.au')")
20
-
21
- # --- Initialize FastAPI App ---
22
- app = FastAPI(
23
- title="gTTS API Service",
24
- description="A simple API service that uses gTTS (Google Text-to-Speech) to convert text into speech (MP3 audio).",
25
- version="1.0.0",
26
- )
27
-
28
- # --- API Endpoint for Text-to-Speech ---
29
- @app.post(
30
- "/api/tts",
31
- tags=["TTS"],
32
- summary="Generate Speech using gTTS",
33
- description="""Send a JSON object with 'text' and optionally 'lang' fields.
34
- Returns the generated speech as an MP3 audio stream.""",
35
- responses={
36
- 200: {
37
- "content": {"audio/mpeg": {}}, # MP3 content type
38
- "description": "Successful response returning the MP3 audio stream.",
39
- },
40
- 400: {"description": "Bad Request (e.g., empty text, invalid language)"},
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
- Receives text and language via POST request, uses gTTS to generate
49
- speech, and returns the MP3 audio directly as a stream.
50
  """
51
- text = tts_request.text
52
- lang = tts_request.lang
53
- # tld = tts_request.tld # Uncomment if using tld
 
 
 
54
 
55
- if not text or not text.strip():
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"Received gTTS request: lang='{lang}', text='{text[:50]}...'")
61
  start_synth_time = time.time()
62
 
63
  try:
64
- # --- Generate Audio using gTTS ---
65
  # Create gTTS object
66
- tts = gTTS(text=text, lang=lang, slow=False) # Add tld=tld if using
67
 
68
- # --- Prepare Audio for Streaming ---
69
- # Use an in-memory buffer (avoids temporary files)
70
- mp3_fp = io.BytesIO()
71
- tts.write_to_fp(mp3_fp)
72
- mp3_fp.seek(0) # Rewind the buffer to the beginning for reading
 
73
 
74
  synthesis_time = time.time() - start_synth_time
75
- logger.info(f"gTTS audio generated in {synthesis_time:.2f} seconds.")
76
 
77
- # --- Return Streaming Response ---
78
- return StreamingResponse(
79
- mp3_fp,
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
- # Check for common errors like invalid language
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 during speech generation: {e}", exc_info=True)
93
- raise HTTPException(status_code=500, detail=f"An unexpected error occurred. Error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
 
 
 
95
 
96
- # --- Health Check Endpoint (Good Practice) ---
97
- @app.get("/health", tags=["System"], summary="Check API Health")
98
  async def health_check():
99
- """
100
- Simple health check endpoint. Returns status ok if the service is running.
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("app:app", host="127.0.0.1", port=8000, reload=True)
 
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)