eshwar06 commited on
Commit
115cdc4
·
verified ·
1 Parent(s): 933d51f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -252
app.py CHANGED
@@ -1,28 +1,21 @@
1
- # app.py - FastAPI + Gradio for Hugging Face Spaces
2
  import os
3
- import asyncio
4
  import logging
5
  import tempfile
6
  import subprocess
7
  from pathlib import Path
8
- from typing import Optional
9
  import requests
10
- import json
11
- import threading
12
- import time
13
-
14
- import gradio as gr
15
- from fastapi import FastAPI, HTTPException
16
- from fastapi.responses import FileResponse
17
- from fastapi.middleware.cors import CORSMiddleware
18
  import uvicorn
 
 
 
19
 
20
  # Configure logging
21
  logging.basicConfig(level=logging.INFO)
22
  logger = logging.getLogger(__name__)
23
 
24
  class PiperTTSSpaces:
25
- """Piper TTS optimized for Hugging Face Spaces with FastAPI"""
26
 
27
  def __init__(self):
28
  self.model_path = self._setup_model()
@@ -75,7 +68,7 @@ class PiperTTSSpaces:
75
  "--output_file", temp_file.name
76
  ]
77
 
78
- logger.info(f"Running: {' '.join(cmd)}")
79
 
80
  # Run Piper with text input
