Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

playlist: Add auto-update functionality and more tests #3151

Merged
merged 14 commits into from
Feb 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions beetsplug/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import os
import fnmatch
import tempfile
import beets


Expand Down Expand Up @@ -86,6 +87,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