Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/legacy_audio_api #456

Merged
merged 15 commits into from
May 10, 2024
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
pip install ./test/end2end/skill-ovos-schedule
pip install ./test/end2end/skill-new-stop
pip install ./test/end2end/skill-old-stop
pip install ./test/end2end/skill-fake-fm
- name: Install core repo
run: |
pip install -e .[mycroft,deprecated]
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
pip install ./test/end2end/skill-ovos-schedule
pip install ./test/end2end/skill-new-stop
pip install ./test/end2end/skill-old-stop
pip install ./test/end2end/skill-fake-fm
- name: Install core repo
run: |
pip install -e .[mycroft,deprecated]
Expand Down
17 changes: 9 additions & 8 deletions ovos_core/intent_services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ class IntentService:
querying the intent service.
"""

def __init__(self, bus):
def __init__(self, bus, config=None):
self.bus = bus
config = Configuration()
self.config = config or Configuration().get("intents", {})
if "padatious" not in self.config:
self.config["padatious"] = Configuration().get("padatious", {})

# Dictionary for translating a skill id to a name
self.skill_names = {}
Expand All @@ -60,18 +62,18 @@ def __init__(self, bus):
self.adapt_service = AdaptService()
try:
from ovos_core.intent_services.padatious_service import PadatiousService
self.padatious_service = PadatiousService(bus, config['padatious'])
self.padatious_service = PadatiousService(bus, self.config["padatious"])
except ImportError:
LOG.error(f'Failed to create padatious intent handlers, padatious not installed')
self.padatious_service = None
self.padacioso_service = PadaciosoService(bus, config['padatious'])
self.padacioso_service = PadaciosoService(bus, self.config["padatious"])
self.fallback = FallbackService(bus)
self.converse = ConverseService(bus)
self.common_qa = CommonQAService(bus)
self.stop = StopService(bus)
self.ocp = None
self.utterance_plugins = UtteranceTransformersService(bus, config=config)
self.metadata_plugins = MetadataTransformersService(bus, config=config)
self.utterance_plugins = UtteranceTransformersService(bus)
self.metadata_plugins = MetadataTransformersService(bus)

self._load_ocp_pipeline() # TODO - enable by default once stable

Expand Down Expand Up @@ -110,8 +112,7 @@ def __init__(self, bus):

def _load_ocp_pipeline(self):
"""EXPERIMENTAL: this feature is not yet ready for end users"""
audio_enabled = Configuration().get("enable_old_audioservice", True)
if not audio_enabled:
if self.config.get("experimental_ocp_pipeline", False):
LOG.warning("EXPERIMENTAL: the OCP pipeline is enabled!")
try:
from ovos_core.intent_services.ocp_service import OCPPipelineMatcher
Expand Down
138 changes: 102 additions & 36 deletions ovos_core/intent_services/ocp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
from threading import RLock
from typing import List, Tuple, Optional

from ovos_classifiers.skovos.classifier import SklearnOVOSClassifier
from ovos_classifiers.skovos.features import ClassifierProbaVectorizer, KeywordFeaturesVectorizer
from padacioso import IntentContainer
from sklearn.pipeline import FeatureUnion

import ovos_core.intent_services
from ovos_bus_client.apis.ocp import OCPInterface, OCPQuery
from ovos_bus_client.apis.ocp import OCPInterface, OCPQuery, ClassicAudioServiceInterface
from ovos_bus_client.message import Message
from ovos_classifiers.skovos.classifier import SklearnOVOSClassifier
from ovos_classifiers.skovos.features import ClassifierProbaVectorizer, KeywordFeaturesVectorizer
from ovos_config import Configuration
from ovos_plugin_manager.ocp import load_stream_extractors
from ovos_utils import classproperty
from ovos_utils.log import LOG
from ovos_utils.messagebus import FakeBus
Expand Down Expand Up @@ -104,6 +106,7 @@ def __init__(self, bus=None, config=None):
resources_dir=f"{dirname(__file__)}")

self.ocp_api = OCPInterface(self.bus)
self.legacy_api = ClassicAudioServiceInterface(self.bus)
JarbasAl marked this conversation as resolved.
Show resolved Hide resolved

self.config = config or {}
self.search_lock = RLock()
Expand Down Expand Up @@ -171,6 +174,12 @@ def register_ocp_api_events(self):
self.bus.on('ovos.common_play.register_keyword', self.handle_skill_keyword_register)
self.bus.on('ovos.common_play.deregister_keyword', self.handle_skill_keyword_deregister)
self.bus.on('ovos.common_play.announce', self.handle_skill_register)

self.bus.on("mycroft.audio.playing_track", self._handle_legacy_audio_start)
self.bus.on("mycroft.audio.queue_end", self._handle_legacy_audio_end)
self.bus.on("mycroft.audio.service.pause", self._handle_legacy_audio_pause)
self.bus.on("mycroft.audio.service.resume", self._handle_legacy_audio_resume)
self.bus.on("mycroft.audio.service.stop", self._handle_legacy_audio_stop)
self.bus.emit(Message("ovos.common_play.status")) # sync on launch

def register_ocp_intents(self):
Expand Down Expand Up @@ -526,7 +535,10 @@ def handle_play_intent(self, message: Message):
'origin': OCP_ID}))

# ovos-PHAL-plugin-mk1 will display music icon in response to play message
self.ocp_api.play(results, query)
if self.use_legacy_audio:
self.legacy_play(results, query)
else:
self.ocp_api.play(results, query)

def handle_open_intent(self, message: Message):
LOG.info("Requesting OCP homescreen")
Expand All @@ -539,49 +551,54 @@ def handle_like_intent(self, message: Message):
self.bus.emit(message.forward("ovos.common_play.like"))

def handle_stop_intent(self, message: Message):
LOG.info("Requesting OCP to go to stop")
self.ocp_api.stop()
if self.use_legacy_audio:
LOG.info("Requesting Legacy AudioService to stop")
self.legacy_api.stop()
else:
LOG.info("Requesting OCP to stop")
self.ocp_api.stop()

def handle_next_intent(self, message: Message):
LOG.info("Requesting OCP to go to next track")
self.ocp_api.next()
if self.use_legacy_audio:
LOG.info("Requesting Legacy AudioService to go to next track")
self.legacy_api.next()
else:
LOG.info("Requesting OCP to go to next track")
self.ocp_api.next()

def handle_prev_intent(self, message: Message):
LOG.info("Requesting OCP to go to prev track")
self.ocp_api.prev()
if self.use_legacy_audio:
LOG.info("Requesting Legacy AudioService to go to prev track")
self.legacy_api.prev()
else:
LOG.info("Requesting OCP to go to prev track")
self.ocp_api.prev()

def handle_pause_intent(self, message: Message):
LOG.info("Requesting OCP to go to pause")
self.ocp_api.pause()
if self.use_legacy_audio:
LOG.info("Requesting Legacy AudioService to pause")
self.legacy_api.pause()
else:
LOG.info("Requesting OCP to go to pause")
self.ocp_api.pause()

def handle_resume_intent(self, message: Message):
LOG.info("Requesting OCP to go to resume")
self.ocp_api.resume()
if self.use_legacy_audio:
LOG.info("Requesting Legacy AudioService to resume")
self.legacy_api.resume()
else:
LOG.info("Requesting OCP to go to resume")
self.ocp_api.resume()

def handle_search_error_intent(self, message: Message):
self.bus.emit(message.forward("mycroft.audio.play_sound",
{"uri": "snd/error.mp3"}))
LOG.info("Requesting OCP to stop")
self.ocp_api.stop()

def _do_play(self, phrase: str, results, media_type=MediaType.GENERIC):
self.bus.emit(Message('ovos.common_play.reset'))
LOG.debug(f"Playing {len(results)} results for: {phrase}")
if not results:
self.speak_dialog("cant.play",
data={"phrase": phrase,
"media_type": media_type})
if self.use_legacy_audio:
LOG.info("Requesting Legacy AudioService to stop")
self.legacy_api.stop()
else:
best = self.select_best(results)
results = [r for r in results if r.uri != best.uri]
results.insert(0, best)
self.bus.emit(Message('add_context',
{'context': "Playing",
'word': "",
'origin': OCP_ID}))

# ovos-PHAL-plugin-mk1 will display music icon in response to play message
self.ocp_api.play(results, phrase)
LOG.info("Requesting OCP to stop")
self.ocp_api.stop()

# NLP
@staticmethod
Expand Down Expand Up @@ -751,11 +768,12 @@ def filter_results(self, results: list, phrase: str, lang: str,
video_only = True

# check if user said "play XXX audio only"
if audio_only:
if audio_only or self.use_legacy_audio:
l1 = len(results)
# TODO - also check inside playlists
results = [r for r in results
if isinstance(r, Playlist) or r.playback == PlaybackType.AUDIO]
if (isinstance(r, Playlist) and not self.use_legacy_audio)
or r.playback == PlaybackType.AUDIO]
LOG.debug(f"filtered {l1 - len(results)} non-audio results")

# check if user said "play XXX video only"
Expand Down Expand Up @@ -853,3 +871,51 @@ def select_best(self, results: list) -> MediaEntry:
LOG.info(f"OVOSCommonPlay selected: {selected.skill_id} - {selected.match_confidence}")
LOG.debug(str(selected))
return selected

##################
# Legacy Audio subsystem API
@property
def use_legacy_audio(self):
"""when neither ovos-media nor old OCP are available"""
if self.config.get("legacy"):
# explicitly set in pipeline config
return True
cfg = Configuration()
return cfg.get("disable_ocp") and cfg.get("enable_old_audioservice")

def legacy_play(self, results: List[MediaEntry], phrase=""):
xtract = load_stream_extractors()
# for legacy audio service we need to do stream extraction here
# we also need to filter video results
results = [xtract.extract_stream(r.uri, video=False)["uri"]
for r in results
if r.playback == PlaybackType.AUDIO
or r.media_type in OCPQuery.cast2audio]
self.player_state = PlayerState.PLAYING
self.media_state = MediaState.LOADING_MEDIA
self.legacy_api.play(results, utterance=phrase)

def _handle_legacy_audio_stop(self, message: Message):
if self.use_legacy_audio:
self.player_state = PlayerState.STOPPED
self.media_state = MediaState.NO_MEDIA

def _handle_legacy_audio_pause(self, message: Message):
if self.use_legacy_audio and self.player_state == PlayerState.PLAYING:
self.player_state = PlayerState.PAUSED
self.media_state = MediaState.LOADED_MEDIA

def _handle_legacy_audio_resume(self, message: Message):
if self.use_legacy_audio and self.player_state == PlayerState.PAUSED:
self.player_state = PlayerState.PLAYING
self.media_state = MediaState.LOADED_MEDIA

def _handle_legacy_audio_start(self, message: Message):
if self.use_legacy_audio:
self.player_state = PlayerState.PLAYING
self.media_state = MediaState.LOADED_MEDIA

def _handle_legacy_audio_end(self, message: Message):
if self.use_legacy_audio:
self.player_state = PlayerState.STOPPED
self.media_state = MediaState.END_OF_MEDIA
7 changes: 3 additions & 4 deletions ovos_core/transformers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Optional, List

from ovos_config import Configuration
from ovos_plugin_manager.metadata_transformers import find_metadata_transformer_plugins
from ovos_plugin_manager.templates.transformers import UtteranceTransformer
from ovos_plugin_manager.text_transformers import find_utterance_transformer_plugins

from ovos_utils.json_helper import merge_dict
Expand All @@ -11,7 +10,7 @@
class UtteranceTransformersService:

def __init__(self, bus, config=None):
self.config_core = config or {}
self.config_core = config or Configuration()
self.loaded_plugins = {}
self.has_loaded = False
self.bus = bus
Expand Down Expand Up @@ -68,7 +67,7 @@ def transform(self, utterances: List[str], context: Optional[dict] = None):
class MetadataTransformersService:

def __init__(self, bus, config=None):
self.config_core = config or {}
self.config_core = config or Configuration()
self.loaded_plugins = {}
self.has_loaded = False
self.bus = bus
Expand Down
3 changes: 2 additions & 1 deletion requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ ovos-gui~=0.0, >=0.0.2
ovos-messagebus~=0.0

# Support OCP tests
ovos_bus_client>=0.0.9a15
ovos_bus_client>=0.0.9a15
ovos-utils>=0.1.0a16
12 changes: 6 additions & 6 deletions test/end2end/minicroft.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@


class MiniCroft(SkillManager):
def __init__(self, skill_ids, *args, **kwargs):
def __init__(self, skill_ids, ocp=False, *args, **kwargs):
bus = FakeBus()
super().__init__(bus, *args, **kwargs)
self.skill_ids = skill_ids
self.intent_service = self._register_intent_services()
self.intent_service = self._register_intent_services(ocp=ocp)
self.scheduler = EventScheduler(bus, schedule_file="/tmp/schetest.json")

def _register_intent_services(self):
def _register_intent_services(self, ocp=False):
"""Start up the all intent services and connect them as needed.

Args:
bus: messagebus client to register the services on
"""
service = IntentService(self.bus)
service = IntentService(self.bus, config={"experimental_ocp_pipeline": ocp})
# Register handler to trigger fallback system
self.bus.on(
'mycroft.skills.fallback',
Expand Down Expand Up @@ -61,11 +61,11 @@ def stop(self):
SessionManager.default_session = SessionManager.sessions["default"] = Session("default")


def get_minicroft(skill_id):
def get_minicroft(skill_id, ocp=False):
if isinstance(skill_id, str):
skill_id = [skill_id]
assert isinstance(skill_id, list)
croft1 = MiniCroft(skill_id)
croft1 = MiniCroft(skill_id, ocp=ocp)
croft1.start()
while croft1.status.state != ProcessState.READY:
sleep(0.2)
Expand Down
Loading
Loading