from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from pathlib import Path import asyncio import contextlib import logging import time from app.routers import speakers, dialog # Import the routers from app import config app = FastAPI( title="Chatterbox TTS API", description="API for generating TTS dialogs using Chatterbox TTS.", version="0.1.0", ) # CORS Middleware configuration # For development, we'll allow all origins # In production, you should restrict this to specific origins app.add_middleware( CORSMiddleware, allow_origins=config.CORS_ORIGINS, allow_credentials=False, allow_methods=["*"], allow_headers=["*"], expose_headers=["*"] ) # Include routers app.include_router(speakers.router, prefix="/api/speakers", tags=["Speakers"]) app.include_router(dialog.router, prefix="/api/dialog", tags=["Dialog Generation"]) @app.get("/") async def read_root(): return {"message": "Welcome to the Chatterbox TTS API!"} # Ensure the directory for serving generated audio exists config.DIALOG_GENERATED_DIR.mkdir(parents=True, exist_ok=True) # Mount StaticFiles to serve generated dialogs app.mount("/generated_audio", StaticFiles(directory=config.DIALOG_GENERATED_DIR), name="generated_audio") # Further endpoints for speakers, dialog generation, etc., will be added here. # --- Background task: idle model reaper --- logger = logging.getLogger("app.model_reaper") @app.on_event("startup") async def _start_model_reaper(): from app.services.model_manager import ModelManager async def reaper(): while True: try: await asyncio.sleep(config.MODEL_IDLE_CHECK_INTERVAL_SECONDS) if not getattr(config, "MODEL_EVICTION_ENABLED", True): continue timeout = getattr(config, "MODEL_IDLE_TIMEOUT_SECONDS", 0) if timeout <= 0: continue m = ModelManager.instance() if m.is_loaded() and m.active() == 0 and (time.time() - m.last_used()) >= timeout: logger.info("Idle timeout reached (%.0fs). Unloading model...", timeout) await m.unload() except asyncio.CancelledError: break except Exception: logger.exception("Model reaper encountered an error") app.state._model_reaper_task = asyncio.create_task(reaper()) @app.on_event("shutdown") async def _stop_model_reaper(): task = getattr(app.state, "_model_reaper_task", None) if task: task.cancel() with contextlib.suppress(Exception): await task