pocket_tts_cli/server/
mod.rs

1//! HTTP API Server
2//!
3//! Axum-based server providing TTS generation endpoints.
4
5use anyhow::Result;
6use pocket_tts::TTSModel;
7
8use crate::commands::serve::{ServeArgs, print_endpoints};
9use crate::voice::resolve_voice;
10
11pub mod handlers;
12pub mod routes;
13pub mod state;
14
15pub async fn start_server(args: ServeArgs) -> Result<()> {
16    // Initialize tracing
17    let _ = tracing_subscriber::fmt::try_init();
18
19    // Load model with configured parameters
20    let model = if args.quantized {
21        #[cfg(feature = "quantized")]
22        {
23            TTSModel::load_quantized_with_params(
24                &args.variant,
25                args.temperature,
26                args.lsd_decode_steps,
27                args.eos_threshold,
28            )?
29        }
30        #[cfg(not(feature = "quantized"))]
31        {
32            anyhow::bail!("Quantization feature not enabled. Rebuild with --features quantized");
33        }
34    } else {
35        TTSModel::load_with_params(
36            &args.variant,
37            args.temperature,
38            args.lsd_decode_steps,
39            args.eos_threshold,
40        )?
41    };
42
43    println!("  ✓ Model loaded (sample rate: {}Hz)", model.sample_rate);
44
45    // Pre-load default voice
46    println!("  Loading default voice: {}...", args.voice);
47    let default_voice_state = resolve_voice(&model, Some(&args.voice))?;
48    println!("  ✓ Default voice ready");
49
50    let state = state::AppState::new(model, default_voice_state);
51    let app = routes::create_router(state);
52
53    let addr = format!("{}:{}", args.host, args.port);
54
55    print_endpoints(&args.host, args.port);
56
57    let listener = tokio::net::TcpListener::bind(&addr).await?;
58
59    axum::serve(listener, app)
60        .with_graceful_shutdown(shutdown_signal())
61        .await?;
62
63    println!("  👋 Server stopped gracefully");
64
65    Ok(())
66}
67
68/// Wait for Ctrl+C or SIGTERM signal
69async fn shutdown_signal() {
70    let ctrl_c = async {
71        tokio::signal::ctrl_c()
72            .await
73            .expect("failed to install Ctrl+C handler");
74    };
75
76    #[cfg(unix)]
77    let terminate = async {
78        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
79            .expect("failed to install signal handler")
80            .recv()
81            .await;
82    };
83
84    #[cfg(not(unix))]
85    let terminate = std::future::pending::<()>();
86
87    tokio::select! {
88        _ = ctrl_c => {
89            println!("\n  ⚠️  Received Ctrl+C, shutting down...");
90        },
91        _ = terminate => {
92            println!("\n  ⚠️  Received SIGTERM, shutting down...");
93        },
94    }
95}