| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- # -*- coding: utf-8 -*-
- """
- Decoder-Registry.
- Zentrale Verwaltung aller Audio-Decoder.
- """
- import logging
- from pathlib import Path
- from typing import Type
- from trixy_core.music.formats.base import AudioDecoder, AudioFormat, DecoderError
- class DecoderRegistry:
- """
- Registry für Audio-Decoder.
- Verwaltet alle verfügbaren Decoder und wählt automatisch den
- passenden Decoder basierend auf Dateiformat, Endung oder MIME-Type.
- """
- def __init__(self) -> None:
- """Initialisiert die Registry."""
- self._decoders: dict[AudioFormat, AudioDecoder] = {}
- self._extension_map: dict[str, AudioDecoder] = {}
- self._mime_map: dict[str, AudioDecoder] = {}
- self.logger = logging.getLogger(__name__)
- # ==========================================================================
- # Registrierung
- # ==========================================================================
- def register(self, decoder_class: Type[AudioDecoder]) -> bool:
- """
- Registriert einen Decoder.
- Args:
- decoder_class: Decoder-Klasse
- Returns:
- True wenn erfolgreich und verfügbar
- """
- try:
- decoder = decoder_class()
- if not decoder.is_available:
- self.logger.debug(
- f"Decoder {decoder_class.__name__} nicht verfügbar (fehlende Dependencies)"
- )
- return False
- # Nach Format registrieren
- self._decoders[decoder.FORMAT] = decoder
- # Nach Extensions registrieren
- for ext in decoder.EXTENSIONS:
- self._extension_map[ext.lower()] = decoder
- # Nach MIME-Types registrieren
- for mime in decoder.MIME_TYPES:
- self._mime_map[mime.lower()] = decoder
- self.logger.debug(f"Decoder registriert: {decoder_class.__name__}")
- return True
- except Exception as e:
- self.logger.error(f"Decoder-Registrierung fehlgeschlagen: {e}")
- return False
- def register_default_decoders(self) -> int:
- """
- Registriert alle Standard-Decoder.
- Returns:
- Anzahl der erfolgreich registrierten Decoder
- """
- from trixy_core.music.formats.mp3 import MP3Decoder
- from trixy_core.music.formats.wav import WAVDecoder
- from trixy_core.music.formats.ogg import OGGDecoder
- from trixy_core.music.formats.flac import FLACDecoder
- decoders = [MP3Decoder, WAVDecoder, OGGDecoder, FLACDecoder]
- count = 0
- for decoder_class in decoders:
- if self.register(decoder_class):
- count += 1
- self.logger.info(f"{count}/{len(decoders)} Decoder registriert")
- return count
- def unregister(self, format: AudioFormat) -> bool:
- """
- Entfernt einen Decoder.
- Args:
- format: Audio-Format
- Returns:
- True wenn entfernt
- """
- if format in self._decoders:
- decoder = self._decoders.pop(format)
- # Extension-Map bereinigen
- to_remove = [ext for ext, d in self._extension_map.items() if d is decoder]
- for ext in to_remove:
- del self._extension_map[ext]
- # MIME-Map bereinigen
- to_remove = [mime for mime, d in self._mime_map.items() if d is decoder]
- for mime in to_remove:
- del self._mime_map[mime]
- return True
- return False
- # ==========================================================================
- # Decoder-Auswahl
- # ==========================================================================
- def get_decoder(self, format: AudioFormat) -> AudioDecoder | None:
- """
- Holt Decoder für Format.
- Args:
- format: Audio-Format
- Returns:
- Decoder oder None
- """
- return self._decoders.get(format)
- def get_decoder_for_file(self, path: Path) -> AudioDecoder | None:
- """
- Holt Decoder basierend auf Dateiendung.
- Args:
- path: Dateipfad
- Returns:
- Decoder oder None
- """
- ext = path.suffix.lower().lstrip(".")
- return self._extension_map.get(ext)
- def get_decoder_for_extension(self, extension: str) -> AudioDecoder | None:
- """
- Holt Decoder für Dateiendung.
- Args:
- extension: Dateiendung (mit oder ohne Punkt)
- Returns:
- Decoder oder None
- """
- ext = extension.lower().lstrip(".")
- return self._extension_map.get(ext)
- def get_decoder_for_mime(self, mime_type: str) -> AudioDecoder | None:
- """
- Holt Decoder für MIME-Type.
- Args:
- mime_type: MIME-Type
- Returns:
- Decoder oder None
- """
- mime = mime_type.lower().split(";")[0].strip()
- return self._mime_map.get(mime)
- def get_best_decoder(
- self,
- path: Path | None = None,
- extension: str | None = None,
- mime_type: str | None = None,
- format: AudioFormat | None = None,
- ) -> AudioDecoder | None:
- """
- Wählt den besten Decoder basierend auf verfügbaren Informationen.
- Args:
- path: Dateipfad
- extension: Dateiendung
- mime_type: MIME-Type
- format: Audio-Format
- Returns:
- Bester passender Decoder oder None
- """
- # Priorität: Format > Datei > Extension > MIME
- if format:
- decoder = self.get_decoder(format)
- if decoder:
- return decoder
- if path:
- decoder = self.get_decoder_for_file(path)
- if decoder:
- return decoder
- if extension:
- decoder = self.get_decoder_for_extension(extension)
- if decoder:
- return decoder
- if mime_type:
- decoder = self.get_decoder_for_mime(mime_type)
- if decoder:
- return decoder
- return None
- # ==========================================================================
- # Informationen
- # ==========================================================================
- @property
- def formats(self) -> list[AudioFormat]:
- """Unterstützte Formate."""
- return list(self._decoders.keys())
- @property
- def extensions(self) -> list[str]:
- """Unterstützte Dateiendungen."""
- return list(self._extension_map.keys())
- @property
- def mime_types(self) -> list[str]:
- """Unterstützte MIME-Types."""
- return list(self._mime_map.keys())
- @property
- def decoders(self) -> list[AudioDecoder]:
- """Alle registrierten Decoder."""
- return list(self._decoders.values())
- def is_supported(
- self,
- path: Path | None = None,
- extension: str | None = None,
- mime_type: str | None = None,
- ) -> bool:
- """
- Prüft ob Format unterstützt wird.
- Args:
- path: Dateipfad
- extension: Dateiendung
- mime_type: MIME-Type
- Returns:
- True wenn unterstützt
- """
- return self.get_best_decoder(
- path=path,
- extension=extension,
- mime_type=mime_type,
- ) is not None
- def get_status(self) -> dict:
- """
- Liefert Status der Registry.
- Returns:
- Status-Dictionary
- """
- return {
- "decoder_count": len(self._decoders),
- "supported_formats": [f.value for f in self.formats],
- "supported_extensions": self.extensions,
- "supported_mime_types": self.mime_types,
- "decoders": {
- f.value: {
- "class": type(d).__name__,
- "available": d.is_available,
- "extensions": list(d.EXTENSIONS),
- }
- for f, d in self._decoders.items()
- },
- }
- def __contains__(self, item) -> bool:
- """Prüft ob Format/Extension unterstützt wird."""
- if isinstance(item, AudioFormat):
- return item in self._decoders
- if isinstance(item, str):
- item = item.lower().lstrip(".")
- return item in self._extension_map or item in self._mime_map
- return False
- def __len__(self) -> int:
- return len(self._decoders)
- def __repr__(self) -> str:
- return (
- f"DecoderRegistry(decoders={len(self._decoders)}, "
- f"formats={[f.value for f in self.formats]})"
- )
- # Globale Default-Registry
- _default_registry: DecoderRegistry | None = None
- def get_default_registry() -> DecoderRegistry:
- """Holt die globale Default-Registry (lazy-initialized)."""
- global _default_registry
- if _default_registry is None:
- _default_registry = DecoderRegistry()
- _default_registry.register_default_decoders()
- return _default_registry
|