def list_items(lib, query, album, path, fmt): """Print out items in lib matching query. If album, then search for albums instead of single items. If path, print the matched objects' paths instead of human-readable information about them. """ if fmt is None: # If no specific template is supplied, use a default. if album: fmt = u'$albumartist - $album' else: fmt = u'$artist - $album - $title' template = Template(fmt) if album: for album in lib.albums(query): if path: print_(album.item_dir()) elif fmt is not None: print_(template.substitute(album._record)) else: for item in lib.items(query): if path: print_(item.path) elif fmt is not None: print_(template.substitute(item.record))
def _get_path_formats(config): """Returns a list of path formats (query/template pairs); reflecting the config's specified path formats. """ legacy_path_format = config_val(config, 'beets', 'path_format', None) if legacy_path_format: # Old path formats override the default values. path_formats = [(library.PF_KEY_DEFAULT, Template(legacy_path_format))] else: # If no legacy path format, use the defaults instead. path_formats = DEFAULT_PATH_FORMATS if config.has_section('paths'): custom_path_formats = [] for key, value in config.items('paths', True): if key in PF_KEY_QUERIES: # Special values that indicate simple queries. key = PF_KEY_QUERIES[key] elif key != library.PF_KEY_DEFAULT: # For non-special keys (literal queries), the _ # character denotes a :. key = key.replace('_', ':') custom_path_formats.append((key, Template(value))) path_formats = custom_path_formats + path_formats return path_formats
def list_items(lib, query, album, path, fmt): """Print out items in lib matching query. If album, then search for albums instead of single items. If path, print the matched objects' paths instead of human-readable information about them. """ template = Template(fmt) if album: for album in lib.albums(query): if path: print_(album.item_dir()) elif fmt is not None: print_(album.evaluate_template(template)) else: for item in lib.items(query): if path: print_(item.path) elif fmt is not None: print_(item.evaluate_template(template, lib))
def destination(self, item, pathmod=None, in_album=False, fragment=False, basedir=None): """Returns the path in the library directory designated for item item (i.e., where the file ought to be). in_album forces the item to be treated as part of an album. fragment makes this method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of encoded as a bytestring. basedir can override the library's base directory for the destination. """ pathmod = pathmod or os.path # Use a path format based on a query, falling back on the # default. for query, path_format in self.path_formats: if query == PF_KEY_DEFAULT: continue query = AndQuery.from_string(query) if in_album: # If we're treating this item as a member of the item, # hack the query so that singleton queries always # observe the item to be non-singleton. for i, subquery in enumerate(query): if isinstance(subquery, SingletonQuery): query[i] = FalseQuery() if subquery.sense else TrueQuery() if query.match(item): # The query matches the item! Use the corresponding path # format. break else: # No query matched; fall back to default. for query, path_format in self.path_formats: if query == PF_KEY_DEFAULT: break else: assert False, "no default path format" subpath_tmpl = Template(path_format) # Get the item's Album if it has one. album = self.get_album(item) # Build the mapping for substitution in the path template, # beginning with the values from the database. mapping = {} for key in ITEM_KEYS_META: # Get the values from either the item or its album. if key in ALBUM_KEYS_ITEM and album is not None: # From album. value = getattr(album, key) else: # From Item. value = getattr(item, key) mapping[key] = util.sanitize_for_path(value, pathmod, key) # Use the album artist if the track artist is not set and # vice-versa. if not mapping["artist"]: mapping["artist"] = mapping["albumartist"] if not mapping["albumartist"]: mapping["albumartist"] = mapping["artist"] # Get values from plugins. for key, value in plugins.template_values(item).iteritems(): mapping[key] = util.sanitize_for_path(value, pathmod, key) # Perform substitution. funcs = DefaultTemplateFunctions(self, item).functions() funcs.update(plugins.template_funcs()) subpath = subpath_tmpl.substitute(mapping, funcs) # Encode for the filesystem, dropping unencodable characters. if isinstance(subpath, unicode) and not fragment: encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() subpath = subpath.encode(encoding, "replace") # Truncate components and remove forbidden characters. subpath = util.sanitize_path(subpath, pathmod, self.replacements) # Preserve extension. _, extension = pathmod.splitext(item.path) subpath += extension.lower() if fragment: return subpath else: basedir = basedir or self.directory return normpath(os.path.join(basedir, subpath))
# Constants. CONFIG_PATH_VAR = 'BEETSCONFIG' DEFAULT_CONFIG_FILENAME_UNIX = '.beetsconfig' DEFAULT_CONFIG_FILENAME_WINDOWS = 'beetsconfig.ini' DEFAULT_LIBRARY_FILENAME_UNIX = '.beetsmusic.blb' DEFAULT_LIBRARY_FILENAME_WINDOWS = 'beetsmusic.blb' DEFAULT_DIRECTORY_NAME = 'Music' WINDOWS_BASEDIR = os.environ.get('APPDATA') or '~' PF_KEY_QUERIES = { 'comp': 'comp:true', 'singleton': 'singleton:true', } DEFAULT_PATH_FORMATS = [ (library.PF_KEY_DEFAULT, Template('$albumartist/$album%aunique{}/$track $title')), (PF_KEY_QUERIES['singleton'], Template('Non-Album/$artist/$title')), (PF_KEY_QUERIES['comp'], Template('Compilations/$album%aunique{}/$track $title')), ] DEFAULT_ART_FILENAME = 'cover' DEFAULT_TIMEOUT = 5.0 NULL_REPLACE = '<strip>' # UI exception. Commands should throw this in order to display # nonrecoverable errors to the user. class UserError(Exception): pass
def destination(self, item, pathmod=None, in_album=False, fragment=False, basedir=None): """Returns the path in the library directory designated for item item (i.e., where the file ought to be). in_album forces the item to be treated as part of an album. fragment makes this method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of encoded as a bytestring. basedir can override the library's base directory for the destination. """ pathmod = pathmod or os.path # Use a path format based on a query, falling back on the # default. for query, path_format in self.path_formats: if query == PF_KEY_DEFAULT: continue query = AndQuery.from_string(query) if in_album: # If we're treating this item as a member of the item, # hack the query so that singleton queries always # observe the item to be non-singleton. for i, subquery in enumerate(query): if isinstance(subquery, SingletonQuery): query[i] = FalseQuery() if subquery.sense \ else TrueQuery() if query.match(item): # The query matches the item! Use the corresponding path # format. break else: # No query matched; fall back to default. for query, path_format in self.path_formats: if query == PF_KEY_DEFAULT: break else: assert False, "no default path format" subpath_tmpl = Template(path_format) # Get the item's Album if it has one. album = self.get_album(item) # Build the mapping for substitution in the path template, # beginning with the values from the database. mapping = {} for key in ITEM_KEYS_META: # Get the values from either the item or its album. if key in ALBUM_KEYS_ITEM and album is not None: # From album. value = getattr(album, key) else: # From Item. value = getattr(item, key) mapping[key] = util.sanitize_for_path(value, pathmod, key) # Use the album artist if the track artist is not set and # vice-versa. if not mapping['artist']: mapping['artist'] = mapping['albumartist'] if not mapping['albumartist']: mapping['albumartist'] = mapping['artist'] # Get values from plugins. for key, value in plugins.template_values(item).iteritems(): mapping[key] = util.sanitize_for_path(value, pathmod, key) # Perform substitution. funcs = DefaultTemplateFunctions(self, item).functions() funcs.update(plugins.template_funcs()) subpath = subpath_tmpl.substitute(mapping, funcs) # Encode for the filesystem, dropping unencodable characters. if isinstance(subpath, unicode) and not fragment: encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() subpath = subpath.encode(encoding, 'replace') # Truncate components and remove forbidden characters. subpath = util.sanitize_path(subpath, pathmod, self.replacements) # Preserve extension. _, extension = pathmod.splitext(item.path) subpath += extension.lower() if fragment: return subpath else: basedir = basedir or self.directory return normpath(os.path.join(basedir, subpath))