main.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. # -*- coding: utf-8 -*-
  2. """
  3. Audio-Ducking Plugin.
  4. Reduziert die System-Lautstaerke (ALSA/PulseAudio) wenn das
  5. Wakeword erkannt wird und stellt sie nach der Conversation
  6. wieder her.
  7. Flow:
  8. Wakeword erkannt → ALSA Output auf duck_volume_percent (duck)
  9. Conversation laeuft → Lautstaerke bleibt reduziert
  10. Conversation beendet → Restore aus client_config.json
  11. Wichtig:
  12. - Ducking aendert NUR die ALSA-Lautstaerke, NICHT den
  13. in client_config.json gespeicherten output_volume Wert.
  14. - Restore liest die gespeicherten Werte aus der Config
  15. statt einer temporaer gespeicherten Variable. Das ist
  16. solider und kommt auch nach Crashes wieder zurecht.
  17. - Im Server-Mode wird das Ducking spaeter ueber eine
  18. Satellite-Audio-Update Nachricht an den aktiven
  19. Satellite weitergereicht. Aktuell unterstuetzt das
  20. Plugin nur Standalone/Client-Mode (lokaler VolumeHandler).
  21. """
  22. from __future__ import annotations
  23. from trixy_core.events.decorators import TrixyEvent
  24. from trixy_core.events.enums import EventPriority
  25. from trixy_core.plugins.trixy_plugin import TrixyPlugin
  26. from trixy_core.utils.debug import pdebug, pinfo
  27. class AudioDuckingPlugin(TrixyPlugin):
  28. """Reduziert System-Lautstaerke bei Sprachinteraktion."""
  29. NAME = "audio_ducking"
  30. VERSION = "2.0.0"
  31. DESCRIPTION = "ALSA-Lautstaerke leiser bei Sprachinteraktion"
  32. AUTHOR = "Trixy"
  33. def __init__(self, application, plugin_path, config=None) -> None:
  34. super().__init__(application, plugin_path, config)
  35. self._ducked = False
  36. async def on_load(self) -> None:
  37. pinfo(f"AudioDucking: Lade v{self.VERSION}")
  38. async def on_unload(self) -> None:
  39. if self._ducked:
  40. await self._restore_volume()
  41. def _get_volume_handler(self):
  42. """Gibt den lokalen VolumeHandler zurueck (falls vorhanden)."""
  43. return getattr(self.application, "_volume_handler", None)
  44. async def _duck_volume(self) -> None:
  45. """Reduziert die ALSA-Lautstaerke ohne Persistenz."""
  46. if self._ducked:
  47. return
  48. handler = self._get_volume_handler()
  49. if handler is None or not handler.available:
  50. pdebug("AudioDucking: Kein VolumeHandler verfuegbar")
  51. return
  52. duck_pct = int(self.get_config_value("duck_volume_percent", 20))
  53. # source="duck" → Volume wird NICHT in Config persistiert
  54. await handler.set_output_volume(duck_pct, source="duck")
  55. self._ducked = True
  56. pinfo(f"AudioDucking: ALSA Output → {duck_pct}%")
  57. async def _restore_volume(self) -> None:
  58. """Stellt die in der Config gespeicherte Lautstaerke wieder her."""
  59. if not self._ducked:
  60. return
  61. handler = self._get_volume_handler()
  62. if handler is not None and handler.available:
  63. # Liest output_volume aus client_config.json — solider als
  64. # eine temporaer gespeicherte Variable, ueberlebt Crashes.
  65. await handler.restore_from_config("output")
  66. pinfo("AudioDucking: ALSA Output aus Config wiederhergestellt")
  67. self._ducked = False
  68. # === Events ===
  69. @TrixyEvent(["wakeword_detected"], priority=EventPriority.HIGHEST)
  70. async def on_wakeword(self, event_name: str, event_data) -> None:
  71. await self._duck_volume()
  72. @TrixyEvent(["wakeword_manual_trigger"], priority=EventPriority.HIGHEST)
  73. async def on_manual_trigger(self, event_name: str, event_data) -> None:
  74. await self._duck_volume()
  75. @TrixyEvent(["recording_complete"])
  76. async def on_recording_complete(self, event_name: str, event_data) -> None:
  77. await self._restore_volume()
  78. @TrixyEvent(["conversation_started"])
  79. async def on_conversation_started(self, event_name: str, event_data) -> None:
  80. await self._duck_volume()
  81. @TrixyEvent(["conversation_ended"])
  82. async def on_conversation_ended(self, event_name: str, event_data) -> None:
  83. await self._restore_volume()