voice-server/app/config.py
Cal Corum a34aec06f1 Initial commit: Voice server with Piper TTS
A local HTTP service that accepts text via POST and speaks it through
system speakers using Piper TTS neural voice synthesis.

Features:
- POST /notify - Queue text for TTS playback
- GET /health - Health check with TTS/audio/queue status
- GET /voices - List installed voice models
- Async queue processing (no overlapping audio)
- Non-blocking audio via sounddevice
- 73 tests covering API contract

Tech stack:
- FastAPI + Uvicorn
- Piper TTS (neural voices, offline)
- sounddevice (PortAudio)
- Pydantic for validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 00:18:12 -06:00

99 lines
2.6 KiB
Python

"""
Configuration management for voice-server.
Loads configuration from environment variables with sensible defaults.
Uses pydantic-settings for type-safe configuration loading and validation.
"""
from functools import lru_cache
from typing import Annotated, Literal
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
# Valid log levels
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
class Settings(BaseSettings):
"""
Application settings loaded from environment variables.
All settings have sensible defaults and can be overridden via environment
variables or a .env file.
"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# Server settings
host: Annotated[str, Field(default="0.0.0.0", description="Host to bind to")]
port: Annotated[
int,
Field(default=8888, ge=1, le=65535, description="Port to listen on"),
]
# TTS settings
model_dir: Annotated[
str,
Field(default="./models", description="Directory containing voice models"),
]
default_voice: Annotated[
str,
Field(default="en_US-lessac-medium", description="Default voice model"),
]
default_rate: Annotated[
int,
Field(default=170, ge=50, le=400, description="Default speech rate (WPM)"),
]
# Queue settings
queue_max_size: Annotated[
int,
Field(default=50, gt=0, description="Maximum TTS queue size"),
]
request_timeout_seconds: Annotated[
int,
Field(default=60, gt=0, description="Request processing timeout"),
]
# Logging
log_level: Annotated[
LogLevel,
Field(default="INFO", description="Logging level"),
]
log_file: Annotated[
str | None,
Field(default=None, description="Log file path (None for stdout only)"),
]
# Debug
voice_enabled: Annotated[
bool,
Field(default=True, description="Enable/disable TTS playback"),
]
@field_validator("log_level", mode="before")
@classmethod
def uppercase_log_level(cls, v: str) -> str:
"""Ensure log level is uppercase."""
if isinstance(v, str):
return v.upper()
return v
@lru_cache
def get_settings() -> Settings:
"""
Get cached application settings.
Returns the same Settings instance on subsequent calls for efficiency.
The cache can be cleared by calling get_settings.cache_clear().
"""
return Settings()