Skip to content

Commit

Permalink
Merge pull request #1120 from xsteadfastx/master
Browse files Browse the repository at this point in the history
Added plexupdate plugin for refreshing plex music library after importing music
  • Loading branch information
sampsyo committed Nov 29, 2014
2 parents 903e88a + 77f8eff commit a60b151
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
84 changes: 84 additions & 0 deletions beetsplug/plexupdate.py
Original file line number Diff line number Diff line change
@@ -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://{0}:{1}'.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/{0}/refresh'.format(section_key)
url = urljoin('http://{0}:{1}'.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.')
6 changes: 6 additions & 0 deletions docs/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
-------------

Expand Down
35 changes: 35 additions & 0 deletions docs/plugins/plexupdate.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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.

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

To use the ``plexupdate`` plugin you need to install the `requests`_ library with:

pip install requests

With that all in place, you'll see beets send the "update" command to your Plex
server every time you change your beets library.

.. _Plex: http://plex.tv/
.. _requests: http://docs.python-requests.org/en/latest/

Configuration
-------------

The available options under the ``plex:`` section are:

- **host**: The Plex server name.
Default: ``localhost``.
- **port**: The Plex server port.
Default: 32400.
106 changes: 106 additions & 0 deletions test/test_plexupdate.py
Original file line number Diff line number Diff line change
@@ -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 = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<MediaContainer size="3" allowSync="0" '
'identifier="com.plexapp.plugins.library" '
'mediaTagPrefix="/system/bundle/media/flags/" '
'mediaTagVersion="1413367228" title1="Plex Library">'
'<Directory allowSync="0" art="/:/resources/movie-fanart.jpg" '
'filters="1" refreshing="0" thumb="/:/resources/movie.png" '
'key="3" type="movie" title="Movies" '
'composite="/library/sections/3/composite/1416232668" '
'agent="com.plexapp.agents.imdb" scanner="Plex Movie Scanner" '
'language="de" uuid="92f68526-21eb-4ee2-8e22-d36355a17f1f" '
'updatedAt="1416232668" createdAt="1415720680">'
'<Location id="3" path="/home/marv/Media/Videos/Movies" />'
'</Directory>'
'<Directory allowSync="0" art="/:/resources/artist-fanart.jpg" '
'filters="1" refreshing="0" thumb="/:/resources/artist.png" '
'key="2" type="artist" title="Music" '
'composite="/library/sections/2/composite/1416929243" '
'agent="com.plexapp.agents.lastfm" scanner="Plex Music Scanner" '
'language="en" uuid="90897c95-b3bd-4778-a9c8-1f43cb78f047" '
'updatedAt="1416929243" createdAt="1415691331">'
'<Location id="2" path="/home/marv/Media/Musik" />'
'</Directory>'
'<Directory allowSync="0" art="/:/resources/show-fanart.jpg" '
'filters="1" refreshing="0" thumb="/:/resources/show.png" '
'key="1" type="show" title="TV Shows" '
'composite="/library/sections/1/composite/1416320800" '
'agent="com.plexapp.agents.thetvdb" scanner="Plex Series Scanner" '
'language="de" uuid="04d2249b-160a-4ae9-8100-106f4ec1a218" '
'updatedAt="1416320800" createdAt="1415690983">'
'<Location id="1" path="/home/marv/Media/Videos/Series" />'
'</Directory>'
'</MediaContainer>')
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')

0 comments on commit a60b151

Please sign in to comment.