registry.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. # -*- coding: utf-8 -*-
  2. """
  3. Decoder-Registry.
  4. Zentrale Verwaltung aller Audio-Decoder.
  5. """
  6. import logging
  7. from pathlib import Path
  8. from typing import Type
  9. from trixy_core.music.formats.base import AudioDecoder, AudioFormat, DecoderError
  10. class DecoderRegistry:
  11. """
  12. Registry für Audio-Decoder.
  13. Verwaltet alle verfügbaren Decoder und wählt automatisch den
  14. passenden Decoder basierend auf Dateiformat, Endung oder MIME-Type.
  15. """
  16. def __init__(self) -> None:
  17. """Initialisiert die Registry."""
  18. self._decoders: dict[AudioFormat, AudioDecoder] = {}
  19. self._extension_map: dict[str, AudioDecoder] = {}
  20. self._mime_map: dict[str, AudioDecoder] = {}
  21. self.logger = logging.getLogger(__name__)
  22. # ==========================================================================
  23. # Registrierung
  24. # ==========================================================================
  25. def register(self, decoder_class: Type[AudioDecoder]) -> bool:
  26. """
  27. Registriert einen Decoder.
  28. Args:
  29. decoder_class: Decoder-Klasse
  30. Returns:
  31. True wenn erfolgreich und verfügbar
  32. """
  33. try:
  34. decoder = decoder_class()
  35. if not decoder.is_available:
  36. self.logger.debug(
  37. f"Decoder {decoder_class.__name__} nicht verfügbar (fehlende Dependencies)"
  38. )
  39. return False
  40. # Nach Format registrieren
  41. self._decoders[decoder.FORMAT] = decoder
  42. # Nach Extensions registrieren
  43. for ext in decoder.EXTENSIONS:
  44. self._extension_map[ext.lower()] = decoder
  45. # Nach MIME-Types registrieren
  46. for mime in decoder.MIME_TYPES:
  47. self._mime_map[mime.lower()] = decoder
  48. self.logger.debug(f"Decoder registriert: {decoder_class.__name__}")
  49. return True
  50. except Exception as e:
  51. self.logger.error(f"Decoder-Registrierung fehlgeschlagen: {e}")
  52. return False
  53. def register_default_decoders(self) -> int:
  54. """
  55. Registriert alle Standard-Decoder.
  56. Returns:
  57. Anzahl der erfolgreich registrierten Decoder
  58. """
  59. from trixy_core.music.formats.mp3 import MP3Decoder
  60. from trixy_core.music.formats.wav import WAVDecoder
  61. from trixy_core.music.formats.ogg import OGGDecoder
  62. from trixy_core.music.formats.flac import FLACDecoder
  63. decoders = [MP3Decoder, WAVDecoder, OGGDecoder, FLACDecoder]
  64. count = 0
  65. for decoder_class in decoders:
  66. if self.register(decoder_class):
  67. count += 1
  68. self.logger.info(f"{count}/{len(decoders)} Decoder registriert")
  69. return count
  70. def unregister(self, format: AudioFormat) -> bool:
  71. """
  72. Entfernt einen Decoder.
  73. Args:
  74. format: Audio-Format
  75. Returns:
  76. True wenn entfernt
  77. """
  78. if format in self._decoders:
  79. decoder = self._decoders.pop(format)
  80. # Extension-Map bereinigen
  81. to_remove = [ext for ext, d in self._extension_map.items() if d is decoder]
  82. for ext in to_remove:
  83. del self._extension_map[ext]
  84. # MIME-Map bereinigen
  85. to_remove = [mime for mime, d in self._mime_map.items() if d is decoder]
  86. for mime in to_remove:
  87. del self._mime_map[mime]
  88. return True
  89. return False
  90. # ==========================================================================
  91. # Decoder-Auswahl
  92. # ==========================================================================
  93. def get_decoder(self, format: AudioFormat) -> AudioDecoder | None:
  94. """
  95. Holt Decoder für Format.
  96. Args:
  97. format: Audio-Format
  98. Returns:
  99. Decoder oder None
  100. """
  101. return self._decoders.get(format)
  102. def get_decoder_for_file(self, path: Path) -> AudioDecoder | None:
  103. """
  104. Holt Decoder basierend auf Dateiendung.
  105. Args:
  106. path: Dateipfad
  107. Returns:
  108. Decoder oder None
  109. """
  110. ext = path.suffix.lower().lstrip(".")
  111. return self._extension_map.get(ext)
  112. def get_decoder_for_extension(self, extension: str) -> AudioDecoder | None:
  113. """
  114. Holt Decoder für Dateiendung.
  115. Args:
  116. extension: Dateiendung (mit oder ohne Punkt)
  117. Returns:
  118. Decoder oder None
  119. """
  120. ext = extension.lower().lstrip(".")
  121. return self._extension_map.get(ext)
  122. def get_decoder_for_mime(self, mime_type: str) -> AudioDecoder | None:
  123. """
  124. Holt Decoder für MIME-Type.
  125. Args:
  126. mime_type: MIME-Type
  127. Returns:
  128. Decoder oder None
  129. """
  130. mime = mime_type.lower().split(";")[0].strip()
  131. return self._mime_map.get(mime)
  132. def get_best_decoder(
  133. self,
  134. path: Path | None = None,
  135. extension: str | None = None,
  136. mime_type: str | None = None,
  137. format: AudioFormat | None = None,
  138. ) -> AudioDecoder | None:
  139. """
  140. Wählt den besten Decoder basierend auf verfügbaren Informationen.
  141. Args:
  142. path: Dateipfad
  143. extension: Dateiendung
  144. mime_type: MIME-Type
  145. format: Audio-Format
  146. Returns:
  147. Bester passender Decoder oder None
  148. """
  149. # Priorität: Format > Datei > Extension > MIME
  150. if format:
  151. decoder = self.get_decoder(format)
  152. if decoder:
  153. return decoder
  154. if path:
  155. decoder = self.get_decoder_for_file(path)
  156. if decoder:
  157. return decoder
  158. if extension:
  159. decoder = self.get_decoder_for_extension(extension)
  160. if decoder:
  161. return decoder
  162. if mime_type:
  163. decoder = self.get_decoder_for_mime(mime_type)
  164. if decoder:
  165. return decoder
  166. return None
  167. # ==========================================================================
  168. # Informationen
  169. # ==========================================================================
  170. @property
  171. def formats(self) -> list[AudioFormat]:
  172. """Unterstützte Formate."""
  173. return list(self._decoders.keys())
  174. @property
  175. def extensions(self) -> list[str]:
  176. """Unterstützte Dateiendungen."""
  177. return list(self._extension_map.keys())
  178. @property
  179. def mime_types(self) -> list[str]:
  180. """Unterstützte MIME-Types."""
  181. return list(self._mime_map.keys())
  182. @property
  183. def decoders(self) -> list[AudioDecoder]:
  184. """Alle registrierten Decoder."""
  185. return list(self._decoders.values())
  186. def is_supported(
  187. self,
  188. path: Path | None = None,
  189. extension: str | None = None,
  190. mime_type: str | None = None,
  191. ) -> bool:
  192. """
  193. Prüft ob Format unterstützt wird.
  194. Args:
  195. path: Dateipfad
  196. extension: Dateiendung
  197. mime_type: MIME-Type
  198. Returns:
  199. True wenn unterstützt
  200. """
  201. return self.get_best_decoder(
  202. path=path,
  203. extension=extension,
  204. mime_type=mime_type,
  205. ) is not None
  206. def get_status(self) -> dict:
  207. """
  208. Liefert Status der Registry.
  209. Returns:
  210. Status-Dictionary
  211. """
  212. return {
  213. "decoder_count": len(self._decoders),
  214. "supported_formats": [f.value for f in self.formats],
  215. "supported_extensions": self.extensions,
  216. "supported_mime_types": self.mime_types,
  217. "decoders": {
  218. f.value: {
  219. "class": type(d).__name__,
  220. "available": d.is_available,
  221. "extensions": list(d.EXTENSIONS),
  222. }
  223. for f, d in self._decoders.items()
  224. },
  225. }
  226. def __contains__(self, item) -> bool:
  227. """Prüft ob Format/Extension unterstützt wird."""
  228. if isinstance(item, AudioFormat):
  229. return item in self._decoders
  230. if isinstance(item, str):
  231. item = item.lower().lstrip(".")
  232. return item in self._extension_map or item in self._mime_map
  233. return False
  234. def __len__(self) -> int:
  235. return len(self._decoders)
  236. def __repr__(self) -> str:
  237. return (
  238. f"DecoderRegistry(decoders={len(self._decoders)}, "
  239. f"formats={[f.value for f in self.formats]})"
  240. )
  241. # Globale Default-Registry
  242. _default_registry: DecoderRegistry | None = None
  243. def get_default_registry() -> DecoderRegistry:
  244. """Holt die globale Default-Registry (lazy-initialized)."""
  245. global _default_registry
  246. if _default_registry is None:
  247. _default_registry = DecoderRegistry()
  248. _default_registry.register_default_decoders()
  249. return _default_registry