From b25393151d90ee2e2bfad0f4727935c25c4acc41 Mon Sep 17 00:00:00 2001 From: Marvin Steadfast Date: Fri, 28 Nov 2014 11:19:17 +0100 Subject: [PATCH] Added plexupdate plugin for refreshing plex music library after importing music. --- beetsplug/plexupdate.py | 84 ++++++++++++++++++++++++++++ docs/plugins/index.rst | 6 ++ docs/plugins/plexupdate.rst | 30 ++++++++++ test/test_plexupdate.py | 106 ++++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 beetsplug/plexupdate.py create mode 100644 docs/plugins/plexupdate.rst create mode 100644 test/test_plexupdate.py diff --git a/beetsplug/plexupdate.py b/beetsplug/plexupdate.py new file mode 100644 index 000000000..3bfcba4c0 --- /dev/null +++ b/beetsplug/plexupdate.py @@ -0,0 +1,84 @@ +"""Updates an Plex library whenever the beets library is changed. + +Put something like the following in your config.yaml to configure: + plex: + host: localhost + port: 32400 +""" +import requests +from urlparse import urljoin +import xml.etree.ElementTree as ET +from beets import config +from beets.plugins import BeetsPlugin + + +# Global variable to detect if database is changed that the update +# is only run once before beets exists. +database_changed = False + + +def get_music_section(host, port): + """Getting the section key for the music library in Plex. + """ + api_endpoint = 'library/sections' + url = urljoin('http://{}:{}'.format(host, port), api_endpoint) + + # Sends request. + r = requests.get(url) + + # Parse xml tree and extract music section key. + tree = ET.fromstring(r.text) + for child in tree.findall('Directory'): + if child.get('title') == 'Music': + return child.get('key') + + +def update_plex(host, port): + """Sends request to the Plex api to start a library refresh. + """ + # Getting section key and build url. + section_key = get_music_section(host, port) + api_endpoint = 'library/sections/{}/refresh'.format(section_key) + url = urljoin('http://{}:{}'.format(host, port), api_endpoint) + + # Sends request and returns requests object. + r = requests.get(url) + return r + + +class PlexUpdate(BeetsPlugin): + def __init__(self): + super(PlexUpdate, self).__init__() + + # Adding defaults. + config['plex'].add({ + u'host': u'localhost', + u'port': 32400}) + + +@PlexUpdate.listen('database_change') +def listen_for_db_change(lib=None): + """Listens for beets db change and set global database_changed + variable to True. + """ + global database_changed + database_changed = True + + +@PlexUpdate.listen('cli_exit') +def update(lib=None): + """When the client exists and the database_changed variable is True + trying to send refresh request to Plex server. + """ + if database_changed: + print('Updating Plex library...') + + # Try to send update request. + try: + update_plex( + config['plex']['host'].get(), + config['plex']['port'].get()) + print('... started.') + + except requests.exceptions.RequestException: + print('Update failed.') diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 30bddd115..cd76aa5d0 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -62,6 +62,7 @@ Each plugin has its own set of options that can be defined in a section bearing mpdupdate permissions play + plexupdate random replaygain rewrite @@ -131,8 +132,13 @@ Interoperability * :doc:`mpdupdate`: Automatically notifies `MPD`_ whenever the beets library changes. * :doc:`play`: Play beets queries in your music player. +* :doc:`plexupdate`: Automatically notifies `Plex`_ whenever the beets library + changes. * :doc:`smartplaylist`: Generate smart playlists based on beets queries. + +.. _Plex: http://plex.tv + Miscellaneous ------------- diff --git a/docs/plugins/plexupdate.rst b/docs/plugins/plexupdate.rst new file mode 100644 index 000000000..b051f24b5 --- /dev/null +++ b/docs/plugins/plexupdate.rst @@ -0,0 +1,30 @@ +PlexUpdate Plugin +================= + +``plexupdate`` is a very simple plugin for beets that lets you automatically +update `Plex`_'s music library whenever you change your beets library. + +.. _Plex: http://plex.tv/ + +To use ``plexupdate`` plugin, enable it in your configuration +(see :ref:`using-plugins`). +Then, you'll probably want to configure the specifics of your Plex server. +You can do that using an ``plex:`` section in your ``config.yaml``, +which looks like this:: + + plex: + host: localhost + port: 32400 + +With that all in place, you'll see beets send the "update" command to your Plex +server every time you change your beets library. + +Configuration +------------- + +The available options under the ``plex:`` section are: + +- **host**: The Plex server name. + Default: ``localhost``. +- **port**: The Plex server port. + Default: 32400. diff --git a/test/test_plexupdate.py b/test/test_plexupdate.py new file mode 100644 index 000000000..9bdea386a --- /dev/null +++ b/test/test_plexupdate.py @@ -0,0 +1,106 @@ +from _common import unittest +from helper import TestHelper +from beetsplug.plexupdate import get_music_section, update_plex +import responses + + +class PlexUpdateTest(unittest.TestCase, TestHelper): + def add_response_get_music_section(self): + """Create response for mocking the get_music_section function. + """ + body = ( + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '') + status = 200 + content_type = 'text/xml;charset=utf-8' + + responses.add(responses.GET, + 'http://localhost:32400/library/sections', + body=body, + status=status, + content_type=content_type) + + def add_response_update_plex(self): + """Create response for mocking the update_plex function. + """ + body = '' + status = 200 + content_type = 'text/html' + + responses.add(responses.GET, + 'http://localhost:32400/library/sections/2/refresh', + body=body, + status=status, + content_type=content_type) + + def setUp(self): + self.setup_beets() + self.load_plugins('plexupdate') + + self.config['plex'] = { + u'host': u'localhost', + u'port': 32400} + + def tearDown(self): + self.teardown_beets() + self.unload_plugins() + + @responses.activate + def test_get_music_section(self): + # Adding response. + self.add_response_get_music_section() + + # Test if section key is "2" out of the mocking data. + self.assertEqual(get_music_section( + self.config['plex']['host'], + self.config['plex']['port']), '2') + + @responses.activate + def test_update_plex(self): + # Adding responses. + self.add_response_get_music_section() + self.add_response_update_plex() + + # Testing status code of the mocking request. + self.assertEqual(update_plex( + self.config['plex']['host'], + self.config['plex']['port']).status_code, 200) + + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + + +if __name__ == '__main__': + unittest.main(defaultTest='suite')