Developing OVOS Common Play Skills
OVOS Common Play (OCP) is a full-fledged media player, compatible with the MPRIS standard. Developing a skill for OCP is similar to writing any other OVOS-compatible skill except basic intents and playing media are handled for the developer. This documentation is a quick start guide for developers hoping to write an OCP skill.
General Steps
- Create a skill class extending the OCP base class
- In the __init__ method indicate the media types you want to handle
self.voc_match(phrase, "skill_name")
to handle specific requests for your skillself.remove_voc(phrase, "skill_name")
to remove matched phrases from the search request- Implement the
ocp_search
decorator, as many as you want (they run in parallel) - The decorated method can return a list or be an iterator of
result_dict
(track or playlist) - The search function can be entirely inline or call another Python library, like pandorinha or plexapi
self.extend_timeout()
to not let OCP call for a Generic search too soon- Place one in each search function so it's extended every time the skill is called
- Implement a confidence score formula
- Values are between 0 and 100
- High confidence scores cancel other OCP skill searches
ocp_featured_media
, return a playlist for the OCP menu if selected from GUI- Create a
requirements.txt
file with third-party package requirements - Create a
skills.json
file for skill metadata
The general interface that OCP expects to receive looks something like the following:
class OVOSAudioTrack(TypedDict):
uri: str # URL/URI of media, OCP will handle formatting and file handling
title: str
media_type: ovos_plugin_common_play.MediaType
playback: ovos_plugin_common_play.PlaybackType
match_confidence: int # 0-100
album: str | None # Parsed even for movies and TV shows
artist: str | None # Parsed even for movies and TV shows
length: int | str | None # in milliseconds, if present
image: str | None
bg_image: str | None
skill_icon: str | None # Optional filename for skill icon
skill_id: str | None # Optional ID of skill to distinguish where results came from
OCP Skill Template
from os.path import join, dirname
from ovos_plugin_common_play.ocp import MediaType, PlaybackType
from ovos_utils.parse import fuzzy_match
from ovos_workshop.skills.common_play import OVOSCommonPlaybackSkill, \
ocp_search
class MySkill(OVOSCommonPlaybackSkill):
def __init__(...):
super(....)
self.supported_media = [MediaType.GENERIC,
MediaType.MUSIC] # <- these are the only media_types that will be sent to your skill
self.skill_icon = join(dirname(__file__), "ui", "pandora.jpeg")
# score
@staticmethod
def calc_score(phrase, match, base_score=0, exact=False):
# implement your own logic here, assing a val from 0 - 100 per result
if exact:
# this requires that the result is related
if phrase.lower() in match["title"].lower():
match["match_confidence"] = max(match["match_confidence"], 80)
elif phrase.lower() in match["artist"].lower():
match["match_confidence"] = max(match["match_confidence"], 85)
elif phrase.lower() == match["station"].lower():
match["match_confidence"] = max(match["match_confidence"], 70)
else:
return 0
title_score = 100 * fuzzy_match(phrase.lower(),
match["title"].lower())
artist_score = 100 * fuzzy_match(phrase.lower(),
match["artist"].lower())
if artist_score > 85:
score += artist_score * 0.85 + title_score * 0.15
elif artist_score > 70:
score += artist_score * 0.6 + title_score * 0.4
elif artist_score > 50:
score += title_score * 0.5 + artist_score * 0.5
else:
score += title_score * 0.8 + artist_score * 0.2
score = min((100, score))
return score
@ocp_search()
def search_my_skill(self, phrase, media_type=MediaType.GENERIC):
# match the request media_type
base_score = 0
if media_type == MediaType.MUSIC:
base_score += 10
else:
base_score -= 15 # some penalty for proof of concept
explicit_request = False
if self.voc_match(phrase, "mySkillNameVoc"):
# explicitly requested our skill
base_score += 50
phrase = self.remove_voc(phrase, "mySkillNameVoc") # clean up search str
explicit_request = True
self.extend_timeout(1) # we know our skill is slow, ask OCP for more time
for r in self.search_my_results(phrase):
yield {
"match_confidence": self.calc_score(phrase, r, base_score,
exact=not explicit_request),
"media_type": MediaType.MUSIC,
"length": r["duration"] * 1000, # seconds to milliseconds
"uri": r["uri"],
"playback": PlaybackType.AUDIO,
"image": r["image"],
"bg_image": r["bg_image"],
"skill_icon": self.skill_icon,
"title": r["title"],
"artist": r["artist"],
"album": r["album"],
"skill_id": self.skill_id
}
skill.json template
{
"title": "Plex OCP Skill",
"url": "https://github.com/d-mcknight/skill-plex",
"summary": "[OCP](https://github.com/OpenVoiceOS/ovos-ocp-audio-plugin) skill to play media from [Plex](https://plex.tv).",
"short_description": "[OCP](https://github.com/OpenVoiceOS/ovos-ocp-audio-plugin) skill to play media from [Plex](https://plex.tv).",
"description": "",
"examples": [
"Play Charles Mingus",
"Play Jamie Cullum on Plex",
"Play the movie Ghostbusters",
"Play the movie Ghostbusters on Plex",
"Play Star Trek the Next Generation on Plex",
"Play the tv show Star Trek the Next Generation on Plex"
],
"desktopFile": false,
"warning": "",
"systemDeps": false,
"requirements": {
"python": ["plexapi~=4.13", "ovos-workshop~=0.0.11"],
"system": {},
"skill": []
},
"incompatible_skills": [],
"platforms": ["i386", "x86_64", "ia64", "arm64", "arm"],
"branch": "master",
"license": "BSD-3-Clause",
"icon": "https://freemusicarchive.org/legacy/fma-smaller.jpg",
"category": "Music",
"categories": ["Music", "Daily"],
"tags": ["music", "NeonAI", "NeonGecko Original", "OCP", "Common Play"],
"credits": ["NeonGeckoCom", "NeonDaniel"],
"skillname": "skill-plex",
"authorname": "d-mcknight",
"foldername": null
}
Installing an OCP Skill
OCP Skills are installed like any other OVOS skill. The preferred pattern is to release a pip package for your OCP skill and install it directly, but skills may also be installed directly from any pip-supported source such as git+https://github.com/OpenVoiceOS/skill-ovos-youtube-music
.
Once a skill has been installed a restart of the mycroft-skills
, ovos-skills
, or neon-skills
service will be required.