# This file is part of beets. # Copyright 2015, Dang Mai . # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Generates smart playlists based on beets queries. """ from __future__ import print_function from beets.plugins import BeetsPlugin from beets import config, ui, library from beets.util import normpath, syspath import os def _items_for_query(lib, playlist, album=False): """Get the matching items for a playlist's configured queries. `album` indicates whether to process the item-level query or the album-level query (if any). """ key = 'album_query' if album else 'query' if key not in playlist: return [] # Parse quer(ies). If it's a list, perform the queries and manually # concatenate the results query_strings = playlist[key] if not isinstance(query_strings, (list, tuple)): query_strings = [query_strings] model = library.Album if album else library.Item results = [] for q in query_strings: query, sort = library.parse_query_string(q, model) if album: new = lib.albums(query, sort) else: new = lib.items(query, sort) results.extend(new) return results class SmartPlaylistPlugin(BeetsPlugin): def __init__(self): super(SmartPlaylistPlugin, self).__init__() self.config.add({ 'relative_to': None, 'playlist_dir': u'.', 'auto': True, 'playlists': [] }) if self.config['auto']: self.register_listener('database_change', self.db_change) def commands(self): def update(lib, opts, args): self.update_playlists(lib) spl_update = ui.Subcommand('splupdate', help='update the smart playlists') spl_update.func = update return [spl_update] def db_change(self, lib): self.register_listener('cli_exit', self.update_playlists) def update_playlists(self, lib): self._log.info("Updating smart playlists...") playlists = self.config['playlists'].get(list) playlist_dir = self.config['playlist_dir'].as_filename() relative_to = self.config['relative_to'].get() if relative_to: relative_to = normpath(relative_to) for playlist in playlists: items = [] items.extend(_items_for_query(lib, playlist, True)) items.extend(_items_for_query(lib, playlist, False)) m3us = {} basename = playlist['name'].encode('utf8') # As we allow tags in the m3u names, we'll need to iterate through # the items and generate the correct m3u file names. for item in items: m3u_name = item.evaluate_template(basename, True) if not (m3u_name in m3us): m3us[m3u_name] = [] item_path = item.path if relative_to: item_path = os.path.relpath(item.path, relative_to) if item_path not in m3us[m3u_name]: m3us[m3u_name].append(item_path) # Now iterate through the m3us that we need to generate for m3u in m3us: m3u_path = normpath(os.path.join(playlist_dir, m3u)) with open(syspath(m3u_path), 'w') as f: for path in m3us[m3u]: f.write(path + '\n') self._log.info("... Done")