# -*- coding: utf-8 -*- """ Music Extension Points. Extension Points für Musikquellen, Decoder und Ausgabeziele. """ from abc import abstractmethod from typing import Any, AsyncIterator, TYPE_CHECKING from trixy_core.plugins.extensions.extension import Extension from trixy_core.plugins.extensions.extension_point import ExtensionPoint, get_global_registry if TYPE_CHECKING: from trixy_core.music.sources.base import MusicSource from trixy_core.music.formats.base import AudioDecoder # Extension Point IDs MUSIC_SOURCE_POINT = "music.source" MUSIC_DECODER_POINT = "music.decoder" MUSIC_OUTPUT_POINT = "music.output" class MusicSourceExtension(Extension["MusicSource"]): """ Extension für Musikquellen. Ermöglicht Plugins, neue Musikquellen hinzuzufügen: - Streaming-Dienste (Spotify, Amazon Music, Tidal, Deezer) - Cloud-Speicher (Google Drive, Dropbox) - Netzwerk-Shares (SMB, WebDAV) - Podcast-Feeds - Internet-Radio-Verzeichnisse Beispiel: class SpotifySourceExtension(MusicSourceExtension): def __init__(self, plugin_name: str): super().__init__( "spotify_source", "Spotify", plugin_name, source_type="spotify", ) async def create_instance(self, config): return SpotifySource( client_id=config["client_id"], client_secret=config["client_secret"], ) """ def __init__( self, extension_id: str, name: str, plugin_name: str = "", source_type: str = "", description: str = "", ) -> None: """ Initialisiert die MusicSource Extension. Args: extension_id: Eindeutige ID name: Anzeigename plugin_name: Plugin-Name source_type: Quellen-Typ (z.B. "spotify", "amazon") description: Beschreibung """ super().__init__(extension_id, name, plugin_name) self._info.description = description self._info.extension_point = MUSIC_SOURCE_POINT self._info.metadata["source_type"] = source_type @property def source_type(self) -> str: """Quellen-Typ.""" return self._info.metadata.get("source_type", "") @abstractmethod async def create_instance(self, config: dict[str, Any]) -> "MusicSource": """Erstellt die MusicSource-Instanz.""" pass class MusicDecoderExtension(Extension["AudioDecoder"]): """ Extension für Audio-Decoder. Ermöglicht Plugins, neue Audio-Formate zu unterstützen: - Proprietäre Formate (MQA, DSD) - Kompressionsformate (AC3, DTS) - Spezielle Codecs Beispiel: class MQADecoderExtension(MusicDecoderExtension): def __init__(self, plugin_name: str): super().__init__( "mqa_decoder", "MQA Decoder", plugin_name, formats=["mqa"], extensions=[".mqa"], ) async def create_instance(self, config): return MQADecoder() """ def __init__( self, extension_id: str, name: str, plugin_name: str = "", formats: list[str] | None = None, extensions: list[str] | None = None, mime_types: list[str] | None = None, description: str = "", ) -> None: """ Initialisiert die AudioDecoder Extension. Args: extension_id: Eindeutige ID name: Anzeigename plugin_name: Plugin-Name formats: Unterstützte Format-Namen extensions: Dateiendungen (z.B. [".mqa"]) mime_types: MIME-Types description: Beschreibung """ super().__init__(extension_id, name, plugin_name) self._info.description = description self._info.extension_point = MUSIC_DECODER_POINT self._info.metadata["formats"] = formats or [] self._info.metadata["extensions"] = extensions or [] self._info.metadata["mime_types"] = mime_types or [] @property def formats(self) -> list[str]: """Unterstützte Formate.""" return self._info.metadata.get("formats", []) @property def file_extensions(self) -> list[str]: """Unterstützte Dateiendungen.""" return self._info.metadata.get("extensions", []) @abstractmethod async def create_instance(self, config: dict[str, Any]) -> "AudioDecoder": """Erstellt die AudioDecoder-Instanz.""" pass class MusicOutputExtension(Extension): """ Extension für Ausgabeziele. Ermöglicht Plugins, neue Ausgabeziele hinzuzufügen: - Chromecast/Google Cast - AirPlay - Sonos - Bluetooth-Lautsprecher - DLNA/UPnP Beispiel: class ChromecastOutputExtension(MusicOutputExtension): def __init__(self, plugin_name: str): super().__init__( "chromecast_output", "Google Chromecast", plugin_name, output_type="chromecast", ) async def create_instance(self, config): return ChromecastOutput() """ def __init__( self, extension_id: str, name: str, plugin_name: str = "", output_type: str = "", description: str = "", ) -> None: """ Initialisiert die MusicOutput Extension. Args: extension_id: Eindeutige ID name: Anzeigename plugin_name: Plugin-Name output_type: Ausgabe-Typ description: Beschreibung """ super().__init__(extension_id, name, plugin_name) self._info.description = description self._info.extension_point = MUSIC_OUTPUT_POINT self._info.metadata["output_type"] = output_type @property def output_type(self) -> str: """Ausgabe-Typ.""" return self._info.metadata.get("output_type", "") def setup_music_extension_points() -> dict[str, ExtensionPoint]: """ Richtet alle Music Extension Points ein. Returns: Dict mit allen erstellten Extension Points """ registry = get_global_registry() points = {} # Musikquellen points[MUSIC_SOURCE_POINT] = registry.create_point( id=MUSIC_SOURCE_POINT, name="Musikquellen", description="Erweiterung um neue Musikquellen (Spotify, Amazon, etc.)", category="music", allows_multiple=True, ) # Audio-Decoder points[MUSIC_DECODER_POINT] = registry.create_point( id=MUSIC_DECODER_POINT, name="Audio-Decoder", description="Erweiterung um neue Audio-Formate", category="music", allows_multiple=True, ) # Ausgabeziele points[MUSIC_OUTPUT_POINT] = registry.create_point( id=MUSIC_OUTPUT_POINT, name="Ausgabeziele", description="Erweiterung um neue Ausgabeziele (Chromecast, AirPlay, etc.)", category="music", allows_multiple=True, ) return points def get_music_sources() -> list["MusicSource"]: """Holt alle registrierten Musikquellen.""" registry = get_global_registry() point = registry.get_point(MUSIC_SOURCE_POINT) if point: return point.get_all_instances() return [] def get_music_decoders() -> list["AudioDecoder"]: """Holt alle registrierten Decoder.""" registry = get_global_registry() point = registry.get_point(MUSIC_DECODER_POINT) if point: return point.get_all_instances() return []