mirror of
https://github.com/beetbox/beets.git
synced 2026-01-08 00:45:55 +01:00
Added plexupdate plugin for refreshing plex music library after importing music.
This commit is contained in:
parent
903e88a228
commit
b25393151d
4 changed files with 226 additions and 0 deletions
84
beetsplug/plexupdate.py
Normal file
84
beetsplug/plexupdate.py
Normal file
|
|
@ -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.')
|
||||
|
|
@ -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
|
||||
-------------
|
||||
|
||||
|
|
|
|||
30
docs/plugins/plexupdate.rst
Normal file
30
docs/plugins/plexupdate.rst
Normal file
|
|
@ -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.
|
||||
106
test/test_plexupdate.py
Normal file
106
test/test_plexupdate.py
Normal file
|
|
@ -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')
|
||||
Loading…
Reference in a new issue