Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import traceback | |
| import numpy as np | |
| import scipy.io.wavfile as wavfile | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import Response | |
| from pydantic import BaseModel | |
| from supertonic import TTS | |
| import uvicorn | |
| app = FastAPI(title="Supertonic TTS API") | |
| class TTSRequest(BaseModel): | |
| text: str | |
| lang: str = "ru" | |
| voice: str = "M2" | |
| print("Загрузка модели Supertonic TTS...") | |
| tts = TTS(auto_download=True) | |
| default_style = tts.get_voice_style(voice_name="M2") | |
| print("Модель успешно загружена и готова к работе!") | |
| async def root(): | |
| return { | |
| "status": "ok", | |
| "message": "Supertonic TTS API is running", | |
| "docs": "/docs", | |
| "usage": "POST /api/tts с JSON: {'text': 'ваш текст', 'lang': 'ru', 'voice': 'M2'}" | |
| } | |
| async def synthesize(request: TTSRequest): | |
| try: | |
| # 1. Получаем стиль голоса | |
| if request.voice == "M2": | |
| style = default_style | |
| else: | |
| style = tts.get_voice_style(voice_name=request.voice) | |
| # 2. Синтез | |
| wav, duration = tts.synthesize(request.text, voice_style=style, lang=request.lang) | |
| # 3. Конвертация аудио в numpy (если модель вернула тензор PyTorch) | |
| if hasattr(wav, 'cpu'): | |
| wav = wav.cpu().numpy() | |
| elif hasattr(wav, 'numpy'): | |
| wav = wav.numpy() | |
| wav = np.asarray(wav) | |
| # 4. Убираем лишние измерения (например, если форма (1, 48000) -> (48000,)) | |
| wav = wav.squeeze() | |
| # 5. Нормализация и конвертация в int16 (стандарт для WAV) | |
| wav = wav.astype(np.float32) | |
| max_val = np.max(np.abs(wav)) | |
| if max_val > 1.0: | |
| wav = wav / max_val | |
| # Конвертируем в int16 (от -32768 до 32767) | |
| wav_int16 = (wav * 32767).astype(np.int16) | |
| # 6. Получаем sample rate | |
| sample_rate = getattr(tts, 'sample_rate', 24000) | |
| # 7. Записываем в память через scipy | |
| out = io.BytesIO() | |
| wavfile.write(out, sample_rate, wav_int16) | |
| audio_bytes = out.getvalue() | |
| # 8. ИСПРАВЛЕНИЕ: Превращаем duration из numpy массива в обычный float | |
| # .item() безопасно извлекает скалярное значение из numpy array | |
| duration_float = float(np.asarray(duration).item()) | |
| # 9. Возвращаем аудио | |
| return Response( | |
| content=audio_bytes, | |
| media_type='audio/wav', | |
| headers={ | |
| "Content-Disposition": "attachment; filename=speech.wav", | |
| "X-Audio-Duration": str(round(duration_float, 2)) | |
| } | |
| ) | |
| except Exception as e: | |
| traceback.print_exc() | |
| raise HTTPException(status_code=500, detail=f"Ошибка генерации: {str(e)}") | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 7860)) | |
| uvicorn.run(app, host='0.0.0.0', port=port) |