import os import json from pathlib import Path from typing import Optional class ConfigService: """Service for managing application configuration and environment variables.""" def __init__(self, config_path: Optional[str] = None): """Initialize the configuration service. Args: config_path: Optional path to the config file. If not provided, uses CONFIG_PATH env var. """ # Set default values self.port = int(os.getenv('PORT', '6544')) self.extra_logging = os.getenv('EXTRA_LOGGING', 'True').lower() == 'true' # Spotify API configuration self.spotify_client_id = os.getenv('CLIENT_ID', '5f573c9620494bae87890c0f08a60293') self.spotify_client_secret = os.getenv('CLIENT_SECRET', '212476d9b0f3472eaa762d90b19b0ba8') # Default public client secret # Path configurations self.config_path = config_path or os.getenv('CONFIG_PATH', '/config/config.json') self.ffmpeg_location = os.getenv('FFMPEG_LOCATION', '/usr/bin/ffmpeg') # Base music directory - using the specified Jellyfin Media location self.music_base_dir = '/Volumes/Jellyfin Media/Music' # Output templates # Single tracks: /Volumes/Jellyfin Media/Music/Artist/Album (Year)/## - Title.ext self.track_output = os.getenv('TRACK_OUTPUT', os.path.join(self.music_base_dir, '{artist}/{album} - ({year})/{track:02d} - {title}.{output-ext}')) # Album tracks: /Volumes/Jellyfin Media/Music/Artist/Album (Year)/## - Title.ext self.album_output = os.getenv('ALBUM_OUTPUT', os.path.join(self.music_base_dir, '{artist}/{album} - ({year})/{track:02d} - {title}.{output-ext}')) # Playlist tracks: /Volumes/Jellyfin Media/Music/Playlists/Playlist Name/Artist - Title.ext self.playlist_output = os.getenv('PLAYLIST_OUTPUT', os.path.join(self.music_base_dir, 'Playlists/{list-name}/{artist} - {title}.{output-ext}')) # Artist/album tracks: /Volumes/Jellyfin Media/Music/Artist/Album (Year)/## - Title.ext self.artist_output = os.getenv('ARTIST_OUTPUT', os.path.join(self.music_base_dir, '{artist}/{album} - ({year})/{track:02d} - {title}.{output-ext}')) # Jellyfin integration self.trigger_jellyfin_scan = os.getenv('TRIGGER_JELLYFIN_SCAN', 'False').lower() == 'true' self.jellyfin_address = os.getenv('JELLYFIN_ADDRESS', '') self.jellyfin_api_key = os.getenv('JELLYFIN_API_KEY', '') # Load additional config from file if it exists self._load_config() def _load_config(self) -> None: """Load configuration from the config file if it exists.""" try: config_path = Path(self.config_path) if config_path.exists() and config_path.is_file(): with open(config_path, 'r') as f: config_data = json.load(f) # Update attributes from config file for key, value in config_data.items(): if hasattr(self, key): setattr(self, key, value) except Exception as e: print(f"Warning: Could not load config file: {e}") def save_config(self) -> None: """Save the current configuration to the config file.""" try: config_path = Path(self.config_path) config_path.parent.mkdir(parents=True, exist_ok=True) # Create a dictionary of all configuration values config_data = { 'port': self.port, 'extra_logging': self.extra_logging, 'spotify_client_id': self.spotify_client_id, 'spotify_client_secret': self.spotify_client_secret, 'ffmpeg_location': self.ffmpeg_location, 'track_output': self.track_output, 'album_output': self.album_output, 'playlist_output': self.playlist_output, 'artist_output': self.artist_output, 'trigger_jellyfin_scan': self.trigger_jellyfin_scan, 'jellyfin_address': self.jellyfin_address, 'jellyfin_api_key': self.jellyfin_api_key, } with open(config_path, 'w') as f: json.dump(config_data, f, indent=2) except Exception as e: print(f"Error saving config file: {e}") def get(self, key: str, default=None): """Get a configuration value by attribute name. Args: key: attribute / config name default: value to return if attribute is missing Returns: The attribute value or default. """ return getattr(self, key, default) def __getattr__(self, name: str): """Fallback attribute access for dynamic config keys.""" if name in self.__dict__: return self.__dict__[name] raise AttributeError(f"{self.__class__.__name__} has no attribute '{name}'") def to_dict(self) -> dict: """Return the current configuration as a dictionary.""" return { 'port': self.port, 'extra_logging': self.extra_logging, 'spotify_client_id': self.spotify_client_id, 'ffmpeg_location': self.ffmpeg_location, 'track_output': self.track_output, 'album_output': self.album_output, 'playlist_output': self.playlist_output, 'artist_output': self.artist_output, 'trigger_jellyfin_scan': self.trigger_jellyfin_scan, 'jellyfin_address': self.jellyfin_address, 'has_jellyfin_api_key': bool(self.jellyfin_api_key), }