Files
local-transcription/client/config.py
jknapp ff067b3368 Add unified per-speaker font support and remote transcription service
Font changes:
- Consolidate font settings into single Display Settings section
- Support Web-Safe, Google Fonts, and Custom File uploads for both displays
- Fix Google Fonts URL encoding (use + instead of %2B for spaces)
- Fix per-speaker font inline style quote escaping in Node.js display
- Add font debug logging to help diagnose font issues
- Update web server to sync all font settings on settings change
- Remove deprecated PHP server documentation files

New features:
- Add remote transcription service for GPU offloading
- Add instance lock to prevent multiple app instances
- Add version tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:09:57 -08:00

165 lines
5.0 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)
# Fonts directory for custom font files
self.fonts_dir = self.app_dir / "fonts"
self.fonts_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__).resolve().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 get_custom_fonts(self) -> list:
"""
Get list of custom font files in the fonts directory.
Returns:
List of (font_name, font_path) tuples
"""
fonts = []
font_extensions = {'.ttf', '.otf', '.woff', '.woff2'}
if self.fonts_dir.exists():
for font_file in self.fonts_dir.iterdir():
if font_file.suffix.lower() in font_extensions:
# Use filename without extension as font name
font_name = font_file.stem
fonts.append((font_name, font_file))
return sorted(fonts, key=lambda x: x[0].lower())
def __repr__(self) -> str:
return f"Config(path={self.config_path})"