- Replace Whisper subprocess calls with direct qwen3_asr_toolkit API - Remove subprocess dependencies, use pure Python API - Update to use DASHSCOPE_API_KEY instead of WHISPER_MODEL - Cleaner code without CLI calls - Better Ukrainian language recognition quality
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
"""
|
|
STT Service (Speech-to-Text) для DAGI Router
|
|
Використовує qwen3_asr_toolkit для розпізнавання голосу
|
|
"""
|
|
|
|
import os
|
|
import uuid
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from fastapi import FastAPI, UploadFile, File, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from pydantic import BaseModel
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = FastAPI(
|
|
title="STT Service",
|
|
description="Speech-to-Text service using Qwen3 ASR Toolkit",
|
|
version="2.0.0"
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Configuration
|
|
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
|
|
TEMP_DIR = Path("/tmp/stt")
|
|
TEMP_DIR.mkdir(exist_ok=True)
|
|
|
|
# Initialize Qwen3 ASR Toolkit
|
|
try:
|
|
from qwen3_asr_toolkit import transcribe_audio
|
|
ASR_AVAILABLE = True
|
|
logger.info("qwen3_asr_toolkit loaded successfully")
|
|
except ImportError:
|
|
ASR_AVAILABLE = False
|
|
logger.warning("qwen3_asr_toolkit not available, install with: pip install qwen3-asr-toolkit")
|
|
|
|
|
|
class STTResponse(BaseModel):
|
|
text: str
|
|
language: Optional[str] = None
|
|
duration: Optional[float] = None
|
|
|
|
|
|
def transcribe_with_qwen(audio_path: str) -> tuple[str, Optional[str], Optional[float]]:
|
|
"""
|
|
Розпізнати мову з аудіо файлу через qwen3_asr_toolkit
|
|
Повертає (text, language, duration)
|
|
"""
|
|
if not ASR_AVAILABLE:
|
|
raise ImportError("qwen3_asr_toolkit not installed. Install with: pip install qwen3-asr-toolkit")
|
|
|
|
if not DASHSCOPE_API_KEY:
|
|
raise ValueError("DASHSCOPE_API_KEY environment variable not set")
|
|
|
|
try:
|
|
# qwen3_asr_toolkit автоматично обробляє різні формати аудіо
|
|
# та виконує необхідні конвертації
|
|
transcript = transcribe_audio(audio_path)
|
|
|
|
# transcribe_audio повертає текст
|
|
# Можна також отримати додаткову інформацію, якщо API підтримує
|
|
text = transcript.strip() if isinstance(transcript, str) else str(transcript).strip()
|
|
|
|
# Для української мови встановлюємо language="uk"
|
|
# qwen3_asr_toolkit може автоматично визначати мову
|
|
language = "uk" # Можна змінити на автоматичне визначення
|
|
|
|
# Duration можна отримати з аудіо файлу, якщо потрібно
|
|
# Поки що повертаємо None
|
|
duration = None
|
|
|
|
return text, language, duration
|
|
|
|
except Exception as e:
|
|
logger.error(f"Qwen3 ASR transcription failed: {e}", exc_info=True)
|
|
raise
|
|
|
|
|
|
@app.post("/stt", response_model=STTResponse)
|
|
async def stt(file: UploadFile = File(...)):
|
|
"""
|
|
Розпізнати мову з аудіо файлу через qwen3_asr_toolkit
|
|
|
|
Підтримує формати: ogg, mp3, wav, m4a, webm, flac
|
|
qwen3_asr_toolkit автоматично обробляє конвертацію
|
|
"""
|
|
if not ASR_AVAILABLE:
|
|
raise HTTPException(
|
|
status_code=503,
|
|
detail="qwen3_asr_toolkit not available. Install with: pip install qwen3-asr-toolkit"
|
|
)
|
|
|
|
if not DASHSCOPE_API_KEY:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="DASHSCOPE_API_KEY not configured"
|
|
)
|
|
|
|
tmp_id = str(uuid.uuid4())
|
|
# Визначаємо розширення файлу
|
|
file_ext = "ogg"
|
|
if file.filename and "." in file.filename:
|
|
file_ext = file.filename.split(".")[-1].lower()
|
|
|
|
tmp_input = TEMP_DIR / f"{tmp_id}.{file_ext}"
|
|
|
|
try:
|
|
# Зберігаємо вхідний файл
|
|
content = await file.read()
|
|
tmp_input.write_bytes(content)
|
|
logger.info(f"Received audio file: {file.filename}, size: {len(content)} bytes, format: {file_ext}")
|
|
|
|
# qwen3_asr_toolkit автоматично обробляє різні формати
|
|
# та виконує необхідні конвертації всередині
|
|
text, language, duration = transcribe_with_qwen(str(tmp_input))
|
|
|
|
logger.info(f"Transcribed: {text[:100]}... (lang: {language})")
|
|
|
|
return STTResponse(
|
|
text=text,
|
|
language=language,
|
|
duration=duration
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
logger.error(f"STT configuration error: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
except ImportError as e:
|
|
logger.error(f"STT import error: {e}")
|
|
raise HTTPException(status_code=503, detail=str(e))
|
|
except Exception as e:
|
|
logger.error(f"STT error: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=f"STT failed: {str(e)}")
|
|
finally:
|
|
# Очищаємо тимчасові файли
|
|
if tmp_input.exists():
|
|
try:
|
|
tmp_input.unlink()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to delete temp file {tmp_input}: {e}")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
"""Health check"""
|
|
return {
|
|
"status": "ok" if ASR_AVAILABLE else "degraded",
|
|
"service": "stt-service",
|
|
"engine": "qwen3_asr_toolkit",
|
|
"asr_available": ASR_AVAILABLE,
|
|
"api_key_configured": DASHSCOPE_API_KEY is not None
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=9000)
|
|
|