Skip to content

Commit 8a13659

Browse files
committed
Initial version
1 parent aadf091 commit 8a13659

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

.travis.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
language: python
2+
python:
3+
- '3.6'
4+
install:
5+
- pip install --upgrade pip
6+
- pip install -e .
7+
deploy:
8+
provider: pypi
9+
user: cumbottler
10+
distributions: sdist bdist_wheel
11+
on:
12+
tags: true
13+
password:
14+
secure: HzwZsiRgHVkQ4P3v4A/xcSEj3NdWM/ek2BF2rsfKeXCIUzUNhtevRw6xtej8dzFu432g2EYTG7B3X8HyvjtFyIy0q4ZLjja1Y7nFj4jtOqETcq1ksTeRTpV9wR+icFyHVPdu6Hx5od45O0b8YeIrEDz84s1s9HdrWa9rrXh0jzCmrMag3QZ2enb2PYFjYsHnN7R0Zcb8CiXMQjz5v8/IxaZ4uY7hQL7zskIpcVUrdXHcnyRcAqTnM858dF4TTCpl0e/zH2FiRataSsDWT91xnpw7k4R1LhpoXkOJERFgRHNi8fKNlTRB6fH+uIXat9dNEvATtqKAk3wKHvKV0kI4JCCAuoexUBxXS0VQnKOyWp1wdVuZGHlxx9BSVLfcj9OZDul0i+Zx5yEb1N4zQ9FzI4cil8XpJB2LTw9syToRAslKAlAFgqS3ipdZbJJPW0LrY31mY9ZGDRskodOjJILIeFwHcgUbMxNZEvMfgBZqS+Xx1vFpTjHiwAtnRMyReZ9Bm1iKN25Elnrh8caxj8m4IvqCBi45OSEPmk7vOg6vFztEkMWt3KMyPk8nZQ4UPnmz4cyL/4xbONu+TYucTyFy+Y//D5zjn2ZrBaAU/uCQNrdZdkkR5ft50JgMGabPacPpJ0sazHKFHTi1q6qil4IeHcq+Z5lZSjCARio1LJRy8u4=

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# beets-mpdqueue
2+
3+
## Description
4+
5+
beets-mpdqueue is a [Beets](http://beets.io/) plugin to add files imported to the Beets library to [Music Player Daemon](https://www.musicpd.org/) queue, so you can start playing your new music immediately.
6+
7+
## Usage
8+
9+
To use the plugin, enable it in the Beets configuration by loading the `mpdqueue` plugin (see [Using Plugins](https://beets.readthedocs.io/en/latest/plugins/index.html#using-plugins) in the Beets documentation). After you have done this, your newly imported files are always added to the end of the MPD queue automatically.
10+
11+
One important thing to note is that this plugin does not do anything when you reimport Beets library items.
12+
13+
### Configuration
14+
15+
The only configuration for this plugin are the MPD server address, port and password, which are configured the same way as with the [MPDUpdate](https://beets.readthedocs.io/en/latest/plugins/mpdupdate.html) plugin included with Beets.
16+
17+
mpd:
18+
host: localhost
19+
port: 6600
20+
password: seekrit

beetsplug/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from pkgutil import extend_path
2+
__path__ = extend_path(__path__, __name__)

beetsplug/mpdqueue.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""Beets plugin to add imported files to Music Player Daemon queue.
2+
3+
This plugin listens for Beets import events and adds all imported items at the
4+
end of the server queue at the end of Beets program execution.
5+
6+
This plugin uses the same configuration as the MPDUpdate plugin included with
7+
Beets to connect to a server.
8+
9+
mpd:
10+
host: localhost
11+
port: 6600
12+
password: seekrit
13+
14+
Compatibility with Python 2.7 or lower not tested or guaranteed.
15+
"""
16+
17+
# pylint: disable=unused-argument
18+
19+
from time import sleep
20+
import os
21+
import socket
22+
23+
from beets import config
24+
from beets.plugins import BeetsPlugin
25+
26+
27+
class MusicPlayerDaemonClient():
28+
"""Simple socket client to provide connectivity to a Music Player Daemon
29+
server for updates and queue management.
30+
"""
31+
32+
def __init__(self, host='localhost', port=6600, password=None):
33+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
34+
self.sock.connect((host, port))
35+
self.sock.settimeout(0.25)
36+
acknowledgement = self._read()[0]
37+
if not acknowledgement.startswith('OK MPD'):
38+
self.close()
39+
if password:
40+
self._send('password "{}"'.format(password))
41+
response = self._read()[0]
42+
if not response.startswith('OK'):
43+
self.close()
44+
45+
def _read(self):
46+
"""Reads data from the server. Returns the sent lines as an array of strings.
47+
48+
This operation may return an empty list if the server does not send
49+
anything during the socket timeout period.
50+
"""
51+
data = b''
52+
while True:
53+
try:
54+
data_buffer = self.sock.recv(1024)
55+
except socket.timeout:
56+
break
57+
if not data_buffer:
58+
break
59+
data += data_buffer
60+
return data.decode().strip().split('\n')
61+
62+
def _send(self, data):
63+
"""Sends a string to the server."""
64+
if data[-1] != '\n':
65+
data = ''.join([data, '\n'])
66+
self.sock.send(data.encode())
67+
68+
def add(self, path):
69+
"""Adds given path to MPD queue."""
70+
self._send('add "{}"'.format(path))
71+
return self._read()[0] == 'OK'
72+
73+
def close(self):
74+
"""Closes the connection to the server."""
75+
self._send('close')
76+
self.sock.close()
77+
78+
def status(self):
79+
"""Fetches status from Music Player Daemon. Returns a tuple of strings.
80+
"""
81+
self._send('status')
82+
return self._read()
83+
84+
def update(self, directory):
85+
"""Updates the MPD database with items from the given directory.
86+
87+
Blocks until the update is finished.
88+
"""
89+
self._send('update "{}"'.format(directory))
90+
response = self._read()
91+
if response[-1] != 'OK':
92+
return
93+
while True:
94+
updating = False
95+
for line in self.status():
96+
if line.startswith('updating_db'):
97+
updating = True
98+
if not updating:
99+
break
100+
sleep(0.5)
101+
102+
103+
class MPDQueuePlugin(BeetsPlugin):
104+
"""Beets plugin that generates a list of imported files after each import
105+
task (import_task_files) and imports them to MPD at the end of program
106+
execution (cli_exit).
107+
"""
108+
109+
def __init__(self):
110+
super(MPDQueuePlugin, self).__init__()
111+
config['mpd']['password'].redact = True
112+
113+
self.files = []
114+
115+
self.register_listener('import_task_files', self.import_task_files)
116+
self.register_listener('cli_exit', self.update_queue)
117+
118+
def import_task_files(self, task, session):
119+
"""Track all the files added during an import operation so they can be
120+
later added to the queue when beets exits.
121+
122+
This operation skips all import tasks that do not have a `toppath`
123+
property as it indicates a reimport of existing library files.
124+
"""
125+
if not task.toppath:
126+
self._log.debug(u'Skipping library re-import')
127+
return
128+
129+
tracks = []
130+
items = sorted(task.items, key=lambda x: x.track)
131+
for item in items:
132+
destination = item.destination(fragment=True)
133+
self._log.debug(u'{0} will be added to queue', destination)
134+
tracks.append(destination)
135+
self.files += tracks
136+
137+
def update_queue(self, lib):
138+
"""Updates the MPD queue with the files added to `self.files` array.
139+
140+
In order for the files to be added successfully to MPD, the database
141+
must be first populated with them. Thus a set of all the directories
142+
for the imported files is first created and then imported into MPD
143+
one-by-one. The update operation is blocking to guarantee that the
144+
files are in the database when they are added.
145+
"""
146+
if not self.files:
147+
self._log.debug(u'No files to add to queue')
148+
return
149+
150+
host = config['mpd']['host'].get()
151+
port = config['mpd']['port'].get(int)
152+
password = config['mpd']['password'].get()
153+
client = MusicPlayerDaemonClient(host, port, password)
154+
155+
directories = set()
156+
for file_ in self.files:
157+
directories.add(os.path.dirname(file_))
158+
for directory in directories:
159+
self._log.debug(u'Updating directory {0}', directory)
160+
client.update(directory)
161+
self._log.debug(u'Finished updating {0}', directory)
162+
163+
for file_ in self.files:
164+
self._log.debug(u'Adding {0} to queue', file_)
165+
success = client.add(file_)
166+
if success:
167+
self._log.debug(u'Added {0} to queue', file_)
168+
else:
169+
self._log.warning(u'Failed to add {0} to queue', file_)
170+
client.close()

setup.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from setuptools import setup, find_packages
2+
3+
4+
setup(
5+
name="beets-mpdqueue",
6+
version="0.1",
7+
packages=find_packages(),
8+
)

0 commit comments

Comments
 (0)