Skip to content

Commit

Permalink
refactor/optional audio service (#49)
Browse files Browse the repository at this point in the history
* feat/optional_audio_service

puts it behind a config option, replaces  feat/disable_audio_service


---------

Co-authored-by: JarbasAi <jarbasai@mailfence.com>
  • Loading branch information
NeonJarbas and JarbasAl authored Jan 12, 2024
1 parent 3fadec6 commit e094911
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 83 deletions.
25 changes: 2 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The "mouth" of the OVOS assistant!

Handles TTS generation and audio playback
Handles TTS generation and sounds playback

## Install

Expand Down Expand Up @@ -51,27 +51,6 @@ under mycroft.conf

// Mechanism used to play OGG audio files
// Override: SYSTEM
"play_ogg_cmdline": "ogg123 -q %1",

"Audio": {
// message.context may contains a source and destination
// native audio (playback / TTS) will only be played if a
// message destination is a native_source or if missing (considered a broadcast)
"native_sources": ["debug_cli", "audio"],

"backends": {
"OCP": {
"type": "ovos_common_play",
"active": true
},
"simple": {
"type": "ovos_audio_simple",
"active": true
},
"vlc": {
"type": "ovos_vlc",
"active": true
}
}
"play_ogg_cmdline": "ogg123 -q %1"
}
```
72 changes: 36 additions & 36 deletions ovos_audio/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
from ovos_bus_client import Message, MessageBusClient
from ovos_bus_client.session import SessionManager
from ovos_config.config import Configuration
from ovos_plugin_manager.audio import get_audio_service_configs
from ovos_plugin_manager.g2p import get_g2p_lang_configs, get_g2p_supported_langs, get_g2p_module_configs
from ovos_plugin_manager.tts import TTS
from ovos_plugin_manager.tts import get_tts_supported_langs, get_tts_lang_configs, get_tts_module_configs
from ovos_utils.file_utils import resolve_resource_file
from ovos_utils.log import LOG
from ovos_utils.log import LOG, deprecated
from ovos_utils.metrics import Stopwatch
from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap
from ovos_utils.sound import play_audio
Expand All @@ -29,23 +28,23 @@


def on_ready():
LOG.info('Audio service is ready.')
LOG.info('TTS service is ready.')


def on_alive():
LOG.info('Audio service is alive.')
LOG.info('TTS service is alive.')


def on_started():
LOG.info('Audio service started.')
LOG.info('TTS service started.')


def on_error(e='Unknown'):
LOG.error(f'Audio service failed to launch ({e}).')
LOG.error(f'TTS service failed to launch ({e}).')


def on_stopping():
LOG.info('Audio service is shutting down...')
LOG.info('TTS service is shutting down...')


class PlaybackService(Thread):
Expand Down Expand Up @@ -93,11 +92,13 @@ def __init__(self, ready_hook=on_ready, error_hook=on_error,
LOG.exception(e)
self.status.set_error(e)

try:
self.audio = AudioService(self.bus, disable_ocp=disable_ocp, validate_source=validate_source)
except Exception as e:
LOG.exception(e)
self.status.set_error(e)
self.audio = None
self.audio_enabled = self.config.get("enable_old_audioservice", True) # TODO default to False soon
if self.audio_enabled:
try:
self.audio = AudioService(self.bus, disable_ocp=disable_ocp, validate_source=validate_source)
except Exception as e:
LOG.exception(e)

@staticmethod
def get_tts_lang_options(lang, blacklist=None):
Expand Down Expand Up @@ -157,6 +158,7 @@ def get_g2p_lang_options(lang, blacklist=None):
return opts

@staticmethod
@deprecated("audio service moved to ovos-media", "0.1.0")
def get_audio_options(blacklist=None):
""" returns a list of options to be consumed by an external UI
each dict contains metadata about the plugins
Expand All @@ -166,17 +168,7 @@ def get_audio_options(blacklist=None):
"active": True,
"plugin_name": 'Ovos Common Play'}]
"""
blacklist = blacklist or []
opts = []
cfgs = get_audio_service_configs()
for name, config in cfgs.items():
engine = config["type"]
if engine in blacklist:
continue
# For Display purposes, we want to show the engine name without the underscore or dash and capitalized all
plugin_display_name = engine.replace("_", " ").replace("-", " ").title()
config["plugin_name"] = plugin_display_name
opts.append(config)
return opts

def handle_opm_tts_query(self, message):
Expand Down Expand Up @@ -229,6 +221,7 @@ def handle_opm_g2p_query(self, message):
}
self.bus.emit(message.response(data))

@deprecated("audio service moved to ovos-media", "0.1.0")
def handle_opm_audio_query(self, message):
""" Responds to opm.audio.query with data about installed plugins
Expand All @@ -237,21 +230,23 @@ def handle_opm_audio_query(self, message):
"configs" - {backend_name: backend_cfg}}
"options" - {lang: [list_of_valid_ui_metadata]}
"""
cfgs = get_audio_service_configs()
data = {
"plugins": list(cfgs.keys()),
"configs": cfgs,
"options": self.get_audio_options()
"plugins": [],
"configs": {},
"options": {}
}
self.bus.emit(message.response(data))

def run(self):
self.status.set_alive()
if self.audio.wait_for_load():
if len(self.audio.service) == 0:
LOG.warning('No audio backends loaded! '
'Audio playback is not available')
LOG.info("Running audio service in TTS only mode")
if self.audio_enabled:
LOG.warning("audio service has moved to ovos-media, if you already migrated to ovos-media "
'set "enable_old_audioservice": false in mycroft.conf')
if self.audio.wait_for_load():
if len(self.audio.service) == 0:
LOG.warning('No audio backends loaded! '
'Audio playback is not available')
LOG.info("Running audio service in TTS only mode")
# If at least TTS exists, report ready
if self.tts:
self.status.set_ready()
Expand Down Expand Up @@ -492,14 +487,18 @@ def handle_instant_play(self, message):
volume_changed = True
elif muted:
self.bus.emit(Message("mycroft.volume.unmute"))
if self.audio.current and not duck_pulse_handled:
self.audio.current.lower_volume()

if self.audio:
if self.audio.current and not duck_pulse_handled:
self.audio.current.lower_volume()

play_audio(audio_file).wait()

# return to previous state
if self.audio.current and not duck_pulse_handled:
self.audio.current.restore_volume()
if self.audio:
if self.audio.current and not duck_pulse_handled:
self.audio.current.restore_volume()

if ensure_volume:
if volume_changed:
self.bus.emit(Message("mycroft.volume.set", {"percent": volume,
Expand Down Expand Up @@ -528,7 +527,8 @@ def shutdown(self):
if self.tts.playback:
self.tts.playback.shutdown()
self.tts.playback.join()
self.audio.shutdown()
if self.audio:
self.audio.shutdown()

def init_messagebus(self):
"""
Expand Down
5 changes: 3 additions & 2 deletions ovos_audio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
#
import time

from ovos_utils.log import deprecated
from ovos_utils.signal import check_for_signal

from ovos_bus_client.send_func import send
from ovos_config import Configuration
from ovos_utils.log import LOG, deprecated
from ovos_utils.signal import check_for_signal


def validate_message_context(message, native_sources=None):
Expand Down
10 changes: 1 addition & 9 deletions requirements/extras.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1 @@
ovos_plugin_common_play~=0.0, >=0.0.6a11

ovos-tts-plugin-server

# ovos-ocp-youtube-plugin
ovos-ocp-m3u-plugin
ovos-ocp-rss-plugin
ovos-ocp-files-plugin
ovos-ocp-news-plugin
ovos-tts-plugin-server
16 changes: 3 additions & 13 deletions test/unittests/test_speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,27 +286,17 @@ def rcvm(msg):

speech.handle_opm_g2p_query(Message("opm.g2p.query"))

@mock.patch('ovos_audio.service.get_audio_service_configs')
def test_opm_audio(self, mock_get_configs, tts_factory_mock, config_mock):
def test_opm_audio(self, tts_factory_mock, config_mock):
setup_mocks(config_mock, tts_factory_mock)

ocp = {"type": "ovos_common_play", "active": True}
p = {"type": "ovos_badass_player", "active": True}

# per module configs, mocking same return val for all plugin inputs (!)
mock_get_configs.return_value = {"ocp": ocp, "badass": p}

bus = FakeBus()
speech = PlaybackService(bus=bus)

def rcvm(msg):
msg = json.loads(msg)
self.assertEqual(msg["type"], "opm.audio.query.response")
self.assertEqual(msg["data"]["plugins"], ["ocp", "badass"])
self.assertEqual(msg["data"]["configs"], {"ocp": ocp, "badass": p})
ocp["plugin_name"] = 'Ovos Common Play'
p["plugin_name"] = 'Ovos Badass Player'
self.assertEqual(msg["data"]["options"], [ocp, p])
self.assertEqual(msg["data"]["plugins"], [])
self.assertEqual(msg["data"]["configs"], {})

bus.on("message", rcvm)

Expand Down

0 comments on commit e094911

Please sign in to comment.