music.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # -*- coding: utf-8 -*-
  2. """
  3. Music Extension Points.
  4. Extension Points für Musikquellen, Decoder und Ausgabeziele.
  5. """
  6. from abc import abstractmethod
  7. from typing import Any, AsyncIterator, TYPE_CHECKING
  8. from trixy_core.plugins.extensions.extension import Extension
  9. from trixy_core.plugins.extensions.extension_point import ExtensionPoint, get_global_registry
  10. if TYPE_CHECKING:
  11. from trixy_core.music.sources.base import MusicSource
  12. from trixy_core.music.formats.base import AudioDecoder
  13. # Extension Point IDs
  14. MUSIC_SOURCE_POINT = "music.source"
  15. MUSIC_DECODER_POINT = "music.decoder"
  16. MUSIC_OUTPUT_POINT = "music.output"
  17. class MusicSourceExtension(Extension["MusicSource"]):
  18. """
  19. Extension für Musikquellen.
  20. Ermöglicht Plugins, neue Musikquellen hinzuzufügen:
  21. - Streaming-Dienste (Spotify, Amazon Music, Tidal, Deezer)
  22. - Cloud-Speicher (Google Drive, Dropbox)
  23. - Netzwerk-Shares (SMB, WebDAV)
  24. - Podcast-Feeds
  25. - Internet-Radio-Verzeichnisse
  26. Beispiel:
  27. class SpotifySourceExtension(MusicSourceExtension):
  28. def __init__(self, plugin_name: str):
  29. super().__init__(
  30. "spotify_source",
  31. "Spotify",
  32. plugin_name,
  33. source_type="spotify",
  34. )
  35. async def create_instance(self, config):
  36. return SpotifySource(
  37. client_id=config["client_id"],
  38. client_secret=config["client_secret"],
  39. )
  40. """
  41. def __init__(
  42. self,
  43. extension_id: str,
  44. name: str,
  45. plugin_name: str = "",
  46. source_type: str = "",
  47. description: str = "",
  48. ) -> None:
  49. """
  50. Initialisiert die MusicSource Extension.
  51. Args:
  52. extension_id: Eindeutige ID
  53. name: Anzeigename
  54. plugin_name: Plugin-Name
  55. source_type: Quellen-Typ (z.B. "spotify", "amazon")
  56. description: Beschreibung
  57. """
  58. super().__init__(extension_id, name, plugin_name)
  59. self._info.description = description
  60. self._info.extension_point = MUSIC_SOURCE_POINT
  61. self._info.metadata["source_type"] = source_type
  62. @property
  63. def source_type(self) -> str:
  64. """Quellen-Typ."""
  65. return self._info.metadata.get("source_type", "")
  66. @abstractmethod
  67. async def create_instance(self, config: dict[str, Any]) -> "MusicSource":
  68. """Erstellt die MusicSource-Instanz."""
  69. pass
  70. class MusicDecoderExtension(Extension["AudioDecoder"]):
  71. """
  72. Extension für Audio-Decoder.
  73. Ermöglicht Plugins, neue Audio-Formate zu unterstützen:
  74. - Proprietäre Formate (MQA, DSD)
  75. - Kompressionsformate (AC3, DTS)
  76. - Spezielle Codecs
  77. Beispiel:
  78. class MQADecoderExtension(MusicDecoderExtension):
  79. def __init__(self, plugin_name: str):
  80. super().__init__(
  81. "mqa_decoder",
  82. "MQA Decoder",
  83. plugin_name,
  84. formats=["mqa"],
  85. extensions=[".mqa"],
  86. )
  87. async def create_instance(self, config):
  88. return MQADecoder()
  89. """
  90. def __init__(
  91. self,
  92. extension_id: str,
  93. name: str,
  94. plugin_name: str = "",
  95. formats: list[str] | None = None,
  96. extensions: list[str] | None = None,
  97. mime_types: list[str] | None = None,
  98. description: str = "",
  99. ) -> None:
  100. """
  101. Initialisiert die AudioDecoder Extension.
  102. Args:
  103. extension_id: Eindeutige ID
  104. name: Anzeigename
  105. plugin_name: Plugin-Name
  106. formats: Unterstützte Format-Namen
  107. extensions: Dateiendungen (z.B. [".mqa"])
  108. mime_types: MIME-Types
  109. description: Beschreibung
  110. """
  111. super().__init__(extension_id, name, plugin_name)
  112. self._info.description = description
  113. self._info.extension_point = MUSIC_DECODER_POINT
  114. self._info.metadata["formats"] = formats or []
  115. self._info.metadata["extensions"] = extensions or []
  116. self._info.metadata["mime_types"] = mime_types or []
  117. @property
  118. def formats(self) -> list[str]:
  119. """Unterstützte Formate."""
  120. return self._info.metadata.get("formats", [])
  121. @property
  122. def file_extensions(self) -> list[str]:
  123. """Unterstützte Dateiendungen."""
  124. return self._info.metadata.get("extensions", [])
  125. @abstractmethod
  126. async def create_instance(self, config: dict[str, Any]) -> "AudioDecoder":
  127. """Erstellt die AudioDecoder-Instanz."""
  128. pass
  129. class MusicOutputExtension(Extension):
  130. """
  131. Extension für Ausgabeziele.
  132. Ermöglicht Plugins, neue Ausgabeziele hinzuzufügen:
  133. - Chromecast/Google Cast
  134. - AirPlay
  135. - Sonos
  136. - Bluetooth-Lautsprecher
  137. - DLNA/UPnP
  138. Beispiel:
  139. class ChromecastOutputExtension(MusicOutputExtension):
  140. def __init__(self, plugin_name: str):
  141. super().__init__(
  142. "chromecast_output",
  143. "Google Chromecast",
  144. plugin_name,
  145. output_type="chromecast",
  146. )
  147. async def create_instance(self, config):
  148. return ChromecastOutput()
  149. """
  150. def __init__(
  151. self,
  152. extension_id: str,
  153. name: str,
  154. plugin_name: str = "",
  155. output_type: str = "",
  156. description: str = "",
  157. ) -> None:
  158. """
  159. Initialisiert die MusicOutput Extension.
  160. Args:
  161. extension_id: Eindeutige ID
  162. name: Anzeigename
  163. plugin_name: Plugin-Name
  164. output_type: Ausgabe-Typ
  165. description: Beschreibung
  166. """
  167. super().__init__(extension_id, name, plugin_name)
  168. self._info.description = description
  169. self._info.extension_point = MUSIC_OUTPUT_POINT
  170. self._info.metadata["output_type"] = output_type
  171. @property
  172. def output_type(self) -> str:
  173. """Ausgabe-Typ."""
  174. return self._info.metadata.get("output_type", "")
  175. def setup_music_extension_points() -> dict[str, ExtensionPoint]:
  176. """
  177. Richtet alle Music Extension Points ein.
  178. Returns:
  179. Dict mit allen erstellten Extension Points
  180. """
  181. registry = get_global_registry()
  182. points = {}
  183. # Musikquellen
  184. points[MUSIC_SOURCE_POINT] = registry.create_point(
  185. id=MUSIC_SOURCE_POINT,
  186. name="Musikquellen",
  187. description="Erweiterung um neue Musikquellen (Spotify, Amazon, etc.)",
  188. category="music",
  189. allows_multiple=True,
  190. )
  191. # Audio-Decoder
  192. points[MUSIC_DECODER_POINT] = registry.create_point(
  193. id=MUSIC_DECODER_POINT,
  194. name="Audio-Decoder",
  195. description="Erweiterung um neue Audio-Formate",
  196. category="music",
  197. allows_multiple=True,
  198. )
  199. # Ausgabeziele
  200. points[MUSIC_OUTPUT_POINT] = registry.create_point(
  201. id=MUSIC_OUTPUT_POINT,
  202. name="Ausgabeziele",
  203. description="Erweiterung um neue Ausgabeziele (Chromecast, AirPlay, etc.)",
  204. category="music",
  205. allows_multiple=True,
  206. )
  207. return points
  208. def get_music_sources() -> list["MusicSource"]:
  209. """Holt alle registrierten Musikquellen."""
  210. registry = get_global_registry()
  211. point = registry.get_point(MUSIC_SOURCE_POINT)
  212. if point:
  213. return point.get_all_instances()
  214. return []
  215. def get_music_decoders() -> list["AudioDecoder"]:
  216. """Holt alle registrierten Decoder."""
  217. registry = get_global_registry()
  218. point = registry.get_point(MUSIC_DECODER_POINT)
  219. if point:
  220. return point.get_all_instances()
  221. return []