59856bd4fc
Services: Pipecat agent (LiveKit + Claude + Whisper STT + Piper TTS), SpeechBrain analysis service, Piper TTS service, web client (nginx). Includes LiveKit server config and docker-compose orchestration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
1.9 KiB
Python
Executable File
67 lines
1.9 KiB
Python
Executable File
"""
|
|
FastAPI service wrapping VocalAnalyzer.
|
|
Internal-only -- not exposed via Traefik.
|
|
"""
|
|
import logging
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI, UploadFile, File, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
from dataclasses import asdict
|
|
|
|
from analyzer import VocalAnalyzer
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
MODEL_DIR = os.getenv("MODEL_DIR", "/app/models")
|
|
analyzer: VocalAnalyzer = None
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
global analyzer
|
|
logger.info("Starting VocalAnalyzer...")
|
|
analyzer = VocalAnalyzer(model_dir=MODEL_DIR)
|
|
logger.info("VocalAnalyzer ready.")
|
|
yield
|
|
logger.info("Shutting down.")
|
|
|
|
|
|
app = FastAPI(title="SpeechBrain Vocal Analysis Service", lifespan=lifespan)
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
if analyzer is None:
|
|
raise HTTPException(status_code=503, detail="Model not loaded")
|
|
return {"status": "ok", "model": "emotion-recognition-wav2vec2-IEMOCAP"}
|
|
|
|
|
|
@app.post("/analyze")
|
|
async def analyze(audio: UploadFile = File(...)):
|
|
if analyzer is None:
|
|
raise HTTPException(status_code=503, detail="Model not loaded")
|
|
|
|
content = await audio.read()
|
|
if len(content) == 0:
|
|
raise HTTPException(status_code=400, detail="Empty audio file")
|
|
|
|
result = analyzer.analyze(content, content_type=audio.content_type or "audio/wav")
|
|
return JSONResponse(content=asdict(result))
|
|
|
|
|
|
@app.post("/calibrate")
|
|
async def calibrate(audio: UploadFile = File(...)):
|
|
if analyzer is None:
|
|
raise HTTPException(status_code=503, detail="Model not loaded")
|
|
|
|
content = await audio.read()
|
|
if len(content) == 0:
|
|
raise HTTPException(status_code=400, detail="Empty audio file")
|
|
|
|
result = analyzer.analyze(content, content_type=audio.content_type or "audio/wav")
|
|
result_dict = asdict(result)
|
|
result_dict["is_baseline"] = True
|
|
return JSONResponse(content=result_dict)
|