Skip to content

Commit

Permalink
clean up for release
Browse files Browse the repository at this point in the history
  • Loading branch information
mikejgray committed Aug 7, 2024
1 parent de24364 commit 5d001fc
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 52 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/skill-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,3 @@ jobs:
uses: neongeckocom/.github/.github/workflows/skill_test_installation.yml@master
license_tests:
uses: neongeckocom/.github/.github/workflows/license_tests.yml@master
python_tests:
uses: neongeckocom/.github/.github/workflows/license_tests.yml@master
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# skill-plex.d-mcknight
# skill-plex.oscillatelabsllc

An MVP for playing music from [Plex](https://plex.tv) on your Neon or OpenVoice OS device via [OVOS Common Play (OCP)](https://github.com/OpenVoiceOS/ovos-ocp-audio-plugin).
An OCP/OVOS-media skill for playing music from [Plex](https://plex.tv) on your Neon or OpenVoice OS device via [OVOS Common Play (OCP)](https://github.com/OpenVoiceOS/ovos-ocp-audio-plugin).

## Usage

**NOTE**: This skill currently only supports the `simple` OCP audio plugin, which requires a GUI. If you are using it on a Mark 1 or other device without a GUI it will probably not work.
Since this is an OCP skill, your music voice requests will automatically search your Plex music library via API. You can also search from the GUI on the OCP dashboard.

Since this is an OCP skill, your music voice requests will automatically search your Plex music library. You can also search from the GUI on the OCP dashboard.
Due to the way OCP handles intent matching, the Plex skill will return more confident results if you ask for a media type explicitly. For example you might say "play the movie Ghostbusters" to make sure you get back a movie result instead of a soundtrack or TV show. This skill also significantly boosts its confidence score if you include the word Plex in your request: "play music by Charles Mingus on Plex."

Please note that precedence matters in OCP - by default, this skill will not be highest precedence. If you want to search your Plex library before other OCP skills you'll need to configure it to have higher precedence. See the OCP documentation for more details.
_Note: The assumption with users of the Plex skill is that they would want to get Plex results by default, so the base confidence score is 95/100. Asking for Plex specifically boosts that base score to 100. While this will increase the chances of Plex results coming in first, other skills may also have high confidence scores, so results are not guaranteed._

Also, due to the way OCP handles intent matching, the Plex skill will return more confident results if you ask for a media type explicitly. For example you might say "play the movie Ghostbusters" to make sure you get back a movie result instead of a soundtrack or TV show. This skill also significantly boosts its confidence score if you include the word Plex in your request: "play music by Charles Mingus on Plex."
## Properties

_Note: The assumption with users of the Plex skill is that they would want to get Plex results by default, so the base confidence score is 75/100. Asking for Plex specifically boosts that base score to 90. While this will increase the chances of Plex results coming in first, other skills may also have high confidence scores, so results are not guaranteed._
- `token` (str): Your Plex token. This is required for the skill to work. You can find your token at [https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/).
- `base_confidence` (int): The base confidence score for this skill, expressed as a percentage. Default is 95.

## Examples

Expand All @@ -21,15 +22,15 @@ Music:
"Hey Mycroft, play music by Michael Jackson"
"Hey Mycroft, play The Who on Plex"

Movies:
Movies (not recommended on Mark 2):
"Hey Mycroft, play the movie Ghostbusters"
"Hey Mycroft, play the Ghostbusters movie on Plex"

TV (not recommended on Mark 2):
"Hey Mycroft, play the Scooby Doo series"
"Hey Mycroft, play the Ghostbusters TV show on Plex"

## Installation
## Setup

### Neon Mark 2

Expand All @@ -40,7 +41,7 @@ Update `~/.config/neon/neon.yaml` to include the following:
```yaml
skills:
default_skills:
- git+https://github.com/mikejgray/skill-plex@feat-minor-improvements
- git+https://github.com/OscillateLabsLLC/skill-plex
```
Restart `neon-skills` with `sudo systemctl restart neon-skills` or restart Neon services from the GUI.
Expand All @@ -54,9 +55,9 @@ When it loads, you will see a screen asking you to visit https://plex.tv/link an
If this is not working for some reason or you prefer to use a specific token, you can manually update the skill settings:

```shell
mkdir -p config/neon/skills/skill-plex.d-mcknight
touch ~/.config/neon/skills/skill-plex.d-mcknight/settings.json
cat <<EOF > ~/.config/neon/skills/skill-plex.d-mcknight/settings.json
mkdir -p config/neon/skills/skill-plex.oscillatelabsllc
touch ~/.config/neon/skills/skill-plex.oscillatelabsllc/settings.json
cat <<EOF > ~/.config/neon/skills/skill-plex.oscillatelabsllc/settings.json
{
"__mycroft_skill_firstrun": false,
"token": "REPLACE"
Expand All @@ -66,7 +67,7 @@ EOF

Visit [https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) for instructions on finding your Plex token.
It should look something like `ys738s6uPWXpwabc4sRYe`.
Enter your token in `~/.config/neon/skills/skill-plex.d-mcknight/settings.json` where it says `REPLACE`.
Enter your token in `~/.config/neon/skills/skill-plex.oscillatelabsllc/settings.json` where it says `REPLACE`.

Finally, `sudo systemctl restart neon-skills`.

Expand All @@ -77,17 +78,22 @@ Edit `docker/config/neon.yaml` and add a default skill:
```yaml
skills:
default-skills:
- git+https://github.com/mikejgray/skill-plex@feat-minor-improvements
- git+https://github.com/OscillateLabsLLC/skill-plex
```

Restart the `neon-skills` container, then edit `docker/xdg/config/neon/skills/skill-plex.d-mcknight/settings.json`. Add a `token` entry per the Mark 2 instructions above.
Restart the `neon-skills` container, then edit `docker/xdg/config/neon/skills/skill-plex.oscillatelabsllc/settings.json`. Add a `token` entry per the Mark 2 instructions above.

## OVOS

`pip install git+https://github.com/mikejgray/skill-plex@feat-minor-improvements`
`pip install git+https://github.com/OscillateLabsLLC/skill-plex`

If you're using containers, add the following to `~/ovos/config/skills.list`:

```config
git+https://github.com/mikejgray/skill-plex@feat-minor-improvements
git+https://github.com/OscillateLabsLLC/skill-plex
```

## Credits

- [Daniel McKnight](https://github.com/d-mcknight)
- [Oscillate Labs LLC](https://oscillatelabs.net)
23 changes: 17 additions & 6 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ def runtime_requirements(self):
no_gui_fallback=True,
)

@property
def base_confidence_score(self):
"""The base confidence score for this skill. Too low and you won't get any results."""
return self.config.get("base_confidence_score") or 95

@property
def plex_api(self) -> PlexAPI:
"""
Expand Down Expand Up @@ -114,9 +119,9 @@ def search_plex(self, phrase, media_type=MediaType.GENERIC):
:returns: list of dict search results
"""
# TODO: improved confidence calculation
confidence = 85
confidence = 95
if self.voc_match(phrase, "plex"):
confidence += 15
confidence += 5
phrase = self.remove_voc(phrase, "plex")

# Determine what kind of media to play
Expand All @@ -127,7 +132,13 @@ def search_plex(self, phrase, media_type=MediaType.GENERIC):
self.log.info("Media type for search: %s", media_type)
self.log.info("Perform a movie search? %s", movie_search)
self.log.info("Perform a tv search? %s", tv_search)
phrase = phrase.replace(" on ", "").replace("in ", "").strip()
phrase = (
phrase.replace("plex", "")
.replace("plexx", "")
.replace(" on ", "")
.replace("in ", "")
.strip()
)
playlist = Playlist(
skill_id=self.skill_id,
skill_icon=self.skill_icon,
Expand All @@ -148,8 +159,8 @@ def search_plex(self, phrase, media_type=MediaType.GENERIC):
confidence if media_type == MediaType.GENERIC else confidence + 10
)
res.skill_id = self.skill_id
res.media_type = MediaType.MUSIC
playlist.add_entry(res)
# max_confidence = sorted([res.match_confidence for res in pl], reverse=True)

# Movie search
if (
Expand All @@ -171,6 +182,7 @@ def search_plex(self, phrase, media_type=MediaType.GENERIC):
confidence if media_type == MediaType.GENERIC else confidence + 10
)
res.skill_id = self.skill_id
res.media_type = MediaType.MOVIE
playlist.add_entry(res)

# TV search
Expand All @@ -185,7 +197,6 @@ def search_plex(self, phrase, media_type=MediaType.GENERIC):
confidence if media_type == MediaType.GENERIC else confidence + 10
)
res.skill_id = self.skill_id
res.media_type = MediaType.TV
playlist.add_entry(res)
self.log.debug(playlist.as_dict)
self.log.debug(playlist.entries)
yield playlist
2 changes: 2 additions & 0 deletions plex_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def search_movies(self, query: str):
for result in results:
if isinstance(result, Movie):
movie_list.append(self._construct_movie_dict(result))
LOG.debug("Found %s movies in Plex", len(movie_list))
return movie_list

def _construct_movie_dict(self, mov):
Expand All @@ -112,6 +113,7 @@ def search_shows(self, query: str):
for result in results:
episodes = self._get_episodes_from_result(result)
show_list += [self._construct_show_dict(show) for show in episodes]
LOG.debug("Found %s TV shows in Plex", len(show_list))
return show_list

def _get_episodes_from_result(self, result):
Expand Down
42 changes: 39 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
ovos-workshop>=0.0.11
plexapi~=4.13
ovos_plugin_common_play>=0.0.6a1,<0.1.0
-i https://pypi.org/simple
certifi==2024.7.4; python_version >= '3.6'
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
click==8.1.7; python_version >= '3.7'
colour==0.1.5
combo-lock==0.2.6
filelock==3.15.4; python_version >= '3.8'
idna==3.7; python_version >= '3.5'
json-database==0.7.0
kthread==0.2.3
markdown-it-py==3.0.0; python_version >= '3.8'
mdurl==0.1.2; python_version >= '3.7'
memory-tempfile==2.2.3; python_version >= '3.6' and python_version < '4.0'
orjson==3.10.6; python_version >= '3.8'
ovos-backend-client==0.1.0
ovos-bus-client==0.0.9a29
ovos-config==0.0.12
ovos-lingua-franca==0.4.7
ovos-utils==0.0.38
ovos-workshop==0.0.16a48
pexpect==4.9.0
plexapi==4.15.15; python_version >= '3.8'
ptyprocess==0.7.0
pyee==8.2.2
pygments==2.18.0; python_version >= '3.8'
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
pyyaml==6.0.1; python_version >= '3.6'
quebra-frases==0.3.7
rapidfuzz==3.9.5; python_version >= '3.8'
regex==2024.7.24; python_version >= '3.8'
requests==2.32.3; python_version >= '3.8'
rich==13.7.1; python_full_version >= '3.7.0'
rich-click==1.8.3; python_version >= '3.7'
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
typing-extensions==4.12.2; python_version >= '3.8'
urllib3==2.2.2; python_version >= '3.8'
watchdog==4.0.1; python_version >= '3.8'
webcolors==24.6.0; python_version >= '3.8'
websocket-client==1.8.0; python_version >= '3.8'
24 changes: 3 additions & 21 deletions skill.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,6 @@
"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://www.plex.tv/wp-content/themes/plex/assets/img/plex-logo.svg",
Expand All @@ -48,9 +29,10 @@
],
"credits": [
"NeonGeckoCom",
"NeonDaniel"
"NeonDaniel",
"mikejgray"
],
"skillname": "skill-plex",
"authorname": "d-mcknight",
"authorname": "OscillateLabsLLC",
"foldername": null
}
5 changes: 3 additions & 2 deletions tests/test_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from os.path import join, dirname, exists
from unittest.mock import Mock
from ovos_utils.messagebus import FakeBus

from ovos_workshop.skill_launcher import SkillLoader

bus = FakeBus()
Expand All @@ -15,7 +14,9 @@ class TestSkill(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
bus.run_in_thread()
skill_loader = SkillLoader(bus=bus, skill_directory=dirname(dirname(__file__)), skill_id="PlexSkill")
skill_loader = SkillLoader(
bus=bus, skill_directory=dirname(dirname(__file__)), skill_id="PlexSkill"
)
# TODO: Mock the PlexAPI class to prevent network calls
skill_loader.load()
cls.skill = skill_loader.instance
Expand Down

0 comments on commit 5d001fc

Please sign in to comment.