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:
2026-02-06 21:30:28 +03:00
parent d5ec5c611f
commit 7c631d09f6
21 changed files with 4535 additions and 2203 deletions

View File

@@ -10,6 +10,14 @@ from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class MediaFolderConfig(BaseModel):
"""Configuration for a media folder."""
path: str = Field(..., description="Absolute path to media folder")
label: str = Field(..., description="Human-readable display label")
enabled: bool = Field(default=True, description="Whether this folder is active")
class CallbackConfig(BaseModel):
"""Configuration for a callback script (no label/description needed)."""
@@ -77,6 +85,18 @@ class Settings(BaseSettings):
description="Callback scripts executed by integration events (on_turn_on, on_turn_off, on_toggle)",
)
# Media folders for browsing
media_folders: dict[str, MediaFolderConfig] = Field(
default_factory=dict,
description="Media folders available for browsing in the media browser",
)
# Thumbnail settings
thumbnail_size: str = Field(
default="medium",
description='Thumbnail size: "small" (150x150), "medium" (300x300), or "both"',
)
@classmethod
def load_from_yaml(cls, path: Optional[Path] = None) -> "Settings":
"""Load settings from a YAML configuration file."""