import os import io import traceback import numpy as np import scipy.io.wavfile as wavfile # Используем scipy вместо soundfile 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("Модель успешно загружена и готова к работе!") @app.get("/") async def root(): return { "status": "ok", "message": "Supertonic TTS API is running", "docs": "/docs", "usage": "POST /api/tts с JSON: {'text': 'ваш текст', 'lang': 'ru', 'voice': 'M2'}" } @app.post("/api/tts") 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) # Сначала приводим к float32 для безопасной нормализации 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. Возвращаем аудио return Response( content=audio_bytes, media_type='audio/wav', headers={ "Content-Disposition": "attachment; filename=speech.wav", "X-Audio-Duration": str(round(duration, 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)