Phase 1 Complete - Standalone Desktop Application Features: - Real-time speech-to-text with Whisper (faster-whisper) - PySide6 desktop GUI with settings dialog - Web server for OBS browser source integration - Audio capture with automatic sample rate detection and resampling - Noise suppression with Voice Activity Detection (VAD) - Configurable display settings (font, timestamps, fade duration) - Settings apply without restart (with automatic model reloading) - Auto-fade for web display transcriptions - CPU/GPU support with automatic device detection - Standalone executable builds (PyInstaller) - CUDA build support (works on systems without CUDA hardware) Components: - Audio capture with sounddevice - Noise reduction with noisereduce + webrtcvad - Transcription with faster-whisper - GUI with PySide6 - Web server with FastAPI + WebSocket - Configuration system with YAML Build System: - Standard builds (CPU-only): build.sh / build.bat - CUDA builds (universal): build-cuda.sh / build-cuda.bat - Comprehensive BUILD.md documentation - Cross-platform support (Linux, Windows) Documentation: - README.md with project overview and quick start - BUILD.md with detailed build instructions - NEXT_STEPS.md with future enhancement roadmap - INSTALL.md with setup instructions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
142 lines
4.2 KiB
Python
142 lines
4.2 KiB
Python
"""Configuration management for the local transcription application."""
|
|
|
|
import os
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
class Config:
|
|
"""Manages application configuration with YAML file storage."""
|
|
|
|
def __init__(self, config_path: Optional[str] = None):
|
|
"""
|
|
Initialize configuration.
|
|
|
|
Args:
|
|
config_path: Path to configuration file. If None, uses default location.
|
|
"""
|
|
self.app_dir = Path.home() / ".local-transcription"
|
|
self.app_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
if config_path is None:
|
|
self.config_path = self.app_dir / "config.yaml"
|
|
else:
|
|
self.config_path = Path(config_path)
|
|
|
|
self.config: Dict[str, Any] = {}
|
|
self.load()
|
|
|
|
def load(self) -> None:
|
|
"""Load configuration from file or create default if not exists."""
|
|
if self.config_path.exists():
|
|
with open(self.config_path, 'r') as f:
|
|
self.config = yaml.safe_load(f) or {}
|
|
else:
|
|
# Load default configuration
|
|
default_config_path = Path(__file__).parent.parent / "config" / "default_config.yaml"
|
|
if default_config_path.exists():
|
|
with open(default_config_path, 'r') as f:
|
|
self.config = yaml.safe_load(f) or {}
|
|
else:
|
|
self.config = self._get_default_config()
|
|
|
|
# Save the default configuration
|
|
self.save()
|
|
|
|
def save(self) -> None:
|
|
"""Save current configuration to file."""
|
|
with open(self.config_path, 'w') as f:
|
|
yaml.dump(self.config, f, default_flow_style=False, indent=2)
|
|
|
|
def get(self, key_path: str, default: Any = None) -> Any:
|
|
"""
|
|
Get configuration value using dot notation.
|
|
|
|
Args:
|
|
key_path: Dot-separated path to config value (e.g., "audio.sample_rate")
|
|
default: Default value if key not found
|
|
|
|
Returns:
|
|
Configuration value or default
|
|
"""
|
|
keys = key_path.split('.')
|
|
value = self.config
|
|
|
|
for key in keys:
|
|
if isinstance(value, dict) and key in value:
|
|
value = value[key]
|
|
else:
|
|
return default
|
|
|
|
return value
|
|
|
|
def set(self, key_path: str, value: Any) -> None:
|
|
"""
|
|
Set configuration value using dot notation.
|
|
|
|
Args:
|
|
key_path: Dot-separated path to config value (e.g., "audio.sample_rate")
|
|
value: Value to set
|
|
"""
|
|
keys = key_path.split('.')
|
|
config = self.config
|
|
|
|
# Navigate to the parent dict
|
|
for key in keys[:-1]:
|
|
if key not in config:
|
|
config[key] = {}
|
|
config = config[key]
|
|
|
|
# Set the value
|
|
config[keys[-1]] = value
|
|
self.save()
|
|
|
|
def _get_default_config(self) -> Dict[str, Any]:
|
|
"""Get hardcoded default configuration."""
|
|
return {
|
|
'user': {
|
|
'name': 'User',
|
|
'id': ''
|
|
},
|
|
'audio': {
|
|
'input_device': 'default',
|
|
'sample_rate': 16000,
|
|
'chunk_duration': 3.0
|
|
},
|
|
'noise_suppression': {
|
|
'enabled': True,
|
|
'strength': 0.7,
|
|
'method': 'noisereduce'
|
|
},
|
|
'transcription': {
|
|
'model': 'base',
|
|
'device': 'auto',
|
|
'language': 'en',
|
|
'task': 'transcribe'
|
|
},
|
|
'processing': {
|
|
'use_vad': True,
|
|
'min_confidence': 0.5
|
|
},
|
|
'server_sync': {
|
|
'enabled': False,
|
|
'url': 'ws://localhost:8000',
|
|
'api_key': ''
|
|
},
|
|
'display': {
|
|
'show_timestamps': True,
|
|
'max_lines': 100,
|
|
'font_size': 12,
|
|
'theme': 'dark'
|
|
}
|
|
}
|
|
|
|
def reset_to_default(self) -> None:
|
|
"""Reset configuration to default values."""
|
|
self.config = self._get_default_config()
|
|
self.save()
|
|
|
|
def __repr__(self) -> str:
|
|
return f"Config(path={self.config_path})"
|