81
  process = subprocess.run(
@@ -124,8 +117,221 @@ app.add_middleware(
124
  allow_headers=["*"],
125
  )
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  @app.post("/api/tts")
128
- async def api_generate_tts(request: dict):
129
  """
130
  Generate TTS from text
131
 
@@ -144,7 +350,6 @@ async def api_generate_tts(request: dict):
144
  raise HTTPException(status_code=503, detail="TTS engine not available")
145
 
146
  try:
147
- logger.info(f"API TTS request: '{text[:50]}...'")
148
  audio_file = tts_engine.synthesize_to_file(text)
149
 
150
  return FileResponse(
@@ -155,7 +360,7 @@ async def api_generate_tts(request: dict):
155
  )
156
 
157
  except Exception as e:
158
- logger.error(f"API TTS failed: {e}")
159
  raise HTTPException(status_code=500, detail=str(e))
160
 
161
  @app.get("/api/health")
@@ -164,245 +369,26 @@ async def health_check():
164
  return {
165
  "status": "healthy" if tts_engine else "tts_engine_unavailable",
166
  "service": "Piper TTS",
167
- "model_loaded": tts_engine is not None
 
168
  }
169
 
170
- @app.get("/")
171
- async def root():
172
- """Root endpoint - redirects to Gradio interface"""
173
- return {"message": "Piper TTS API is running. Visit /docs for API documentation."}
174
 
175
- # Gradio interface functions
176
- def generate_speech(text, progress=gr.Progress()):
177
- """Generate speech from text for Gradio"""
178
- if not text or not text.strip():
179
- return None, "⚠️ Please enter some text to convert to speech."
180
-
181
- if tts_engine is None:
182
- return None, "❌ TTS engine not available. Please check the logs."
183
-
184
- if len(text) > 1000:
185
- return None, "⚠️ Text is too long. Please limit to 1000 characters."
186
-
187
- try:
188
- progress(0.2, desc="Initializing TTS...")
189
-
190
- progress(0.5, desc="Generating speech...")
191
- audio_file = tts_engine.synthesize_to_file(text)
192
-
193
- progress(0.9, desc="Finalizing...")
194
-
195
- progress(1.0, desc="Complete!")
196
- success_msg = f"✅ Generated speech for: '{text[:50]}{'...' if len(text) > 50 else ''}'"
197
- return audio_file, success_msg
198
-
199
- except Exception as e:
200
- error_msg = f"❌ Error generating speech: {str(e)}"
201
- logger.error(error_msg)
202
- return None, error_msg
203
 
204
- def clear_inputs():
205
- """Clear all inputs and outputs"""
206
- return "", None, "Ready for new text..."
207
-
208
- # Create Gradio interface
209
- def create_gradio_interface():
210
- """Create the Gradio interface"""
211
-
212
- css = """
213
- .container { max-width: 900px; margin: auto; }
214
- .header { text-align: center; margin-bottom: 2rem; }
215
- .example-btn { margin: 0.25rem; }
216
- .status-box { border-radius: 8px; }
217
- """
218
-
219
- with gr.Blocks(
220
- theme=gr.themes.Soft(),
221
- title="🎙️ Piper TTS with FastAPI",
222
- css=css
223
- ) as interface:
224
-
225
- gr.Markdown(
226
- """
227
- # 🎙️ Piper TTS with FastAPI Integration
228
-
229
- High-quality neural text-to-speech with **both Gradio UI and FastAPI endpoints**.
230
- Perfect for digital companions and conversational AI applications.
231
-
232
- ✨ **Features:**
233
- - 🌐 **FastAPI Endpoints** for easy integration
234
- - 🎯 **Gradio Interface** for testing
235
- - ⚡ **Low Latency** neural synthesis
236
- - 🔄 **Production Ready** with proper CORS
237
- """,
238
- elem_classes=["header"]
239
- )
240
-
241
- with gr.Row():
242
- with gr.Column(scale=2):
243
- text_input = gr.Textbox(
244
- label="💬 Text to Convert",
245
- placeholder="Enter the text you want to convert to speech...",
246
- lines=4,
247
- max_lines=8,
248
- value="Hello! I'm your digital companion with FastAPI integration."
249
- )
250
-
251
- with gr.Row():
252
- generate_btn = gr.Button("🎵 Generate Speech", variant="primary", size="lg")
253
- clear_btn = gr.Button("🗑️ Clear", variant="secondary")
254
-
255
- with gr.Column(scale=1):
256
- gr.Markdown("### 📝 Quick Examples")
257
-
258
- examples = [
259
- "Hello! How can I help you today?",
260
- "I'm excited to assist with your questions.",
261
- "Thank you for using our TTS service!",
262
- "The API is working perfectly!",
263
- "I understand what you're asking about.",
264
- "Let me process that information."
265
- ]
266
-
267
- for example in examples:
268
- label = f"{example[:35]}{'...' if len(example) > 35 else ''}"
269
- example_btn = gr.Button(
270
- label,
271
- size="sm",
272
- elem_classes=["example-btn"]
273
- )
274
- example_btn.click(lambda x=example: x, outputs=text_input)
275
-
276
-
277
- # Output section
278
- with gr.Row():
279
- audio_output = gr.Audio(
280
- label="🔊 Generated Speech",
281
- type="filepath",
282
- autoplay=True,
283
- show_download_button=True
284
- )
285
-
286
- status_output = gr.Textbox(
287
- label="📊 Status",
288
- interactive=False,
289
- value="Ready! Use either the interface above OR the FastAPI endpoints.",
290
- elem_classes=["status-box"]
291
- )
292
-
293
- # API Documentation
294
- with gr.Accordion("🚀 FastAPI Integration", open=True):
295
- gr.Markdown(
296
- """
297
- ### 🌐 REST API Endpoints
298
-
299
- **Your Space provides these FastAPI endpoints:**
300
-
301
- #### Generate TTS
302
- ```bash
303
- curl -X POST "https://eshwar06-piper-tts-server.hf.space/api/tts" \\
304
- -H "Content-Type: application/json" \\
305
- -d '{"text": "Hello from FastAPI!"}' \\
306
- --output speech.wav
307
- ```
308
-
309
- #### Health Check
310
- ```bash
311
- curl "https://eshwar06-piper-tts-server.hf.space/api/health"
312
- ```
313
-
314
- #### Python Usage
315
- ```python
316
- import requests
317
-
318
- # Generate TTS
319
- response = requests.post(
320
- "https://eshwar06-piper-tts-server.hf.space/api/tts",
321
- json={"text": "Hello from Python!"}
322
- )
323
-
324
- # Save audio
325
- with open("speech.wav", "wb") as f:
326
- f.write(response.content)
327
- ```
328
-
329
- #### For Your Digital Companion
330
- ```python
331
- import requests
332
-
333
- class DigitalCompanion:
334
- def __init__(self):
335
- self.tts_url = "https://eshwar06-piper-tts-server.hf.space/api/tts"
336
-
337
- def speak(self, text):
338
- response = requests.post(
339
- self.tts_url,
340
- json={"text": text}
341
- )
342
- return response.content # WAV audio bytes
343
-
344
- # Usage
345
- companion = DigitalCompanion()
346
- audio_data = companion.speak("I'm your AI assistant!")
347
- ```
348
-
349
- ### 📋 API Documentation
350
- Visit: `https://eshwar06-piper-tts-server.hf.space/docs` for interactive API docs!
351
- """
352
- )
353
-
354
- # Event handlers
355
- generate_btn.click(
356
- fn=generate_speech,
357
- inputs=[text_input],
358
- outputs=[audio_output, status_output],
359
- show_progress="full"
360
- )
361
-
362
- clear_btn.click(
363
- fn=clear_inputs,
364
- outputs=[text_input, audio_output, status_output]
365
- )
366
-
367
- text_input.submit(
368
- fn=generate_speech,
369
- inputs=[text_input],
370
- outputs=[audio_output, status_output],
371
- show_progress="full"
372
- )
373
-
374
- gr.Markdown(
375
- """
376
- ---
377
- 🌐 **FastAPI Integration Ready!** Use `/api/tts` for your applications.
378
- 📚 **API Docs:** Visit `/docs` for interactive documentation.
379
- ⭐ **Perfect for Digital Companions** with low-latency TTS.
380
- """,
381
- elem_classes=["header"]
382
- )
383
-
384
- return interface
385
-
386
- # Create Gradio interface
387
- gradio_app = create_gradio_interface()
388
-
389
- def run_fastapi():
390
- """Run FastAPI server in a separate thread"""
391
- uvicorn.run(app, host="0.0.0.0", port=7861, log_level="warning")
392
-
393
- # Start FastAPI in background thread
394
  if __name__ == "__main__":
395
- # Start FastAPI server in background
396
- fastapi_thread = threading.Thread(target=run_fastapi, daemon=True)
397
- fastapi_thread.start()
398
-
399
- # Give FastAPI time to start
400
- time.sleep(2)
401
-
402
- # Launch Gradio (main process)
403
- gradio_app.launch(
404
- server_name="0.0.0.0",
405
- server_port=7860,
406
- show_error=True,
407
- share=False
408
  )
 
1
+ # app.py - Simple FastAPI-only version for Hugging Face Spaces
2
  import os
 
3
  import logging
4
  import tempfile
5
  import subprocess
6
  from pathlib import Path
 
7
  import requests
 
 
 
 
 
 
 
 
8
  import uvicorn
9
+ from fastapi import FastAPI, HTTPException, Request
10
+ from fastapi.responses import FileResponse, HTMLResponse
11
+ from fastapi.middleware.cors import CORSMiddleware
12
 
13
  # Configure logging
14
  logging.basicConfig(level=logging.INFO)
15
  logger = logging.getLogger(__name__)
16
 
17
  class PiperTTSSpaces:
18
+ """Piper TTS for Hugging Face Spaces with FastAPI"""
19
 
20
  def __init__(self):
21
  self.model_path = self._setup_model()
 
68
  "--output_file", temp_file.name
69
  ]
70
 
71
+ logger.info(f"TTS request: '{text[:50]}...'")
72
 
73
  # Run Piper with text input
74
  process = subprocess.run(
 
117
  allow_headers=["*"],
118
  )
119
 
120
+ @app.get("/", response_class=HTMLResponse)
121
+ async def root():
122
+ """Serve a simple HTML interface"""
123
+ return HTMLResponse("""
124
+ <!DOCTYPE html>
125
+ <html>
126
+ <head>
127
+ <title>🎙️ Piper TTS API</title>
128
+ <meta charset="UTF-8">
129
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
130
+ <style>
131
+ body {
132
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
133
+ max-width: 800px;
134
+ margin: 0 auto;
135
+ padding: 20px;
136
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
137
+ min-height: 100vh;
138
+ color: white;
139
+ }
140
+ .container {
141
+ background: rgba(255, 255, 255, 0.1);
142
+ padding: 30px;
143
+ border-radius: 15px;
144
+ backdrop-filter: blur(10px);
145
+ box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
146
+ }
147
+ .header { text-align: center; margin-bottom: 30px; }
148
+ .form-group { margin: 20px 0; }
149
+ label { display: block; margin-bottom: 8px; font-weight: bold; }
150
+ textarea {
151
+ width: 100%;
152
+ padding: 12px;
153
+ border: none;
154
+ border-radius: 8px;
155
+ font-size: 16px;
156
+ background: rgba(255, 255, 255, 0.9);
157
+ color: #333;
158
+ resize: vertical;
159
+ }
160
+ button {
161
+ background: #4CAF50;
162
+ color: white;
163
+ padding: 12px 24px;
164
+ border: none;
165
+ border-radius: 8px;
166
+ font-size: 16px;
167
+ cursor: pointer;
168
+ margin: 5px;
169
+ }
170
+ button:hover { background: #45a049; }
171
+ button:disabled { background: #cccccc; cursor: not-allowed; }
172
+ .status {
173
+ margin: 15px 0;
174
+ padding: 10px;
175
+ border-radius: 5px;
176
+ background: rgba(255, 255, 255, 0.1);
177
+ }
178
+ .success { background: rgba(76, 175, 80, 0.3); }
179
+ .error { background: rgba(244, 67, 54, 0.3); }
180
+ audio { width: 100%; margin: 10px 0; }
181
+ .examples {
182
+ display: grid;
183
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
184
+ gap: 10px;
185
+ margin: 20px 0;
186
+ }
187
+ .example-btn {
188
+ background: rgba(255, 255, 255, 0.2);
189
+ padding: 8px 12px;
190
+ font-size: 14px;
191
+ }
192
+ .api-info {
193
+ background: rgba(0, 0, 0, 0.2);
194
+ padding: 20px;
195
+ border-radius: 10px;
196
+ margin: 20px 0;
197
+ }
198
+ code {
199
+ background: rgba(0, 0, 0, 0.3);
200
+ padding: 2px 6px;
201
+ border-radius: 4px;
202
+ font-family: monospace;
203
+ }
204
+ </style>
205
+ </head>
206
+ <body>
207
+ <div class="container">
208
+ <div class="header">
209
+ <h1>🎙️ Piper TTS API</h1>
210
+ <p>High-quality neural text-to-speech for digital companions</p>
211
+ </div>
212
+
213
+ <div class="form-group">
214
+ <label for="textInput">💬 Enter text to convert to speech:</label>
215
+ <textarea id="textInput" rows="4" placeholder="Hello! I'm your digital companion powered by Piper TTS.">Hello! I'm your digital companion powered by Piper TTS. I can generate natural-sounding speech perfect for conversational AI applications.</textarea>
216
+ </div>
217
+
218
+ <div class="form-group">
219
+ <button onclick="generateSpeech()" id="generateBtn">🎵 Generate Speech</button>
220
+ <button onclick="clearAll()">🗑️ Clear</button>
221
+ </div>
222
+
223
+ <div class="examples">
224
+ <button class="example-btn" onclick="setExample('Hello! How can I help you today?')">Greeting</button>
225
+ <button class="example-btn" onclick="setExample('I understand your question perfectly.')">Understanding</button>
226
+ <button class="example-btn" onclick="setExample('Let me think about that for a moment.')">Thinking</button>
227
+ <button class="example-btn" onclick="setExample('Thank you for using our service!')">Thanks</button>
228
+ <button class="example-btn" onclick="setExample('Is there anything else you need help with?')">Follow-up</button>
229
+ <button class="example-btn" onclick="setExample('I apologize for any confusion.')">Apology</button>
230
+ </div>
231
+
232
+ <div id="status" class="status" style="display: none;"></div>
233
+ <audio id="audioPlayer" controls style="display: none;"></audio>
234
+
235
+ <div class="api-info">
236
+ <h3>🚀 API Integration</h3>
237
+ <p><strong>Endpoint:</strong> <code>POST /api/tts</code></p>
238
+ <p><strong>Body:</strong> <code>{"text": "Your text here"}</code></p>
239
+ <p><strong>Response:</strong> WAV audio file</p>
240
+
241
+ <h4>Python Example:</h4>
242
+ <pre><code>import requests
243
+
244
+ response = requests.post(
245
+ "https://eshwar06-piper-tts-server.hf.space/api/tts",
246
+ json={"text": "Hello from Python!"}
247
+ )
248
+
249
+ with open("speech.wav", "wb") as f:
250
+ f.write(response.content)</code></pre>
251
+
252
+ <h4>cURL Example:</h4>
253
+ <pre><code>curl -X POST "https://eshwar06-piper-tts-server.hf.space/api/tts" \\
254
+ -H "Content-Type: application/json" \\
255
+ -d '{"text":"Hello world!"}' \\
256
+ --output speech.wav</code></pre>
257
+ </div>
258
+ </div>
259
+
260
+ <script>
261
+ function setExample(text) {
262
+ document.getElementById('textInput').value = text;
263
+ }
264
+
265
+ function clearAll() {
266
+ document.getElementById('textInput').value = '';
267
+ document.getElementById('status').style.display = 'none';
268
+ document.getElementById('audioPlayer').style.display = 'none';
269
+ }
270
+
271
+ async function generateSpeech() {
272
+ const text = document.getElementById('textInput').value;
273
+ const statusDiv = document.getElementById('status');
274
+ const audioPlayer = document.getElementById('audioPlayer');
275
+ const generateBtn = document.getElementById('generateBtn');
276
+
277
+ if (!text.trim()) {
278
+ showStatus('Please enter some text', 'error');
279
+ return;
280
+ }
281
+
282
+ generateBtn.disabled = true;
283
+ generateBtn.textContent = '⏳ Generating...';
284
+ showStatus('Generating speech, please wait...', 'info');
285
+
286
+ try {
287
+ const response = await fetch('/api/tts', {
288
+ method: 'POST',
289
+ headers: {
290
+ 'Content-Type': 'application/json',
291
+ },
292
+ body: JSON.stringify({text: text})
293
+ });
294
+
295
+ if (response.ok) {
296
+ const audioBlob = await response.blob();
297
+ const audioUrl = URL.createObjectURL(audioBlob);
298
+
299
+ audioPlayer.src = audioUrl;
300
+ audioPlayer.style.display = 'block';
301
+
302
+ showStatus('✅ Speech generated successfully! Click play below.', 'success');
303
+ } else {
304
+ const errorText = await response.text();
305
+ showStatus(`❌ Error: ${response.status} - ${errorText}`, 'error');
306
+ }
307
+ } catch (error) {
308
+ showStatus(`❌ Error: ${error.message}`, 'error');
309
+ } finally {
310
+ generateBtn.disabled = false;
311
+ generateBtn.textContent = '🎵 Generate Speech';
312
+ }
313
+ }
314
+
315
+ function showStatus(message, type) {
316
+ const statusDiv = document.getElementById('status');
317
+ statusDiv.textContent = message;
318
+ statusDiv.className = `status ${type}`;
319
+ statusDiv.style.display = 'block';
320
+ }
321
+
322
+ // Allow Enter key to generate speech
323
+ document.getElementById('textInput').addEventListener('keypress', function(e) {
324
+ if (e.key === 'Enter' && e.ctrlKey) {
325
+ generateSpeech();
326
+ }
327
+ });
328
+ </script>
329
+ </body>
330
+ </html>
331
+ """)
332
+
333
  @app.post("/api/tts")
334
+ async def generate_tts(request: dict):
335
  """
336
  Generate TTS from text
337
 
 
350
  raise HTTPException(status_code=503, detail="TTS engine not available")
351
 
352
  try:
 
353
  audio_file = tts_engine.synthesize_to_file(text)
354
 
355
  return FileResponse(
 
360
  )
361
 
362
  except Exception as e:
363
+ logger.error(f"TTS failed: {e}")
364
  raise HTTPException(status_code=500, detail=str(e))
365
 
366
  @app.get("/api/health")
 
369
  return {
370
  "status": "healthy" if tts_engine else "tts_engine_unavailable",
371
  "service": "Piper TTS",
372
+ "model_loaded": tts_engine is not None,
373
+ "version": "1.0.0"
374
  }
375
 
376
+ @app.get("/docs-redirect")
377
+ async def docs_redirect():
378
+ """Redirect to API docs"""
379
+ return {"message": "Visit /docs for interactive API documentation"}
380
 
381
+ # Health check for Spaces
382
+ @app.get("/health")
383
+ async def health_alias():
384
+ """Alternative health endpoint"""
385
+ return await health_check()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  if __name__ == "__main__":
388
+ # Run the FastAPI app
389
+ uvicorn.run(
390
+ app,
391
+ host="0.0.0.0",
392
+ port=7860, # Spaces default port
393
+ log_level="info"
 
 
 
 
 
 
 
394
  )