Skip to content

Commit

Permalink
Merge pull request #3151 from Holzhaus/playlist-plugin-additions
Browse files Browse the repository at this point in the history
playlist: Add auto-update functionality and more tests
  • Loading branch information
sampsyo authored Feb 18, 2019
2 parents 9320db2 + 4ba5dfa commit 81c5ae3
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 16 deletions.
94 changes: 94 additions & 0 deletions beetsplug/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import os
import fnmatch
import tempfile
import beets


Expand Down Expand Up @@ -82,6 +83,99 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin):
def __init__(self):
super(PlaylistPlugin, self).__init__()
self.config.add({
'auto': False,
'playlist_dir': '.',
'relative_to': 'library',
})

self.playlist_dir = self.config['playlist_dir'].as_filename()
self.changes = {}

if self.config['relative_to'].get() == 'library':
self.relative_to = beets.util.bytestring_path(
beets.config['directory'].as_filename())
elif self.config['relative_to'].get() != 'playlist':
self.relative_to = beets.util.bytestring_path(
self.config['relative_to'].as_filename())
else:
self.relative_to = None

if self.config['auto']:
self.register_listener('item_moved', self.item_moved)
self.register_listener('item_removed', self.item_removed)
self.register_listener('cli_exit', self.cli_exit)

def item_moved(self, item, source, destination):
self.changes[source] = destination

def item_removed(self, item):
if not os.path.exists(beets.util.syspath(item.path)):
self.changes[item.path] = None

def cli_exit(self, lib):
for playlist in self.find_playlists():
self._log.info('Updating playlist: {0}'.format(playlist))
base_dir = beets.util.bytestring_path(
self.relative_to if self.relative_to
else os.path.dirname(playlist)
)

try:
self.update_playlist(playlist, base_dir)
except beets.util.FilesystemError:
self._log.error('Failed to update playlist: {0}'.format(
beets.util.displayable_path(playlist)))

def find_playlists(self):
"""Find M3U playlists in the playlist directory."""
try:
dir_contents = os.listdir(beets.util.syspath(self.playlist_dir))
except OSError:
self._log.warning('Unable to open playlist directory {0}'.format(
beets.util.displayable_path(self.playlist_dir)))
return

for filename in dir_contents:
if fnmatch.fnmatch(filename, '*.[mM]3[uU]'):
yield os.path.join(self.playlist_dir, filename)

def update_playlist(self, filename, base_dir):
"""Find M3U playlists in the specified directory."""
changes = 0
deletions = 0

with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as tempfp:
new_playlist = tempfp.name
with open(filename, mode='rb') as fp:
for line in fp:
original_path = line.rstrip(b'\r\n')

# Ensure that path from playlist is absolute
is_relative = not os.path.isabs(line)
if is_relative:
lookup = os.path.join(base_dir, original_path)
else:
lookup = original_path

try:
new_path = self.changes[beets.util.normpath(lookup)]
except KeyError:
tempfp.write(line)
else:
if new_path is None:
# Item has been deleted
deletions += 1
continue

changes += 1
if is_relative:
new_path = os.path.relpath(new_path, base_dir)

tempfp.write(line.replace(original_path, new_path))

if changes or deletions:
self._log.info(
'Updated playlist {0} ({1} changes, {2} deletions)'.format(
filename, changes, deletions))
beets.util.copy(new_playlist, filename, replace=True)
beets.util.remove(new_playlist)
9 changes: 9 additions & 0 deletions docs/plugins/playlist.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ To use it, enable the ``playlist`` plugin in your configuration
Then configure your playlists like this::

playlist:
auto: no
relative_to: ~/Music
playlist_dir: ~/.mpd/playlists

Expand All @@ -22,13 +23,21 @@ name::

$ beet ls playlist:anotherplaylist

The plugin can also update playlists in the playlist directory automatically
every time an item is moved or deleted. This can be controlled by the ``auto``
configuration option.

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

To configure the plugin, make a ``smartplaylist:`` section in your
configuration file. In addition to the ``playlists`` described above, the
other configuration options are:

- **auto**: If this is set to ``yes``, then anytime an item in the library is
moved or removed, the plugin will update all playlists in the
``playlist_dir`` directory that contain that item to reflect the change.
Default: ``no``
- **playlist_dir**: Where to read playlist files from.
Default: The current working directory (i.e., ``'.'``).
- **relative_to**: Interpret paths in the playlist files relative to a base
Expand Down
Loading

0 comments on commit 81c5ae3

Please sign in to comment.