Files
bookstorm/src/config_manager.py
2025-10-19 18:02:34 -04:00

389 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Config Manager
Manages BookStorm settings using INI format.
Settings stored in ~/.config/stormux/bookstorm/settings.ini
"""
import configparser
from pathlib import Path
class ConfigManager:
"""Manages application configuration"""
def __init__(self, configPath=None):
"""
Initialize config manager
Args:
configPath: Path to config file (optional)
"""
if configPath is None:
homePath = Path.home()
configDir = homePath / ".config" / "stormux" / "bookstorm"
configDir.mkdir(parents=True, exist_ok=True)
configPath = configDir / "settings.ini"
self.configPath = Path(configPath)
self.config = configparser.ConfigParser()
# Load or create config
if self.configPath.exists():
self.config.read(self.configPath)
else:
self._create_default_config()
def _create_default_config(self):
"""Create default configuration"""
self.config['TTS'] = {
'voice_model': '/usr/share/piper-voices/en/en_US/hfc_male/medium/en_US-hfc_male-medium.onnx',
'voice_dir': '/usr/share/piper-voices/en/en_US',
'reader_engine': 'piper',
'speechd_voice': '',
'speechd_output_module': '',
'speech_rate': '0'
}
self.config['Reading'] = {
'auto_advance': 'true',
'auto_save_bookmark': 'true'
}
self.config['Display'] = {
'show_text': 'true'
}
self.config['Audio'] = {
'playback_speed': '1.0'
}
self.config['Paths'] = {
'last_book': '',
'books_directory': str(Path.home()),
'library_directory': ''
}
self.config['Audiobookshelf'] = {
'server_url': '',
'username': '',
'auth_token': '',
'auto_sync': 'true',
'sync_interval': '30',
'prefer_local': 'true',
'stream_cache_limit': '500'
}
self.config['Braille'] = {
'enabled': 'false',
'translation_table': 'grade2',
'sync_with_tts': 'true',
'show_status': 'true',
'mute_voice': 'false'
}
self.save()
def get(self, section, key, fallback=None):
"""
Get configuration value
Args:
section: Config section
key: Config key
fallback: Default value if not found
Returns:
Configuration value
"""
try:
return self.config.get(section, key)
except (configparser.NoSectionError, configparser.NoOptionError):
return fallback
def get_bool(self, section, key, fallback=False):
"""
Get boolean configuration value
Args:
section: Config section
key: Config key
fallback: Default value if not found
Returns:
Boolean configuration value
"""
try:
return self.config.getboolean(section, key)
except (configparser.NoSectionError, configparser.NoOptionError):
return fallback
def set(self, section, key, value):
"""
Set configuration value
Args:
section: Config section
key: Config key
value: Value to set
"""
if not self.config.has_section(section):
self.config.add_section(section)
self.config.set(section, key, str(value))
def save(self):
"""Save configuration to file"""
with open(self.configPath, 'w') as configFile:
self.config.write(configFile)
def get_voice_model(self):
"""Get configured voice model path"""
return self.get('TTS', 'voice_model')
def set_voice_model(self, modelPath):
"""Set voice model path"""
self.set('TTS', 'voice_model', str(modelPath))
self.save()
def get_voice_dir(self):
"""Get voice models directory"""
return self.get('TTS', 'voice_dir', '/usr/share/piper-voices/en/en_US')
def set_voice_dir(self, voiceDir):
"""Set voice models directory"""
self.set('TTS', 'voice_dir', str(voiceDir))
self.save()
def get_last_book(self):
"""Get last opened book path"""
lastBook = self.get('Paths', 'last_book')
return lastBook if lastBook else None
def set_last_book(self, bookPath):
"""Set last opened book path"""
self.set('Paths', 'last_book', str(bookPath))
self.save()
def get_books_directory(self):
"""Get books directory for file browser"""
return self.get('Paths', 'books_directory', str(Path.home()))
def set_books_directory(self, booksDir):
"""Set books directory"""
self.set('Paths', 'books_directory', str(booksDir))
self.save()
def get_auto_advance(self):
"""Get auto-advance setting"""
return self.get_bool('Reading', 'auto_advance', True)
def get_auto_save(self):
"""Get auto-save bookmark setting"""
return self.get_bool('Reading', 'auto_save_bookmark', True)
def get_reader_engine(self):
"""Get reader engine (piper or speechd)"""
return self.get('TTS', 'reader_engine', 'piper')
def set_reader_engine(self, engine):
"""Set reader engine (piper or speechd)"""
if engine in ['piper', 'speechd']:
self.set('TTS', 'reader_engine', engine)
self.save()
def get_speechd_voice(self):
"""Get speech-dispatcher voice"""
return self.get('TTS', 'speechd_voice', '')
def set_speechd_voice(self, voice):
"""Set speech-dispatcher voice"""
self.set('TTS', 'speechd_voice', str(voice))
self.save()
def get_speechd_output_module(self):
"""Get speech-dispatcher output module"""
return self.get('TTS', 'speechd_output_module', '')
def set_speechd_output_module(self, module):
"""Set speech-dispatcher output module"""
self.set('TTS', 'speechd_output_module', str(module))
self.save()
def get_speech_rate(self):
"""Get speech rate"""
try:
return int(self.get('TTS', 'speech_rate', '0'))
except ValueError:
return 0
def set_speech_rate(self, rate):
"""Set speech rate"""
self.set('TTS', 'speech_rate', str(rate))
self.save()
def get_show_text(self):
"""Get show text display setting"""
return self.get_bool('Display', 'show_text', True)
def set_show_text(self, enabled):
"""Set show text display setting"""
self.set('Display', 'show_text', str(enabled).lower())
self.save()
def get_library_directory(self):
"""Get library directory (default starting point for book browser)"""
return self.get('Paths', 'library_directory', '')
def set_library_directory(self, libraryDir):
"""Set library directory"""
self.set('Paths', 'library_directory', str(libraryDir))
self.save()
# Audiobookshelf settings
def get_abs_server_url(self):
"""Get Audiobookshelf server URL"""
return self.get('Audiobookshelf', 'server_url', '')
def set_abs_server_url(self, serverUrl):
"""Set Audiobookshelf server URL"""
self.set('Audiobookshelf', 'server_url', str(serverUrl))
self.save()
def get_abs_username(self):
"""Get Audiobookshelf username"""
return self.get('Audiobookshelf', 'username', '')
def set_abs_username(self, username):
"""Set Audiobookshelf username"""
self.set('Audiobookshelf', 'username', str(username))
self.save()
def get_abs_auth_token(self):
"""Get Audiobookshelf authentication token"""
return self.get('Audiobookshelf', 'auth_token', '')
def set_abs_auth_token(self, token):
"""Set Audiobookshelf authentication token"""
self.set('Audiobookshelf', 'auth_token', str(token))
self.save()
def get_abs_auto_sync(self):
"""Get Audiobookshelf auto-sync setting"""
return self.get_bool('Audiobookshelf', 'auto_sync', True)
def set_abs_auto_sync(self, enabled):
"""Set Audiobookshelf auto-sync setting"""
self.set('Audiobookshelf', 'auto_sync', str(enabled).lower())
self.save()
def get_abs_sync_interval(self):
"""Get Audiobookshelf sync interval in seconds"""
try:
return int(self.get('Audiobookshelf', 'sync_interval', '30'))
except ValueError:
return 30
def set_abs_sync_interval(self, seconds):
"""Set Audiobookshelf sync interval in seconds"""
self.set('Audiobookshelf', 'sync_interval', str(seconds))
self.save()
def get_abs_prefer_local(self):
"""Get Audiobookshelf prefer local books setting"""
return self.get_bool('Audiobookshelf', 'prefer_local', True)
def set_abs_prefer_local(self, enabled):
"""Set Audiobookshelf prefer local books setting"""
self.set('Audiobookshelf', 'prefer_local', str(enabled).lower())
self.save()
def get_abs_stream_cache_limit(self):
"""Get Audiobookshelf stream cache limit in MB"""
try:
return int(self.get('Audiobookshelf', 'stream_cache_limit', '500'))
except ValueError:
return 500
def set_abs_stream_cache_limit(self, limitMb):
"""Set Audiobookshelf stream cache limit in MB"""
self.set('Audiobookshelf', 'stream_cache_limit', str(limitMb))
self.save()
def is_abs_configured(self):
"""Check if Audiobookshelf server is configured"""
serverUrl = self.get_abs_server_url()
username = self.get_abs_username()
return bool(serverUrl and username)
def get_playback_speed(self):
"""Get audio playback speed (0.5 to 2.0)"""
try:
speed = float(self.get('Audio', 'playback_speed', '1.0'))
# Clamp to valid range
return max(0.5, min(2.0, speed))
except ValueError:
return 1.0
def set_playback_speed(self, speed):
"""Set audio playback speed (0.5 to 2.0)"""
# Clamp to valid range
speed = max(0.5, min(2.0, float(speed)))
self.set('Audio', 'playback_speed', str(speed))
self.save()
# Braille settings
def get_braille_enabled(self):
"""Get Braille output enabled setting"""
return self.get_bool('Braille', 'enabled', False)
def set_braille_enabled(self, enabled):
"""Set Braille output enabled setting"""
self.set('Braille', 'enabled', str(enabled).lower())
self.save()
def get_braille_translation_table(self):
"""Get Braille translation table (grade1/grade2/ueb)"""
return self.get('Braille', 'translation_table', 'grade2')
def set_braille_translation_table(self, table):
"""Set Braille translation table"""
if table in ['grade1', 'grade2', 'ueb']:
self.set('Braille', 'translation_table', table)
self.save()
def get_braille_sync_with_tts(self):
"""Get Braille sync with TTS setting"""
return self.get_bool('Braille', 'sync_with_tts', True)
def set_braille_sync_with_tts(self, enabled):
"""Set Braille sync with TTS setting"""
self.set('Braille', 'sync_with_tts', str(enabled).lower())
self.save()
def get_braille_show_status(self):
"""Get Braille show status messages setting"""
return self.get_bool('Braille', 'show_status', True)
def set_braille_show_status(self, enabled):
"""Set Braille show status messages setting"""
self.set('Braille', 'show_status', str(enabled).lower())
self.save()
def get_braille_mute_voice(self):
"""Get Braille mute voice (Braille-only mode) setting"""
return self.get_bool('Braille', 'mute_voice', False)
def set_braille_mute_voice(self, enabled):
"""Set Braille mute voice setting"""
self.set('Braille', 'mute_voice', str(enabled).lower())
self.save()
def save_settings(self):
"""Alias for save() - for backward compatibility"""
self.save()