Add media browser feature with UI improvements
- Refactored index.html: Split into separate HTML (309 lines), CSS (908 lines), and JS (1,286 lines) files - Implemented media browser with folder configuration, recursive navigation, and thumbnail display - Added metadata extraction using mutagen library (title, artist, album, duration, bitrate, codec) - Implemented thumbnail generation and caching with SHA256 hash-based keys and LRU eviction - Added platform-specific file playback (os.startfile on Windows, xdg-open on Linux, open on macOS) - Implemented path validation security to prevent directory traversal attacks - Added smooth thumbnail loading with fade-in animation and loading spinner - Added i18n support for browser (English and Russian) - Updated dependencies: mutagen>=1.47.0, pillow>=10.0.0 - Added comprehensive media browser documentation to README Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ from typing import Optional
|
||||
|
||||
import yaml
|
||||
|
||||
from .config import CallbackConfig, ScriptConfig, settings
|
||||
from .config import CallbackConfig, MediaFolderConfig, ScriptConfig, settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -279,6 +279,114 @@ class ConfigManager:
|
||||
|
||||
logger.info(f"Callback '{name}' deleted from config")
|
||||
|
||||
def add_media_folder(self, folder_id: str, config: MediaFolderConfig) -> None:
|
||||
"""Add a new media folder to config.
|
||||
|
||||
Args:
|
||||
folder_id: Folder ID (must be unique).
|
||||
config: Media folder configuration.
|
||||
|
||||
Raises:
|
||||
ValueError: If folder already exists.
|
||||
IOError: If config file cannot be written.
|
||||
"""
|
||||
with self._lock:
|
||||
# Read YAML
|
||||
if not self._config_path.exists():
|
||||
data = {}
|
||||
else:
|
||||
with open(self._config_path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
# Check if folder already exists
|
||||
if "media_folders" in data and folder_id in data["media_folders"]:
|
||||
raise ValueError(f"Media folder '{folder_id}' already exists")
|
||||
|
||||
# Add folder
|
||||
if "media_folders" not in data:
|
||||
data["media_folders"] = {}
|
||||
data["media_folders"][folder_id] = config.model_dump(exclude_none=True)
|
||||
|
||||
# Write YAML
|
||||
self._config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self._config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
# Update in-memory settings
|
||||
settings.media_folders[folder_id] = config
|
||||
|
||||
logger.info(f"Media folder '{folder_id}' added to config")
|
||||
|
||||
def update_media_folder(self, folder_id: str, config: MediaFolderConfig) -> None:
|
||||
"""Update an existing media folder.
|
||||
|
||||
Args:
|
||||
folder_id: Folder ID.
|
||||
config: New media folder configuration.
|
||||
|
||||
Raises:
|
||||
ValueError: If folder does not exist.
|
||||
IOError: If config file cannot be written.
|
||||
"""
|
||||
with self._lock:
|
||||
# Read YAML
|
||||
if not self._config_path.exists():
|
||||
raise ValueError(f"Config file not found: {self._config_path}")
|
||||
|
||||
with open(self._config_path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
# Check if folder exists
|
||||
if "media_folders" not in data or folder_id not in data["media_folders"]:
|
||||
raise ValueError(f"Media folder '{folder_id}' does not exist")
|
||||
|
||||
# Update folder
|
||||
data["media_folders"][folder_id] = config.model_dump(exclude_none=True)
|
||||
|
||||
# Write YAML
|
||||
with open(self._config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
# Update in-memory settings
|
||||
settings.media_folders[folder_id] = config
|
||||
|
||||
logger.info(f"Media folder '{folder_id}' updated in config")
|
||||
|
||||
def delete_media_folder(self, folder_id: str) -> None:
|
||||
"""Delete a media folder from config.
|
||||
|
||||
Args:
|
||||
folder_id: Folder ID.
|
||||
|
||||
Raises:
|
||||
ValueError: If folder does not exist.
|
||||
IOError: If config file cannot be written.
|
||||
"""
|
||||
with self._lock:
|
||||
# Read YAML
|
||||
if not self._config_path.exists():
|
||||
raise ValueError(f"Config file not found: {self._config_path}")
|
||||
|
||||
with open(self._config_path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
# Check if folder exists
|
||||
if "media_folders" not in data or folder_id not in data["media_folders"]:
|
||||
raise ValueError(f"Media folder '{folder_id}' does not exist")
|
||||
|
||||
# Delete folder
|
||||
del data["media_folders"][folder_id]
|
||||
|
||||
# Write YAML
|
||||
with open(self._config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
# Update in-memory settings
|
||||
if folder_id in settings.media_folders:
|
||||
del settings.media_folders[folder_id]
|
||||
|
||||
logger.info(f"Media folder '{folder_id}' deleted from config")
|
||||
|
||||
|
||||
# Global config manager instance
|
||||
config_manager = ConfigManager()
|
||||
|
||||
Reference in New Issue
Block a user