mirror of
https://github.com/beetbox/beets.git
synced 2026-01-03 22:42:44 +01:00
merge
This commit is contained in:
commit
f48fefdf61
33 changed files with 2727 additions and 100 deletions
|
|
@ -2,3 +2,4 @@
|
|||
^beets\.egg-info/
|
||||
^build/
|
||||
^MANIFEST$
|
||||
^docs/_build/
|
||||
|
|
|
|||
3
NEWS
3
NEWS
|
|
@ -28,11 +28,14 @@
|
|||
* A new plugin, called "web", encapsulates a simple Web-based GUI for
|
||||
beets. The current iteration can browse the library and play music
|
||||
in browsers that support HTML5 Audio.
|
||||
* When moving items that are part of an album, the album art implicitly
|
||||
moves too.
|
||||
* Files are no longer silently overwritten when moving and copying files.
|
||||
* Handle exceptions thrown when running Mutagen.
|
||||
* Fix a missing __future__ import in embedart on Python 2.5.
|
||||
* Fix ID3 and MPEG-4 tag names for the album-artist field.
|
||||
* Fix Unicode encoding of album artist, album type, and label.
|
||||
* Fix crash when "copying" an art file that's already in place.
|
||||
|
||||
1.0b9
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ imagine for your music collection. Via `plugins`_, beets becomes a panacea:
|
|||
If beets doesn't do what you want yet, `writing your own plugin`_ is
|
||||
shockingly simple if you know a little Python.
|
||||
|
||||
.. _plugins: http://code.google.com/p/beets/wiki/Plugins
|
||||
.. _plugins: http://beets.readthedocs.org/en/latest/plugins/
|
||||
.. _MPD: http://mpd.wikia.com/
|
||||
.. _MusicBrainz music collection: http://musicbrainz.org/show/collection/
|
||||
.. _writing your own plugin:
|
||||
http://code.google.com/p/beets/wiki/Plugins#Writing_Plugins
|
||||
http://beets.readthedocs.org/en/latest/plugins/#writing-plugins
|
||||
|
||||
Read More
|
||||
---------
|
||||
|
|
@ -44,7 +44,7 @@ Check out the `Getting Started`_ guide to learn about installing and using
|
|||
beets.
|
||||
|
||||
.. _its Web site: http://beets.radbox.org/
|
||||
.. _Getting Started: http://code.google.com/p/beets/wiki/GettingStarted
|
||||
.. _Getting Started: http://beets.readthedocs.org/en/latest/guides/main.html
|
||||
.. _@b33ts: http://twitter.com/b33ts/
|
||||
|
||||
Authors
|
||||
|
|
|
|||
|
|
@ -628,7 +628,7 @@ def apply_choices(config):
|
|||
# If we're replacing an item, then move rather than
|
||||
# copying.
|
||||
do_copy = not bool(replaced_items[item])
|
||||
item.move(lib, do_copy, task.is_album)
|
||||
lib.move(item, do_copy, task.is_album)
|
||||
if config.write and task.should_write_tags():
|
||||
item.write()
|
||||
|
||||
|
|
|
|||
150
beets/library.py
150
beets/library.py
|
|
@ -207,52 +207,22 @@ class Item(object):
|
|||
for key in ITEM_KEYS_WRITABLE:
|
||||
setattr(f, key, getattr(self, key))
|
||||
f.save()
|
||||
|
||||
|
||||
# Dealing with files themselves.
|
||||
|
||||
def move(self, library, copy=False, in_album=False, basedir=None):
|
||||
"""Move the item to its designated location within the library
|
||||
directory (provided by destination()). Subdirectories are
|
||||
created as needed. If the operation succeeds, the item's path
|
||||
field is updated to reflect the new location.
|
||||
|
||||
If copy is True, moving the file is copied rather than moved.
|
||||
|
||||
If in_album is True, then the track is treated as part of an
|
||||
album even if it does not yet have an album_id associated with
|
||||
it. (This allows items to be moved before they are added to the
|
||||
database, a performance optimization.)
|
||||
|
||||
basedir overrides the library base directory for the
|
||||
destination.
|
||||
|
||||
Passes on appropriate exceptions if directories cannot be
|
||||
created or moving/copying fails.
|
||||
|
||||
Note that one should almost certainly call store() and
|
||||
library.save() after this method in order to keep on-disk data
|
||||
consistent.
|
||||
# Files themselves.
|
||||
|
||||
def move(self, dest, copy=False):
|
||||
"""Moves or copies the item's file, updating the path value if
|
||||
the move succeeds.
|
||||
"""
|
||||
dest = library.destination(self, in_album=in_album, basedir=basedir)
|
||||
|
||||
# Create necessary ancestry for the move.
|
||||
util.mkdirall(dest)
|
||||
|
||||
if not samefile(self.path, dest):
|
||||
if copy:
|
||||
util.copy(self.path, dest)
|
||||
else:
|
||||
util.move(self.path, dest)
|
||||
if copy:
|
||||
util.copy(self.path, dest)
|
||||
else:
|
||||
util.move(self.path, dest)
|
||||
|
||||
# Either copying or moving succeeded, so update the stored path.
|
||||
old_path = self.path
|
||||
self.path = dest
|
||||
|
||||
# Prune vacated directory.
|
||||
if not copy:
|
||||
util.prune_dirs(os.path.dirname(old_path), library.directory)
|
||||
|
||||
|
||||
# Library queries.
|
||||
|
||||
|
|
@ -864,13 +834,13 @@ class Library(BaseLibrary):
|
|||
return normpath(os.path.join(basedir, subpath))
|
||||
|
||||
|
||||
# Main interface.
|
||||
# Item manipulation.
|
||||
|
||||
def add(self, item, copy=False):
|
||||
#FIXME make a deep copy of the item?
|
||||
item.library = self
|
||||
if copy:
|
||||
item.move(self, copy=True)
|
||||
self.move(item, copy=True)
|
||||
|
||||
# build essential parts of query
|
||||
columns = ','.join([key for key in ITEM_KEYS if key != 'id'])
|
||||
|
|
@ -962,6 +932,53 @@ class Library(BaseLibrary):
|
|||
if delete:
|
||||
util.soft_remove(item.path)
|
||||
util.prune_dirs(os.path.dirname(item.path), self.directory)
|
||||
|
||||
def move(self, item, copy=False, in_album=False, basedir=None,
|
||||
with_album=True):
|
||||
"""Move the item to its designated location within the library
|
||||
directory (provided by destination()). Subdirectories are
|
||||
created as needed. If the operation succeeds, the item's path
|
||||
field is updated to reflect the new location.
|
||||
|
||||
If copy is True, moving the file is copied rather than moved.
|
||||
|
||||
If in_album is True, then the track is treated as part of an
|
||||
album even if it does not yet have an album_id associated with
|
||||
it. (This allows items to be moved before they are added to the
|
||||
database, a performance optimization.)
|
||||
|
||||
basedir overrides the library base directory for the
|
||||
destination.
|
||||
|
||||
If the item is in an album, the album is given an opportunity to
|
||||
move its art. (This can be disabled by passing
|
||||
with_album=False.)
|
||||
|
||||
The item is stored to the database if it is in the database, so
|
||||
any dirty fields prior to the move() call will be written as a
|
||||
side effect. You probably want to call save() to commit the DB
|
||||
transaction.
|
||||
"""
|
||||
dest = self.destination(item, in_album=in_album, basedir=basedir)
|
||||
|
||||
# Create necessary ancestry for the move.
|
||||
util.mkdirall(dest)
|
||||
|
||||
# Perform the move and store the change.
|
||||
old_path = item.path
|
||||
item.move(dest, copy)
|
||||
if item.id is not None:
|
||||
self.store(item)
|
||||
|
||||
# If this item is in an album, move its art.
|
||||
if with_album:
|
||||
album = self.get_album(item)
|
||||
if album:
|
||||
album.move_art(copy)
|
||||
|
||||
# Prune vacated directory.
|
||||
if not copy:
|
||||
util.prune_dirs(os.path.dirname(old_path), self.directory)
|
||||
|
||||
|
||||
# Querying.
|
||||
|
|
@ -1156,37 +1173,44 @@ class Album(BaseAlbum):
|
|||
(self.id,)
|
||||
)
|
||||
|
||||
def move_art(self, copy=False):
|
||||
"""Move or copy any existing album art so that it remains in the
|
||||
same directory as the items.
|
||||
"""
|
||||
old_art = self.artpath
|
||||
if not old_art:
|
||||
return
|
||||
|
||||
new_art = self.art_destination(old_art)
|
||||
if new_art == old_art:
|
||||
return
|
||||
|
||||
log.debug('moving album art %s to %s' % (old_art, new_art))
|
||||
if copy:
|
||||
util.copy(old_art, new_art)
|
||||
else:
|
||||
util.move(old_art, new_art)
|
||||
self.artpath = new_art
|
||||
|
||||
# Prune old path when moving.
|
||||
if not copy:
|
||||
util.prune_dirs(os.path.dirname(old_art),
|
||||
self._library.directory)
|
||||
|
||||
def move(self, copy=False, basedir=None):
|
||||
"""Moves (or copies) all items to their destination. Any
|
||||
album art moves along with them. basedir overrides the library
|
||||
base directory for the destination.
|
||||
"""Moves (or copies) all items to their destination. Any album
|
||||
art moves along with them. basedir overrides the library base
|
||||
directory for the destination.
|
||||
"""
|
||||
basedir = basedir or self._library.directory
|
||||
|
||||
# Move items.
|
||||
items = list(self.items())
|
||||
for item in items:
|
||||
item.move(self._library, copy, basedir=basedir)
|
||||
newdir = os.path.dirname(items[0].path)
|
||||
self._library.move(item, copy, basedir=basedir, with_album=False)
|
||||
|
||||
# Move art.
|
||||
old_art = self.artpath
|
||||
if old_art:
|
||||
new_art = self.art_destination(old_art, newdir)
|
||||
if new_art != old_art:
|
||||
if copy:
|
||||
util.copy(old_art, new_art)
|
||||
else:
|
||||
util.move(old_art, new_art)
|
||||
self.artpath = new_art
|
||||
if not copy: # Prune old path.
|
||||
util.prune_dirs(os.path.dirname(old_art),
|
||||
self._library.directory)
|
||||
|
||||
# Store new item paths. We do this at the end to avoid
|
||||
# locking the database for too long while files are copied.
|
||||
for item in items:
|
||||
self._library.store(item)
|
||||
self.move_art(copy)
|
||||
|
||||
def item_dir(self):
|
||||
"""Returns the directory containing the album's first item,
|
||||
|
|
|
|||
|
|
@ -331,9 +331,20 @@ def colorize(color, text):
|
|||
return escape + text + RESET_COLOR
|
||||
|
||||
def colordiff(a, b, highlight='red'):
|
||||
"""Given two strings, return the same pair of strings except with
|
||||
their differences highlighted in the specified color.
|
||||
"""Given two values, return the same pair of strings except with
|
||||
their differences highlighted in the specified color. Strings are
|
||||
highlighted intelligently to show differences; other values are
|
||||
stringified and highlighted in their entirety.
|
||||
"""
|
||||
if not isinstance(a, basestring) or not isinstance(b, basestring):
|
||||
# Non-strings: use ordinary equality.
|
||||
a = unicode(a)
|
||||
b = unicode(b)
|
||||
if a == b:
|
||||
return a, b
|
||||
else:
|
||||
return colorize(highlight, a), colorize(highlight, b)
|
||||
|
||||
a_out = []
|
||||
b_out = []
|
||||
|
||||
|
|
@ -356,7 +367,7 @@ def colordiff(a, b, highlight='red'):
|
|||
else:
|
||||
assert(False)
|
||||
|
||||
return ''.join(a_out), ''.join(b_out)
|
||||
return u''.join(a_out), u''.join(b_out)
|
||||
|
||||
|
||||
# Subcommand parsing infrastructure.
|
||||
|
|
|
|||
|
|
@ -65,11 +65,20 @@ def _do_query(lib, query, album, also_items=True):
|
|||
|
||||
return items, albums
|
||||
|
||||
FLOAT_EPSILON = 0.01
|
||||
def _showdiff(field, oldval, newval, color):
|
||||
"""Prints out a human-readable field difference line."""
|
||||
# Considering floats incomparable for perfect equality, introduce
|
||||
# an epsilon tolerance.
|
||||
if isinstance(oldval, float) and isinstance(newval, float) and \
|
||||
abs(oldval - newval) < FLOAT_EPSILON:
|
||||
return
|
||||
|
||||
if newval != oldval:
|
||||
if color:
|
||||
oldval, newval = ui.colordiff(oldval, newval)
|
||||
else:
|
||||
oldval, newval = unicode(oldval), unicode(newval)
|
||||
print_(u' %s: %s -> %s' % (field, oldval, newval))
|
||||
|
||||
|
||||
|
|
@ -703,6 +712,14 @@ def update_items(lib, query, album, move, color):
|
|||
old_data = dict(item.record)
|
||||
item.read()
|
||||
|
||||
# Special-case album artist when it matches track artist. (Hacky
|
||||
# but necessary for preserving album-level metadata for non-
|
||||
# autotagged imports.)
|
||||
if not item.albumartist and \
|
||||
old_data['albumartist'] == old_data['artist'] == item.artist:
|
||||
item.albumartist = old_data['albumartist']
|
||||
item.dirty['albumartist'] = False
|
||||
|
||||
# Get and save metadata changes.
|
||||
changes = {}
|
||||
for key in library.ITEM_KEYS_META:
|
||||
|
|
@ -716,7 +733,7 @@ def update_items(lib, query, album, move, color):
|
|||
|
||||
# Move the item if it's in the library.
|
||||
if move and lib.directory in ancestry(item.path):
|
||||
item.move(lib)
|
||||
lib.move(item)
|
||||
|
||||
lib.store(item)
|
||||
affected_albums.add(item.album_id)
|
||||
|
|
@ -909,7 +926,7 @@ def modify_items(lib, mods, query, write, move, album, color, confirm):
|
|||
if album:
|
||||
obj.move()
|
||||
else:
|
||||
obj.move(lib)
|
||||
lib.move(obj)
|
||||
|
||||
# When modifying items, we have to store them to the database.
|
||||
if not album:
|
||||
|
|
@ -971,7 +988,7 @@ def move_items(lib, dest, query, copy, album):
|
|||
if album:
|
||||
obj.move(copy, basedir=dest)
|
||||
else:
|
||||
obj.move(lib, copy, basedir=dest)
|
||||
lib.move(obj, copy, basedir=dest)
|
||||
lib.store(obj)
|
||||
lib.save()
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ def prune_dirs(path, root, clutter=('.DS_Store', 'Thumbs.db')):
|
|||
ancestors.reverse()
|
||||
for directory in ancestors:
|
||||
directory = syspath(directory)
|
||||
if not os.path.exists(directory):
|
||||
# Directory gone already.
|
||||
continue
|
||||
|
||||
if all(fn in clutter for fn in os.listdir(directory)):
|
||||
# Directory contains only clutter (or nothing).
|
||||
try:
|
||||
|
|
@ -193,9 +197,12 @@ def _assert_not_exists(path, pathmod=None):
|
|||
|
||||
def copy(path, dest, replace=False, pathmod=None):
|
||||
"""Copy a plain file. Permissions are not copied. If dest already
|
||||
exists, raises an OSError unless replace is True. Paths are
|
||||
translated to system paths before the syscall.
|
||||
exists, raises an OSError unless replace is True. Has no effect if
|
||||
path is the same as dest. Paths are translated to system paths
|
||||
before the syscall.
|
||||
"""
|
||||
if samefile(path, dest):
|
||||
return
|
||||
path = syspath(path)
|
||||
dest = syspath(dest)
|
||||
_assert_not_exists(dest, pathmod)
|
||||
|
|
@ -203,9 +210,11 @@ def copy(path, dest, replace=False, pathmod=None):
|
|||
|
||||
def move(path, dest, replace=False, pathmod=None):
|
||||
"""Rename a file. dest may not be a directory. If dest already
|
||||
exists, raises an OSError unless replace is True. Paths are
|
||||
translated to system paths.
|
||||
exists, raises an OSError unless replace is True. Hos no effect if
|
||||
path is the same as dest. Paths are translated to system paths.
|
||||
"""
|
||||
if samefile(path, dest):
|
||||
return
|
||||
path = syspath(path)
|
||||
dest = syspath(dest)
|
||||
_assert_not_exists(dest, pathmod)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class ThreadException(Exception):
|
|||
|
||||
def run(root_coro):
|
||||
# The "threads" dictionary keeps track of all the currently-
|
||||
# executing coroutines. It maps coroutines to their currenly
|
||||
# executing coroutines. It maps coroutines to their currently
|
||||
# "blocking" event.
|
||||
threads = {root_coro: ValueEvent(None)}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import beets.library
|
|||
import flask
|
||||
from flask import g
|
||||
|
||||
DEFAULT_HOST = ''
|
||||
DEFAULT_PORT = 8337
|
||||
|
||||
|
||||
# Utilities.
|
||||
|
||||
|
|
@ -112,7 +115,13 @@ class WebPlugin(BeetsPlugin):
|
|||
cmd.parser.add_option('-d', '--debug', action='store_true',
|
||||
default=False, help='debug mode')
|
||||
def func(lib, config, opts, args):
|
||||
host = args.pop(0) if args else \
|
||||
beets.ui.config_val(config, 'web', 'host', DEFAULT_HOST)
|
||||
port = args.pop(0) if args else \
|
||||
beets.ui.config_val(config, 'web', 'port', str(DEFAULT_PORT))
|
||||
port = int(port)
|
||||
|
||||
app.config['lib'] = lib
|
||||
app.run(host='', debug=opts.debug, threaded=True)
|
||||
app.run(host=host, port=port, debug=opts.debug, threaded=True)
|
||||
cmd.func = func
|
||||
return [cmd]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
<dt>Bitrate</dt>
|
||||
<dd><%= Math.round(bitrate/1000) %> kbps</dd>
|
||||
<% if (mb_trackid) { %>
|
||||
<dt>MuscBrainz entry</dt>
|
||||
<dt>MusicBrainz entry</dt>
|
||||
<dd>
|
||||
<a target="_blank" href="http://musicbrainz.org/recording/<%= mb_trackid %>">view</a>
|
||||
</dd>
|
||||
|
|
|
|||
130
docs/Makefile
Normal file
130
docs/Makefile
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/beets.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/beets.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/beets"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/beets"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
625
docs/changelog.rst
Normal file
625
docs/changelog.rst
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
1.0b10 (September XX, 2011)
|
||||
---------------------------
|
||||
|
||||
This version of beets focuses on making it easier to manage your metadata
|
||||
*after* you've imported it. A bumper crop of new commands has been added: a
|
||||
manual tag editor (``modify``), a tool to pick up out-of-band deletions and
|
||||
modifications (``update``), and functionality for moving and copying files
|
||||
around (``move``). Furthermore, the concept of "re-importing" is new: you can
|
||||
choose to re-run beets' advanced autotagger on any files you already have in
|
||||
your library if you change your mind after you finish the initial import.
|
||||
|
||||
As a couple of added bonuses, imports can now automatically skip
|
||||
previously-imported directories (with the ``-i`` flag) and there's an
|
||||
:doc:`experimental Web interface </plugins/web>` to beets in a new standard
|
||||
plugin.
|
||||
|
||||
* A new ``beet modify`` command enables **manual, command-line-based
|
||||
modification** of music metadata. Pass it a query along with ``field=value``
|
||||
pairs that specify the changes you want to make.
|
||||
|
||||
* A new ``beet update`` command updates the database to reflect **changes in the
|
||||
on-disk metadata**. You can now use an external program to edit tags on files,
|
||||
remove files and directories, etc., and then run ``beet update`` to make sure
|
||||
your beets library is in sync. This will also rename files to reflect their
|
||||
new metadata.
|
||||
|
||||
* A new ``beet move`` command can **copy or move files** into your library
|
||||
directory or to another specified directory.
|
||||
|
||||
* When importing files that are already in the library database, the items are
|
||||
no longer duplicated---instead, the library is updated to reflect the new
|
||||
metadata. This way, the import command can be transparently used as a
|
||||
**re-import**.
|
||||
|
||||
* Relatedly, the ``-L`` flag to the "import" command makes it take a query as
|
||||
its argument instead of a list of directories. The matched albums (or items,
|
||||
depending on the ``-s`` flag) are then re-imported.
|
||||
|
||||
* A new flag ``-i`` to the import command runs **incremental imports**, keeping
|
||||
track of and skipping previously-imported directories. This has the effect of
|
||||
making repeated import commands pick up only newly-added directories. The
|
||||
``import_incremental`` config option makes this the default.
|
||||
|
||||
* When pruning directories, "clutter" files such as ``.DS_Store`` and
|
||||
``Thumbs.db`` are ignored (and removed with otherwise-empty directories).
|
||||
|
||||
* The :doc:`/plugins/web` encapsulates a simple **Web-based GUI for beets**. The
|
||||
current iteration can browse the library and play music in browsers that
|
||||
support `HTML5 Audio`_.
|
||||
|
||||
* When moving items that are part of an album, the album art implicitly moves
|
||||
too.
|
||||
|
||||
* Files are no longer silently overwritten when moving and copying files.
|
||||
|
||||
* Handle exceptions thrown when running Mutagen.
|
||||
|
||||
* Fix a missing ``__future__`` import in ``embed art`` on Python 2.5.
|
||||
|
||||
* Fix ID3 and MPEG-4 tag names for the album-artist field.
|
||||
|
||||
* Fix Unicode encoding of album artist, album type, and label.
|
||||
|
||||
* Fix crash when "copying" an art file that's already in place.
|
||||
|
||||
.. _HTML5 Audio: http://www.w3.org/TR/html-markup/audio.html
|
||||
|
||||
1.0b9 (July 9, 2011)
|
||||
--------------------
|
||||
|
||||
This release focuses on a large number of small fixes and improvements that turn
|
||||
beets into a well-oiled, music-devouring machine. See the full release notes,
|
||||
below, for a plethora of new features.
|
||||
|
||||
* **Queries can now contain whitespace.** Spaces passed as shell arguments are
|
||||
now preserved, so you can use your shell's escaping syntax (quotes or
|
||||
backslashes, for instance) to include spaces in queries. For example,
|
||||
typing``beet ls "the knife"`` or ``beet ls the\ knife``. Read more in
|
||||
:doc:`/reference/query`.
|
||||
|
||||
* Queries can **match items from the library by directory**. A ``path:`` prefix
|
||||
is optional; any query containing a path separator (/ on POSIX systems) is
|
||||
assumed to be a path query. Running ``beet ls path/to/music`` will show all
|
||||
the music in your library under the specified directory. The
|
||||
:doc:`/reference/query` reference again has more details.
|
||||
|
||||
* **Local album art** is now automatically discovered and copied from the
|
||||
imported directories when available.
|
||||
|
||||
* When choosing the "as-is" import album (or doing a non-autotagged import),
|
||||
**every album either has an "album artist" set or is marked as a compilation
|
||||
(Various Artists)**. The choice is made based on the homogeneity of the
|
||||
tracks' artists. This prevents compilations that are imported as-is from being
|
||||
scattered across many directories after they are imported.
|
||||
|
||||
* The release **label** for albums and tracks is now fetched from !MusicBrainz,
|
||||
written to files, and stored in the database.
|
||||
|
||||
* The "list" command now accepts a ``-p`` switch that causes it to **show
|
||||
paths** instead of titles. This makes the output of ``beet ls -p`` suitable
|
||||
for piping into another command such as `xargs`_.
|
||||
|
||||
* Release year and label are now shown in the candidate selection list to help
|
||||
disambiguate different releases of the same album.
|
||||
|
||||
* Prompts in the importer interface are now colorized for easy reading. The
|
||||
default option is always highlighted.
|
||||
|
||||
* The importer now provides the option to specify a MusicBrainz ID manually if
|
||||
the built-in searching isn't working for a particular album or track.
|
||||
|
||||
* ``$bitrate`` in path formats is now formatted as a human-readable kbps value
|
||||
instead of as a raw integer.
|
||||
|
||||
* The import logger has been improved for "always-on" use. First, it is now
|
||||
possible to specify a log file in .beetsconfig. Also, logs are now appended
|
||||
rather than overwritten and contain timestamps.
|
||||
|
||||
* Album art fetching and plugin events are each now run in separate pipeline
|
||||
stages during imports. This should bring additional performance when using
|
||||
album art plugins like embedart or beets-lyrics.
|
||||
|
||||
* Accents and other Unicode decorators on characters are now treated more fairly
|
||||
by the autotagger. For example, if you're missing the acute accent on the "e"
|
||||
in "café", that change won't be penalized. This introduces a new dependency
|
||||
on the `unidecode`_ Python module.
|
||||
|
||||
* When tagging a track with no title set, the track's filename is now shown
|
||||
(instead of nothing at all).
|
||||
|
||||
* The bitrate of lossless files is now calculated from their file size (rather
|
||||
than being fixed at 0 or reflecting the uncompressed audio bitrate).
|
||||
|
||||
* Fixed a problem where duplicate albums or items imported at the same time
|
||||
would fail to be detected.
|
||||
|
||||
* BPD now uses a persistent "virtual filesystem" in order to fake a directory
|
||||
structure. This means that your path format settings are respected in BPD's
|
||||
browsing hierarchy. This may come at a performance cost, however. The virtual
|
||||
filesystem used by BPD is available for reuse by plugins (e.g., the FUSE
|
||||
plugin).
|
||||
|
||||
* Singleton imports (``beet import -s``) can now take individual files as
|
||||
arguments as well as directories.
|
||||
|
||||
* Fix Unicode queries given on the command line.
|
||||
|
||||
* Fix crasher in quiet singleton imports (``import -qs``).
|
||||
|
||||
* Fix crash when autotagging files with no metadata.
|
||||
|
||||
* Fix a rare deadlock when finishing the import pipeline.
|
||||
|
||||
* Fix an issue that was causing mpdupdate to run twice for every album.
|
||||
|
||||
* Fix a bug that caused release dates/years not to be fetched.
|
||||
|
||||
* Fix a crasher when setting MBIDs on MP3s file metadata.
|
||||
|
||||
* Fix a "broken pipe" error when piping beets' standard output.
|
||||
|
||||
* A better error message is given when the database file is unopenable.
|
||||
|
||||
* Suppress errors due to timeouts and bad responses from MusicBrainz.
|
||||
|
||||
* Fix a crash on album queries with item-only field names.
|
||||
|
||||
.. _xargs: http://en.wikipedia.org/wiki/xargs
|
||||
.. _unidecode: http://pypi.python.org/pypi/Unidecode/0.04.1
|
||||
|
||||
1.0b8 (April 28, 2011)
|
||||
----------------------
|
||||
|
||||
This release of beets brings two significant new features. First, beets now has
|
||||
first-class support for "singleton" tracks. Previously, it was only really meant
|
||||
to manage whole albums, but many of us have lots of non-album tracks to keep
|
||||
track of alongside our collections of albums. So now beets makes it easy to tag,
|
||||
catalog, and manipulate your individual tracks. Second, beets can now
|
||||
(optionally) embed album art directly into file metadata rather than only
|
||||
storing it in a "file on the side." Check out the :doc:`/plugins/embedart` for
|
||||
that functionality.
|
||||
|
||||
* Better support for **singleton (non-album) tracks**. Whereas beets previously
|
||||
only really supported full albums, now it can also keep track of individual,
|
||||
off-album songs. The "singleton" path format can be used to customize where
|
||||
these tracks are stored. To import singleton tracks, provide the -s switch to
|
||||
the import command or, while doing a normal full-album import, choose the "as
|
||||
Tracks" (T) option to add singletons to your library. To list only singleton
|
||||
or only album tracks, use the new ``singleton:`` query term: the query
|
||||
``singleton:true`` matches only singleton tracks; ``singleton:false`` matches
|
||||
only album tracks. The :doc:`/plugins/lastid` has been extended to support
|
||||
matching individual items as well.
|
||||
|
||||
* The importer/autotagger system has been heavily refactored in this release.
|
||||
If anything breaks as a result, please get in touch or just file a bug.
|
||||
|
||||
* Support for **album art embedded in files**. A new :doc:`/plugins/embedart`
|
||||
implements this functionality. Enable the plugin to automatically embed
|
||||
downloaded album art into your music files' metadata. The plugin also provides
|
||||
the "embedart" and "extractart" commands for moving image files in and out of
|
||||
metadata. See the wiki for more details. (Thanks, daenney!)
|
||||
|
||||
* The "distance" number, which quantifies how different an album's current and
|
||||
proposed metadata are, is now displayed as "similarity" instead. This should
|
||||
be less noisy and confusing; you'll now see 99.5% instead of 0.00489323.
|
||||
|
||||
* A new "timid mode" in the importer asks the user every time, even when it
|
||||
makes a match with very high confidence. The ``-t`` flag on the command line
|
||||
and the ``import_timid`` config option control this mode. (Thanks to mdecker
|
||||
on GitHub!)
|
||||
|
||||
* The multithreaded importer should now abort (either by selecting aBort or by
|
||||
typing ^C) much more quickly. Previously, it would try to get a lot of work
|
||||
done before quitting; now it gives up as soon as it can.
|
||||
|
||||
* Added a new plugin event, ``album_imported``, which is called every time an
|
||||
album is added to the library. (Thanks, Lugoues!)
|
||||
|
||||
* A new plugin method, ``register_listener``, is an imperative alternative to
|
||||
the ``@listen`` decorator (Thanks again, Lugoues!)
|
||||
|
||||
* In path formats, ``$albumartist`` now falls back to ``$artist`` (as well as
|
||||
the other way around).
|
||||
|
||||
* The importer now prints "(unknown album)" when no tags are present.
|
||||
|
||||
* When autotagging, "and" is considered equal to "&".
|
||||
|
||||
* Fix some crashes when deleting files that don't exist.
|
||||
|
||||
* Fix adding individual tracks in BPD.
|
||||
|
||||
* Fix crash when ``~/.beetsconfig`` does not exist.
|
||||
|
||||
|
||||
1.0b7 (April 5, 2011)
|
||||
---------------------
|
||||
|
||||
Beta 7's focus is on better support for "various artists" releases. These albums
|
||||
can be treated differently via the new ``[paths]`` config section and the
|
||||
autotagger is better at handling them. It also includes a number of
|
||||
oft-requested improvements to the ``beet`` command-line tool, including several
|
||||
new configuration options and the ability to clean up empty directory subtrees.
|
||||
|
||||
* **"Various artists" releases** are handled much more gracefully. The
|
||||
autotagger now sets the ``comp`` flag on albums whenever the album is
|
||||
identified as a "various artists" release by !MusicBrainz. Also, there is now
|
||||
a distinction between the "album artist" and the "track artist", the latter of
|
||||
which is never "Various Artists" or other such bogus stand-in. *(Thanks to
|
||||
Jonathan for the bulk of the implementation work on this feature!)*
|
||||
|
||||
* The directory hierarchy can now be **customized based on release type**. In
|
||||
particular, the ``path_format`` setting in .beetsconfig has been replaced with
|
||||
a new ``[paths]`` section, which allows you to specify different path formats
|
||||
for normal and "compilation" (various artists) releases as well as for each
|
||||
album type (see below). The default path formats have been changed to use
|
||||
``$albumartist`` instead of ``$artist``.
|
||||
|
||||
* A **new ``albumtype`` field** reflects the release type `as specified by
|
||||
MusicBrainz`_.
|
||||
|
||||
* When deleting files, beets now appropriately "prunes" the directory
|
||||
tree---empty directories are automatically cleaned up. *(Thanks to
|
||||
wlof on GitHub for this!)*
|
||||
|
||||
* The tagger's output now always shows the album directory that is currently
|
||||
being tagged. This should help in situations where files' current tags are
|
||||
missing or useless.
|
||||
|
||||
* The logging option (``-l``) to the ``import`` command now logs duplicate
|
||||
albums.
|
||||
|
||||
* A new ``import_resume`` configuration option can be used to disable the
|
||||
importer's resuming feature or force it to resume without asking. This option
|
||||
may be either ``yes``, ``no``, or ``ask``, with the obvious meanings. The
|
||||
``-p`` and ``-P`` command-line flags override this setting and correspond to
|
||||
the "yes" and "no" settings.
|
||||
|
||||
* Resuming is automatically disabled when the importer is in quiet (``-q``)
|
||||
mode. Progress is still saved, however, and the ``-p`` flag (above) can be
|
||||
used to force resuming.
|
||||
|
||||
* The ``BEETSCONFIG`` environment variable can now be used to specify the
|
||||
location of the config file that is at ~/.beetsconfig by default.
|
||||
|
||||
* A new ``import_quiet_fallback`` config option specifies what should
|
||||
happen in quiet mode when there is no strong recommendation. The options are
|
||||
``skip`` (the default) and "asis".
|
||||
|
||||
* When importing with the "delete" option and importing files that are already
|
||||
at their destination, files could be deleted (leaving zero copies afterward).
|
||||
This is fixed.
|
||||
|
||||
* The ``version`` command now lists all the loaded plugins.
|
||||
|
||||
* A new plugin, called ``info``, just prints out audio file metadata.
|
||||
|
||||
* Fix a bug where some files would be erroneously interpreted as MPEG-4 audio.
|
||||
|
||||
* Fix permission bits applied to album art files.
|
||||
|
||||
* Fix malformed !MusicBrainz queries caused by null characters.
|
||||
|
||||
* Fix a bug with old versions of the Monkey's Audio format.
|
||||
|
||||
* Fix a crash on broken symbolic links.
|
||||
|
||||
* Retry in more cases when !MusicBrainz servers are slow/overloaded.
|
||||
|
||||
* The old "albumify" plugin for upgrading databases was removed.
|
||||
|
||||
.. _as specified by MusicBrainz: http://wiki.musicbrainz.org/ReleaseType
|
||||
|
||||
1.0b6 (January 20, 2011)
|
||||
------------------------
|
||||
|
||||
This version consists primarily of bug fixes and other small improvements. It's
|
||||
in preparation for a more feature-ful release in beta 7. The most important
|
||||
issue involves correct ordering of autotagged albums.
|
||||
|
||||
* **Quiet import:** a new "-q" command line switch for the import command
|
||||
suppresses all prompts for input; it pessimistically skips all albums that the
|
||||
importer is not completely confident about.
|
||||
|
||||
* Added support for the **WavPack** and **Musepack** formats. Unfortunately, due
|
||||
to a limitation in the Mutagen library (used by beets for metadata
|
||||
manipulation), Musepack SV8 is not yet supported. Here's the `upstream bug`_
|
||||
in question.
|
||||
|
||||
* BPD now uses a pure-Python socket library and no longer requires
|
||||
eventlet/greenlet (the latter of which is a C extension). For the curious, the
|
||||
socket library in question is called `Bluelet`_.
|
||||
|
||||
* Non-autotagged imports are now resumable (just like autotagged imports).
|
||||
|
||||
* Fix a terrible and long-standing bug where track orderings were never applied.
|
||||
This manifested when the tagger appeared to be applying a reasonable ordering
|
||||
to the tracks but, later, the database reflects a completely wrong association
|
||||
of track names to files. The order applied was always just alphabetical by
|
||||
filename, which is frequently but not always what you want.
|
||||
|
||||
* We now use Windows' "long filename" support. This API is fairly tricky,
|
||||
though, so some instability may still be present---please file a bug if you
|
||||
run into pathname weirdness on Windows. Also, filenames on Windows now never
|
||||
end in spaces.
|
||||
|
||||
* Fix crash in lastid when the artist name is not available.
|
||||
|
||||
* Fixed a spurious crash when ``LANG`` or a related environment variable is set
|
||||
to an invalid value (such as ``'UTF-8'`` on some installations of Mac OS X).
|
||||
|
||||
* Fixed an error when trying to copy a file that is already at its destination.
|
||||
|
||||
* When copying read-only files, the importer now tries to make the copy
|
||||
writable. (Previously, this would just crash the import.)
|
||||
|
||||
* Fixed an ``UnboundLocalError`` when no matches are found during autotag.
|
||||
|
||||
* Fixed a Unicode encoding error when entering special characters into the
|
||||
"manual search" prompt.
|
||||
|
||||
* Added `` beet version`` command that just shows the current release version.
|
||||
|
||||
.. _upstream bug: http://code.google.com/p/mutagen/issues/detail?id=7
|
||||
.. _Bluelet: https://github.com/sampsyo/bluelet
|
||||
|
||||
1.0b5 (September 28, 2010)
|
||||
--------------------------
|
||||
|
||||
This version of beets focuses on increasing the accuracy of the autotagger. The
|
||||
main addition is an included plugin that uses acoustic fingerprinting to match
|
||||
based on the audio content (rather than existing metadata). Additional
|
||||
heuristics were also added to the metadata-based tagger as well that should make
|
||||
it more reliable. This release also greatly expands the capabilities of beets'
|
||||
:doc:`plugin API </plugins/index>`. A host of other little features and fixes
|
||||
are also rolled into this release.
|
||||
|
||||
* The :doc:`/plugins/lastid` adds Last.fm **acoustic fingerprinting
|
||||
support** to the autotagger. Similar to the PUIDs used by !MusicBrainz Picard,
|
||||
this system allows beets to recognize files that don't have any metadata at
|
||||
all. You'll need to install some dependencies for this plugin to work.
|
||||
|
||||
* To support the above, there's also a new system for **extending the autotagger
|
||||
via plugins**. Plugins can currently add components to the track and album
|
||||
distance functions as well as augment the MusicBrainz search. The new API is
|
||||
documented at :doc:`/plugins/index`.
|
||||
|
||||
* **String comparisons** in the autotagger have been augmented to act more
|
||||
intuitively. Previously, if your album had the title "Something (EP)" and it
|
||||
was officially called "Something", then beets would think this was a fairly
|
||||
significant change. It now checks for and appropriately reweights certain
|
||||
parts of each string. As another example, the title "The Great Album" is
|
||||
considered equal to "Great Album, The".
|
||||
|
||||
* New **event system for plugins** (thanks, Jeff!). Plugins can now get
|
||||
callbacks from beets when certain events occur in the core. Again, the API is
|
||||
documented in :doc:`/plugins/index`.
|
||||
|
||||
* The BPD plugin is now disabled by default. This greatly simplifies
|
||||
installation of the beets core, which is now 100% pure Python. To use BPD,
|
||||
though, you'll need to set ``plugins: bpd`` in your .beetsconfig.
|
||||
|
||||
* The ``import`` command can now remove original files when it copies items into
|
||||
your library. (This might be useful if you're low on disk space.) Set the
|
||||
``import_delete`` option in your .beetsconfig to ``yes``.
|
||||
|
||||
* Importing without autotagging (``beet import -A``) now prints out album names
|
||||
as it imports them to indicate progress.
|
||||
|
||||
* The new :doc:`/plugins/mpdupdate` will automatically update your MPD server's
|
||||
index whenever your beets library changes.
|
||||
|
||||
* Efficiency tweak should reduce the number of !MusicBrainz queries per
|
||||
autotagged album.
|
||||
|
||||
* A new ``-v`` command line switch enables debugging output.
|
||||
|
||||
* Fixed bug that completely broke non-autotagged imports (``import -A``).
|
||||
|
||||
* Fixed bug that logged the wrong paths when using ``import -l``.
|
||||
|
||||
* Fixed autotagging for the creatively-named band `!!!`_.
|
||||
|
||||
* Fixed normalization of relative paths.
|
||||
|
||||
* Fixed escaping of ``/`` characters in paths on Windows.
|
||||
|
||||
.. _!!!: http://musicbrainz.org/artist/f26c72d3-e52c-467b-b651-679c73d8e1a7.html
|
||||
|
||||
1.0b4 (August 9, 2010)
|
||||
----------------------
|
||||
|
||||
This thrilling new release of beets focuses on making the tagger more usable in
|
||||
a variety of ways. First and foremost, it should now be much faster: the tagger
|
||||
now uses a multithreaded algorithm by default (although, because the new tagger
|
||||
is experimental, a single-threaded version is still available via a config
|
||||
option). Second, the tagger output now uses a little bit of ANSI terminal
|
||||
coloring to make changes stand out. This way, it should be faster to decide what
|
||||
to do with a proposed match: the more red you see, the worse the match is.
|
||||
Finally, the tagger can be safely interrupted (paused) and restarted later at
|
||||
the same point. Just enter ``b`` for aBort at any prompt to stop the tagging
|
||||
process and save its progress. (The progress-saving also works in the
|
||||
unthinkable event that beets crashes while tagging.)
|
||||
|
||||
Among the under-the-hood changes in 1.0b4 is a major change to the way beets
|
||||
handles paths (filenames). This should make the whole system more tolerant to
|
||||
special characters in filenames, but it may break things (especially databases
|
||||
created with older versions of beets). As always, let me know if you run into
|
||||
weird problems with this release.
|
||||
|
||||
Finally, this release's ``setup.py`` should install a ``beet.exe`` startup stub
|
||||
for Windows users. This should make running beets much easier: just type
|
||||
``beet`` if you have your ``PATH`` environment variable set up correctly. The
|
||||
:doc:`/guides/main` guide has some tips on installing beets on Windows.
|
||||
|
||||
Here's the detailed list of changes:
|
||||
|
||||
* **Parallel tagger.** The autotagger has been reimplemented to use multiple
|
||||
threads. This means that it can concurrently read files from disk, talk to the
|
||||
user, communicate with MusicBrainz, and write data back to disk. Not only does
|
||||
this make the tagger much faster because independent work may be performed in
|
||||
parallel, but it makes the tagging process much more pleasant for large
|
||||
imports. The user can let albums queue up in the background while making a
|
||||
decision rather than waiting for beets between each question it asks. The
|
||||
parallel tagger is on by default but a sequential (single- threaded) version
|
||||
is still available by setting the ``threaded`` config value to ``no`` (because
|
||||
the parallel version is still quite experimental).
|
||||
|
||||
* **Colorized tagger output.** The autotagger interface now makes it a little
|
||||
easier to see what's going on at a glance by highlighting changes with
|
||||
terminal colors. This feature is on by default, but you can turn it off by
|
||||
setting ``color`` to ``no`` in your ``.beetsconfig`` (if, for example, your
|
||||
terminal doesn't understand colors and garbles the output).
|
||||
|
||||
* **Pause and resume imports.** The ``import`` command now keeps track of its
|
||||
progress, so if you're interrupted (beets crashes, you abort the process, an
|
||||
alien devours your motherboard, etc.), beets will try to resume from the point
|
||||
where you left off. The next time you run ``import`` on the same directory, it
|
||||
will ask if you want to resume. It accomplishes this by "fast-forwarding"
|
||||
through the albums in the directory until it encounters the last one it saw.
|
||||
(This means it might fail if that album can't be found.) Also, you can now
|
||||
abort the tagging process by entering ``b`` (for aBort) at any of the prompts.
|
||||
|
||||
* Overhauled methods for handling fileystem paths to allow filenames that have
|
||||
badly encoded special characters. These changes are pretty fragile, so please
|
||||
report any bugs involving ``UnicodeError`` or SQLite ``ProgrammingError``
|
||||
messages in this version.
|
||||
|
||||
* The destination paths (the library directory structure) now respect
|
||||
album-level metadata. This means that if you have an album in which two tracks
|
||||
have different album-level attributes (like year, for instance), they will
|
||||
still wind up in the same directory together. (There's currently not a very
|
||||
smart method for picking the "correct" album-level metadata, but we'll fix
|
||||
that later.)
|
||||
|
||||
* Fixed a bug where the CLI would fail completely if the ``LANG`` environment
|
||||
variable was not set.
|
||||
|
||||
* Fixed removal of albums (``beet remove -a``): previously, the album record
|
||||
would stay around although the items were deleted.
|
||||
|
||||
* The setup script now makes a ``beet.exe`` startup stub on Windows; Windows
|
||||
users can now just type ``beet`` at the prompt to run beets.
|
||||
|
||||
* Fixed an occasional bug where Mutagen would complain that a tag was already
|
||||
present.
|
||||
|
||||
* Fixed a bug with reading invalid integers from ID3 tags.
|
||||
|
||||
* The tagger should now be a little more reluctant to reorder tracks that
|
||||
already have indices.
|
||||
|
||||
1.0b3 (July 22, 2010)
|
||||
---------------------
|
||||
|
||||
This release features two major additions to the autotagger's functionality:
|
||||
album art fetching and MusicBrainz ID tags. It also contains some important
|
||||
under-the-hood improvements: a new plugin architecture is introduced
|
||||
and the database schema is extended with explicit support for albums.
|
||||
|
||||
This release has one major backwards-incompatibility. Because of the new way
|
||||
beets handles albums in the library, databases created with an old version of
|
||||
beets might have trouble with operations that deal with albums (like the ``-a``
|
||||
switch to ``beet list`` and ``beet remove``, as well as the file browser for
|
||||
BPD). To "upgrade" an old database, you can use the included ``albumify`` plugin
|
||||
(see the fourth bullet point below).
|
||||
|
||||
* **Album art.** The tagger now, by default, downloads album art from Amazon
|
||||
that is referenced in the MusicBrainz database. It places the album art
|
||||
alongside the audio files in a file called (for example) ``cover.jpg``. The
|
||||
``import_art`` config option controls this behavior, as do the ``-r`` and
|
||||
``-R`` options to the import command. You can set the name (minus extension)
|
||||
of the album art file with the ``art_filename`` config option. (See
|
||||
:doc:`/reference/config` for more information about how to configure the album
|
||||
art downloader.)
|
||||
|
||||
* **Support for MusicBrainz ID tags.** The autotagger now keeps track of the
|
||||
MusicBrainz track, album, and artist IDs it matched for each file. It also
|
||||
looks for album IDs in new files it's importing and uses those to look up data
|
||||
in MusicBrainz. Furthermore, track IDs are used as a component of the tagger's
|
||||
distance metric now. (This obviously lays the groundwork for a utility that
|
||||
can update tags if the MB database changes, but that's `for the future`_.)
|
||||
Tangentially, this change required the database code to support a lightweight
|
||||
form of migrations so that new columns could be added to old databases--this
|
||||
is a delicate feature, so it would be very wise to make a backup of your
|
||||
database before upgrading to this version.
|
||||
|
||||
* **Plugin architecture.** Add-on modules can now add new commands to the beets
|
||||
command-line interface. The ``bpd`` and ``dadd`` commands were removed from
|
||||
the beets core and turned into plugins; BPD is loaded by default. To load the
|
||||
non-default plugins, use the config options ``plugins`` (a space-separated
|
||||
list of plugin names) and ``pluginpath`` (a colon-separated list of
|
||||
directories to search beyond ``sys.path``). Plugins are just Python modules
|
||||
under the ``beetsplug`` namespace package containing subclasses of
|
||||
``beets.plugins.BeetsPlugin``. See `the beetsplug directory`_ for examples or
|
||||
:doc:`/plugins/index` for instructions.
|
||||
|
||||
* As a consequence of adding album art, the database was significantly
|
||||
refactored to keep track of some information at an album (rather than item)
|
||||
granularity. Databases created with earlier versions of beets should work
|
||||
fine, but they won't have any "albums" in them--they'll just be a bag of
|
||||
items. This means that commands like ``beet ls -a`` and ``beet rm -a`` won't
|
||||
match anything. To "upgrade" your database, you can use the included
|
||||
``albumify`` plugin. Running ``beets albumify`` with the plugin activated (set
|
||||
``plugins=albumify`` in your config file) will group all your items into
|
||||
albums, making beets behave more or less as it did before.
|
||||
|
||||
* Fixed some bugs with encoding paths on Windows. Also, ``:`` is now replaced
|
||||
with ``-`` in path names (instead of ``_``) for readability.
|
||||
|
||||
* ``MediaFile``s now have a ``format`` attribute, so you can use ``$format`` in
|
||||
your library path format strings like ``$artist - $album ($format)`` to get
|
||||
directories with names like ``Paul Simon - Graceland (FLAC)``.
|
||||
|
||||
.. _for the future: http://code.google.com/p/beets/issues/detail?id=69
|
||||
.. _the beetsplug directory:
|
||||
http://code.google.com/p/beets/source/browse/#hg/beetsplug
|
||||
|
||||
Beets also now has its first third-party plugin: `beetfs`_, by Martin Eve! It
|
||||
exposes your music in a FUSE filesystem using a custom directory structure. Even
|
||||
cooler: it lets you keep your files intact on-disk while correcting their tags
|
||||
when accessed through FUSE. Check it out!
|
||||
|
||||
.. _beetfs: http://code.google.com/p/beetfs/
|
||||
|
||||
1.0b2 (July 7, 2010)
|
||||
--------------------
|
||||
|
||||
This release focuses on high-priority fixes and conspicuously missing features.
|
||||
Highlights include support for two new audio formats (Monkey's Audio and Ogg
|
||||
Vorbis) and an option to log untaggable albums during import.
|
||||
|
||||
* **Support for Ogg Vorbis and Monkey's Audio** files and their tags. (This
|
||||
support should be considered preliminary: I haven't tested it heavily because
|
||||
I don't use either of these formats regularly.)
|
||||
|
||||
* An option to the ``beet import`` command for **logging albums that are
|
||||
untaggable** (i.e., are skipped or taken "as-is"). Use ``beet import -l
|
||||
LOGFILE PATHS``. The log format is very simple: it's just a status (either
|
||||
"skip" or "asis") followed by the path to the album in question. The idea is
|
||||
that you can tag a large collection and automatically keep track of the albums
|
||||
that weren't found in MusicBrainz so you can come back and look at them later.
|
||||
|
||||
* Fixed a ``UnicodeEncodeError`` on terminals that don't (or don't claim to)
|
||||
support UTF-8.
|
||||
|
||||
* Importing without autotagging (``beet import -A``) is now faster and doesn't
|
||||
print out a bunch of whitespace. It also lets you specify single files on the
|
||||
command line (rather than just directories).
|
||||
|
||||
* Fixed importer crash when attempting to read a corrupt file.
|
||||
|
||||
* Reorganized code for CLI in preparation for adding pluggable subcommands. Also
|
||||
removed dependency on the aging ``cmdln`` module in favor of `a hand-rolled
|
||||
solution`_.
|
||||
|
||||
.. _a hand-rolled solution: http://gist.github.com/462717
|
||||
|
||||
1.0b1 (June 17, 2010)
|
||||
---------------------
|
||||
|
||||
Initial release.
|
||||
40
docs/conf.py
Normal file
40
docs/conf.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
AUTHOR = u'Adrian Sampson'
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
extensions = []
|
||||
|
||||
#templates_path = ['_templates']
|
||||
exclude_patterns = ['_build']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
project = u'beets'
|
||||
copyright = u'2011, Adrian Sampson'
|
||||
|
||||
version = '1.0b10'
|
||||
release = '1.0b10'
|
||||
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
html_theme = 'default'
|
||||
#html_static_path = ['_static']
|
||||
htmlhelp_basename = 'beetsdoc'
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_documents = [
|
||||
('index', 'beets.tex', u'beets Documentation',
|
||||
AUTHOR, 'manual'),
|
||||
]
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
man_pages = [
|
||||
('reference/cli', 'beet', u'beets command-line interface',
|
||||
[AUTHOR], 1),
|
||||
('reference/config', 'beetsconfig', u'beets configuration file',
|
||||
[AUTHOR], 5),
|
||||
]
|
||||
12
docs/guides/index.rst
Normal file
12
docs/guides/index.rst
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Guides
|
||||
======
|
||||
|
||||
This section contains a couple of walkthroughs that will help you get familiar
|
||||
with beets. If you're new to beets, you'll want to begin with the :doc:`main`
|
||||
guide.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
main
|
||||
tagger
|
||||
239
docs/guides/main.rst
Normal file
239
docs/guides/main.rst
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
Welcome to `beets`_! This guide will help you begin using it to make your music
|
||||
collection better.
|
||||
|
||||
.. _beets: http://beets.radbox.org/
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
You will need Python. (Beets is written for `Python 2.7`_, but it works with
|
||||
2.5 and 2.6 as well. Python 3.x is not yet supported.)
|
||||
|
||||
.. _Python 2.7: http://www.python.org/download/releases/2.7.1/
|
||||
|
||||
* **Mac OS X** v10.7 (Lion) includes Python 2.7 out of the box; Snow Leopard
|
||||
ships with Python 2.6.
|
||||
|
||||
* On **Ubuntu**, you can get everything you need by running:
|
||||
``apt-get install python-dev python-setuptools python-pip``
|
||||
|
||||
* For **Arch Linux**, try getting `beets from AUR`_. (There's also a `dev
|
||||
package`_, which is likely broken.) If you don't want to use the AUR build,
|
||||
this suffices to get the dependencies: ``pacman -S base-devel python2-pip``
|
||||
|
||||
* If you're on **CentOS** 5, you have Python 2.4. To get 2.6,
|
||||
`try this yum repository`_.
|
||||
|
||||
.. _try this yum repository:
|
||||
http://chrislea.com/2009/09/09/easy-python-2-6-django-on-centos-5/
|
||||
.. _beets from AUR: http://aur.archlinux.org/packages.php?ID=39577
|
||||
.. _dev package: http://aur.archlinux.org/packages.php?ID=48617
|
||||
|
||||
|
||||
If you have `pip`_, just say ``pip install beets`` (you might need ``sudo`` in
|
||||
front of that). Otherwise, head over to the `Downloads`_ area, download the most
|
||||
recent source distribution, and run ``python setup.py install`` in the directory
|
||||
therein.
|
||||
|
||||
.. _pip: http://pip.openplans.org/
|
||||
.. _Downloads: http://code.google.com/p/beets/downloads/list
|
||||
|
||||
The best way to upgrade beets to a new version is by running ``pip install -U
|
||||
beets``. You may want to follow `@b33ts`_ on Twitter to hear about progress on
|
||||
new versions.
|
||||
|
||||
.. _@b33ts: http://twitter.com/b33ts
|
||||
|
||||
Installing on Windows
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Installing beets on Windows can be tricky. Following these steps might help you
|
||||
get it right:
|
||||
|
||||
1. If you don't have it, `install Python`_ (you want Python 2.7).
|
||||
|
||||
.. _install Python: http://python.org/download/
|
||||
|
||||
2. Install `Setuptools`_ from PyPI. To do this, scroll to the bottom of that
|
||||
page and download the Windows installer (``.exe``, not ``.egg``) for your
|
||||
Python version (for example: ``setuptools-0.6c11.win32-py2.7.exe``).
|
||||
|
||||
.. _Setuptools: http://pypi.python.org/pypi/setuptools
|
||||
|
||||
3. If you haven't done so already, set your ``PATH`` environment variable to
|
||||
include Python and its scripts. To do so, you have to get the "Properties"
|
||||
window for "My Computer", then choose the "Advanced" tab, then hit the
|
||||
"Environment Variables" button, and then look for the ``PATH`` variable in
|
||||
the table. Add the following to the end of the variable's value:
|
||||
``;C:\Python27;C:\Python27\Scripts``.
|
||||
|
||||
4. Open a command prompt and install pip by running: ``easy_install pip``
|
||||
|
||||
5. Now install beets by running: ``pip install beets``
|
||||
|
||||
6. You're all set! Type ``beet`` at the command prompt to make sure everything's
|
||||
in order.
|
||||
|
||||
Because I don't use Windows myself, I may have missed something. If you have
|
||||
trouble or you have more detail to contribute here, please `let me know`_.
|
||||
|
||||
.. _let me know: mailto:adrian@radbox.org
|
||||
|
||||
Configuring
|
||||
-----------
|
||||
|
||||
You'll want to set a few basic options before you start using beets. To do this,
|
||||
create and edit the file ``~/.beetsconfig`` with your favorite text editor. This
|
||||
file will start out empty, but here's good place to start::
|
||||
|
||||
[beets]
|
||||
directory: ~/music
|
||||
library: ~/data/musiclibrary.blb
|
||||
|
||||
Change that first path to a directory where you'd like to keep your music. Then,
|
||||
for ``library``, choose a good place to keep a database file that keeps an index
|
||||
of your music.
|
||||
|
||||
Here, you can also change a few more options: you can leave files in place
|
||||
instead of copying everything to your library folder; you can customize the
|
||||
library's directory structure and naming scheme; you can also choose not to
|
||||
write updated tags to files you import. If you're curious,
|
||||
see :doc:`/reference/config`.
|
||||
|
||||
Importing Your Library
|
||||
----------------------
|
||||
|
||||
There are two good ways to bring your existing library into beets. You can
|
||||
either: (a) quickly bring all your files with all their current metadata into
|
||||
beets' database, or (b) use beets' highly-refined autotagger to find canonical
|
||||
metadata for every album you import. Option (a) is really fast, but option (b)
|
||||
makes sure all your songs' tags are exactly right from the get-go. The point
|
||||
about speed bears repeating: using the autotagger on a large library can take a
|
||||
very long time, and it's an interactive process. So set aside a good chunk of
|
||||
time if you're going to go that route. (I'm working on improving the
|
||||
autotagger's performance and automation.) For more information on the
|
||||
interactive tagging process, see :doc:`tagger`.
|
||||
|
||||
If you've got time and want to tag all your music right once and for all, do
|
||||
this::
|
||||
|
||||
$ beet import /path/to/my/music
|
||||
|
||||
(Note that by default, this command will *copy music into the directory you
|
||||
specified above*. If you want to use your current directory structure, set the
|
||||
``import_copy`` config option.) To take the fast,
|
||||
un-autotagged path, just say::
|
||||
|
||||
$ beet import -A /my/huge/mp3/library
|
||||
|
||||
Note that you just need to add ``-A`` for "don't autotag".
|
||||
|
||||
Adding More Music
|
||||
-----------------
|
||||
|
||||
If you've ripped or... otherwise obtained some new music, you can add it with
|
||||
the ``beet import`` command, the same way you imported your library. Like so::
|
||||
|
||||
$ beet import ~/some_great_album
|
||||
|
||||
This will attempt to autotag the new album (interactively) and add it to your
|
||||
library. There are, of course, more options for this command---just type ``beet
|
||||
help import`` to see what's available.
|
||||
|
||||
By default, the ``import`` command will try to find and download album art for
|
||||
every album it finds. It will store the art in a file called ``cover.jpg``
|
||||
alongside the songs. If you don't like that, you can disable it with the ``-R``
|
||||
switch or by setting a value in the :doc:`configuration file
|
||||
</reference/config>`.
|
||||
|
||||
Seeing Your Music
|
||||
-----------------
|
||||
|
||||
If you want to query your music library, the ``beet list`` (shortened to ``beet
|
||||
ls``) command is for you. You give it a :doc:`query string </reference/query>`,
|
||||
which is formatted something like a Google search, and it gives you a list of
|
||||
songs. Thus::
|
||||
|
||||
$ beet ls the magnetic fields
|
||||
The Magnetic Fields - Distortion - Three-Way
|
||||
The Magnetic Fields - Distortion - California Girls
|
||||
The Magnetic Fields - Distortion - Old Fools
|
||||
$ beet ls hissing gronlandic
|
||||
of Montreal - Hissing Fauna, Are You the Destroyer? - Gronlandic Edit
|
||||
$ beet ls bird
|
||||
The Knife - The Knife - Bird
|
||||
The Mae Shi - Terrorbird - Revelation Six
|
||||
$ beet ls album:bird
|
||||
The Mae Shi - Terrorbird - Revelation Six
|
||||
|
||||
As you can see, search terms by default search all attributes of songs. (They're
|
||||
also implicitly joined by ANDs: a track must match *all* criteria in order to
|
||||
match the query.) To narrow a search term to a particular metadata field, just
|
||||
put the field before the term, separated by a : character. So ``album:bird``
|
||||
only looks for ``bird`` in the "album" field of your songs. (Need to know more?
|
||||
:doc:`/reference/query/` will answer all your questions.)
|
||||
|
||||
The ``beet list`` command has another useful option worth mentioning, ``-a``,
|
||||
which searches for albums instead of songs::
|
||||
|
||||
$ beet ls -a forever
|
||||
Bon Iver - For Emma, Forever Ago
|
||||
Freezepop - Freezepop Forever
|
||||
|
||||
So handy!
|
||||
|
||||
Beets also has a ``stats`` command, just in case you want to see how much music
|
||||
you have::
|
||||
|
||||
$ ./beet stats
|
||||
Tracks: 13019
|
||||
Total time: 4.9 weeks
|
||||
Total size: 71.1 GB
|
||||
Artists: 548
|
||||
Albums: 1094
|
||||
|
||||
Playing Music
|
||||
-------------
|
||||
|
||||
Beets is primarily intended as a music organizer, not a player. It's designed to
|
||||
be used in conjunction with other players (consider `Decibel`_ or `cmus`_;
|
||||
there's even :ref:`a cmus plugin for beets <other-plugins>`). However, it does
|
||||
include a simple music player---it doesn't have a ton of features, but it gets
|
||||
the job done.
|
||||
|
||||
.. _Decibel: http://decibel.silent-blade.org/
|
||||
.. _cmus: http://cmus.sourceforge.net/
|
||||
|
||||
The player, called BPD, is a clone of an excellent music player called `MPD`_.
|
||||
Like MPD, it runs as a daemon (i.e., without a user interface). Another program,
|
||||
called an MPD client, controls the player and provides the user with an
|
||||
interface. You'll need to enable the BPD plugin before you can use it. Check out
|
||||
:doc:`/plugins/bpd`.
|
||||
|
||||
.. _MPD: http://mpd.wikia.com/
|
||||
|
||||
You can, of course, use the bona fide MPD server with your beets library. MPD is
|
||||
a great player and has more features than BPD. BPD just provides a convenient,
|
||||
built-in player that integrates tightly with your beets database.
|
||||
|
||||
Keep Playing
|
||||
------------
|
||||
|
||||
The :doc:`/reference/cli` page has more detailed description of all of beets'
|
||||
functionality. (Like deleting music! That's important.) Start exploring!
|
||||
|
||||
Also, check out :ref:`included-plugins` as well as :ref:`other-plugins`. The
|
||||
real power of beets is in its extensibility---with plugins, beets can do almost
|
||||
anything for your music collection.
|
||||
|
||||
You can always get help using the ``beet help`` command. The plain ``beet help``
|
||||
command lists all the available commands; then, for example, ``beet help
|
||||
import`` gives more specific help about the ``import`` command.
|
||||
|
||||
Please let me know what you think of beets via `email`_ or `Twitter`_.
|
||||
|
||||
.. _email: mailto:adrian@radbox.org
|
||||
.. _twitter: http://twitter.com/b33ts
|
||||
241
docs/guides/tagger.rst
Normal file
241
docs/guides/tagger.rst
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
Using the Auto-Tagger
|
||||
=====================
|
||||
|
||||
Beets' automatic metadata correcter is sophisticated but complicated and
|
||||
cryptic. This is a guide to help you through its myriad inputs and options.
|
||||
|
||||
An Apology and a Brief Interlude
|
||||
--------------------------------
|
||||
|
||||
I would like to sincerely apologize that the autotagger in beets is so fussy. It
|
||||
asks you a *lot* of complicated questions, insecurely asking that you verify
|
||||
nearly every assumption it makes. This means importing and correcting the tags
|
||||
for a large library can be an endless, tedious process. I'm sorry for this.
|
||||
|
||||
Maybe it will help to think of it as a tradeoff. By carefully examining every
|
||||
album you own, you get to become more familiar with your library, its extent,
|
||||
its variation, and its quirks. People used to spend hours lovingly sorting and
|
||||
resorting their shelves of LPs. In the iTunes age, many of us toss our music
|
||||
into a heap and forget about it. This is great for some people. But there's
|
||||
value in intimate, complete familiarity with your collection. So instead of a
|
||||
chore, try thinking of correcting tags as quality time with your music
|
||||
collection. That's what I do.
|
||||
|
||||
One practical piece of advice: because beets' importer runs in multiple threads,
|
||||
it queues up work in the background while it's waiting for you to respond. So if
|
||||
you find yourself waiting for beets for a few seconds between every question it
|
||||
asks you, try walking away from the computer for a while, making some tea, and
|
||||
coming back. Beets will have a chance to catch up with you and will ask you
|
||||
questions much more quickly.
|
||||
|
||||
Back to the guide.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Beets' tagger is invoked using the ``beet import`` command. Point it at a
|
||||
directory and it imports the files into your library, tagging them as it goes
|
||||
(unless you pass ``--noautotag``, of course). There are several assumptions
|
||||
beets currently makes about the music you import. In time, we'd like to remove
|
||||
all of these limitations.
|
||||
|
||||
* Your music should be organized by album into directories. That is, the tagger
|
||||
assumes that each album is in a single directory. These directories can be
|
||||
arbitrarily deep (like ``music/2010/hiphop/seattle/freshespresso/glamour``),
|
||||
but any directory with music files in it is interpreted as a separate album.
|
||||
This means that your flat directory of six thousand uncategorized MP3s won't
|
||||
currently be autotaggable. This will change eventually.
|
||||
|
||||
* The music may have bad tags, but it's not completely untagged. (This is
|
||||
actually not a hard-and-fast rule: using the *E* option described below, it's
|
||||
entirely possible to search for a release to tag a given album.) This is
|
||||
because beets by default infers tags based on existing metadata. The
|
||||
:doc:`LastID plugin </plugins/lastid>` extends the autotagger to use acoustic
|
||||
fingerprinting to find information for arbitrary audio. Install that plugin if
|
||||
you're willing to spend a little more CPU power to get tags for unidentified
|
||||
albums.
|
||||
|
||||
* There isn't currently a good solution for multi-disc albums. Currently, every
|
||||
disc is treated as a separate release, so you'll see "69 Love Songs (disc 1)",
|
||||
"69 Love Songs (disc 2)" and such. We should be more flexible about this.
|
||||
|
||||
* Currently MP3, AAC, FLAC, Ogg Vorbis, Monkey's Audio, WavPack, and Musepack
|
||||
files are supported. (Do you use some other format?
|
||||
`Let me know!`_
|
||||
|
||||
.. _Let me know!: mailto:adrian@radbox.org
|
||||
|
||||
Now that that's out of the way, let's tag some music.
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
To import music, just say ``beet import MUSICDIR``. There are, of course, a few
|
||||
command-line options you should know:
|
||||
|
||||
* ``beet import -A``: don't try to autotag anything; just import files (this
|
||||
goes much faster than with autotagging enabled)
|
||||
|
||||
* ``beet import -W``: when autotagging, don't write new tags to the files
|
||||
themselves (just keep the new metadata in beets' database)
|
||||
|
||||
* ``beet import -C``: don't copy imported files to your music directory; leave
|
||||
them where they are
|
||||
|
||||
* ``beet import -R``: don't fetch album art.
|
||||
|
||||
* ``beet import -l LOGFILE``: write a message to ``LOGFILE`` every time you skip
|
||||
an album or choose to take its tags "as-is" (see below) or the album is
|
||||
skipped as a duplicate; this lets you come back later and reexamine albums
|
||||
that weren't tagged successfully
|
||||
|
||||
* ``beet import -q``: quiet mode. Never prompt for input and, instead,
|
||||
conservatively skip any albums that need your opinion. The ``-ql`` combination
|
||||
is recommended.
|
||||
|
||||
* ``beet import -t``: timid mode, which is sort of the opposite of "quiet." The
|
||||
importer will ask your permission for everything it does, confirming even very
|
||||
good matches with a prompt.
|
||||
|
||||
* ``beet import -p``: automatically resume an interrupted import. The importer
|
||||
keeps track of imports that don't finish completely (either due to a crash or
|
||||
because you stop them halfway through) and, by default, prompts you to decide
|
||||
whether to resume them. The ``-p`` flag automatically says "yes" to this
|
||||
question. Relatedly, ``-P`` flag automatically says "no."
|
||||
|
||||
* ``beet import -s``: run in *singleton* mode, tagging individual tracks instead
|
||||
of whole albums at a time. See the "as Tracks" choice below. This means you
|
||||
can use ``beet import -AC`` to quickly add a bunch of files to your library
|
||||
without doing anything to them.
|
||||
|
||||
Similarity
|
||||
----------
|
||||
|
||||
So you import an album into your beets library. It goes like this::
|
||||
|
||||
$ beet imp witchinghour
|
||||
Tagging: Ladytron - Witching Hour
|
||||
(Similarity: 98.4%)
|
||||
* Last One Standing -> The Last One Standing
|
||||
* Beauty -> Beauty*2
|
||||
* White Light Generation -> Whitelightgenerator
|
||||
* All the Way -> All the Way...
|
||||
|
||||
Here, beets gives you a preview of the album match it has found. It shows you
|
||||
which track titles will be changed if the match is applied. In this case, beets
|
||||
has found a match and thinks it's a good enough match to proceed without asking
|
||||
your permission. It has reported the *similarity* for the match it's found.
|
||||
Similarity is a measure of how well-matched beets thinks a tagging option is.
|
||||
100% similarity means a perfect match 0% indicates a truly horrible match.
|
||||
|
||||
In this case, beets has proceeded automatically because it found an option with
|
||||
very high similarity (98.4%). But, as you'll notice, if the similarity isn't
|
||||
quite so high, beets will ask you to confirm changes. This is because beets
|
||||
can't be very confident about more dissimilar matches, and you (as a human) are
|
||||
better at making the call than a computer. So it occasionally asks for help.
|
||||
|
||||
Choices
|
||||
-------
|
||||
|
||||
When beets needs your input about a match, it says something like this::
|
||||
|
||||
Tagging: Beirut - Lon Gisland
|
||||
(Similarity: 94.4%)
|
||||
* Scenic World (Second Version) -> Scenic World
|
||||
[A]pply, More candidates, Skip, Use as-is, as Tracks, Enter search, or aBort?
|
||||
|
||||
When beets asks you this question, it wants you to enter one of the capital letters: A, M, S, U, T, E, or B. That is, you can choose one of the following:
|
||||
|
||||
* *A*: Apply the suggested changes shown and move on.
|
||||
|
||||
* *M*: Show more options. (See the Candidates section, below.)
|
||||
|
||||
* *S*: Skip this album entirely and move on to the next one.
|
||||
|
||||
* *U*: Import the album without changing any tags. This is a good option for
|
||||
albums that aren't in the MusicBrainz database, like your friend's operatic
|
||||
faux-goth solo record that's only on two CD-Rs in the universe.
|
||||
|
||||
* *T*: Import the directory as *singleton* tracks, not as an album. Choose this
|
||||
if the tracks don't form a real release---you just have one or more loner
|
||||
tracks that aren't a full album. This will temporarily flip the tagger into
|
||||
*singleton* mode, which attempts to match each track individually.
|
||||
|
||||
* *E*: Enter an artist and album to use as a search in the database. Use this
|
||||
option if beets hasn't found any good options because the album is mistagged
|
||||
or untagged.
|
||||
|
||||
* *B*: Cancel this import task altogether. No further albums will be tagged;
|
||||
beets shuts down immediately. The next time you attempt to import the same
|
||||
directory, though, beets will ask you if you want to resume tagging where you
|
||||
left off.
|
||||
|
||||
Note that the option with ``[B]rackets`` is the default---so if you want to
|
||||
apply the changes, you can just hit return without entering anything.
|
||||
|
||||
Candidates
|
||||
----------
|
||||
|
||||
If you choose the M option, or if beets isn't very confident about any of the
|
||||
choices it found, it will present you with a list of choices (called
|
||||
candidates), like so::
|
||||
|
||||
Finding tags for "Panther - Panther".
|
||||
Candidates:
|
||||
1. Panther - Yourself (66.8%)
|
||||
2. Tav Falco's Panther Burns - Return of the Blue Panther (30.4%)
|
||||
# selection (default 1), Skip, Use as-is, or Enter search, or aBort?
|
||||
|
||||
Here, you have many of the same options as before, but you can also enter a
|
||||
number to choose one of the options that beets has found. Don't worry about
|
||||
guessing---beets will show you the proposed changes and ask you to confirm
|
||||
them, just like the earlier example. As the prompt suggests, you can just hit
|
||||
return to select the first candidate.
|
||||
|
||||
Fingerprinting
|
||||
--------------
|
||||
|
||||
You may have noticed by now that beets' autotagger works pretty well for most
|
||||
files, but can get confused when files don't have any metadata (or have wildly
|
||||
incorrect metadata). In this case, you need *acoustic fingerprinting*, a
|
||||
technology that identifies songs from the audio itself. With fingerprinting,
|
||||
beets can autotag files that have very bad or missing tags. The :doc:`"lastid"
|
||||
plugin </plugins/lastid>`, distributed with beets, uses `Last.fm's open-source
|
||||
fingerprinting implementation`_, but it's disabled by default. That's because
|
||||
it's sort of tricky to install. See the :doc:`/plugins/lastid` page for a guide
|
||||
to getting it set up.
|
||||
|
||||
.. _Last.fm's open-source fingerprinting implementation:
|
||||
http://github.com/lastfm/Fingerprinter
|
||||
|
||||
Missing Albums?
|
||||
---------------
|
||||
|
||||
If you're having trouble tagging a particular album with beets, you might want to check the following possibilities:
|
||||
|
||||
* Is the album present in `the MusicBrainz database`_? You can search on their
|
||||
site to make sure it's cataloged there. If not, anyone can edit
|
||||
MusicBrainz---so consider adding the data yourself.
|
||||
|
||||
* Beets won't show you possibilities from MusicBrainz with a mismatched number
|
||||
of tracks. That is, if your album is missing tracks or has additional tracks
|
||||
beyond what the MB database reflects, then you'll never see a match for that
|
||||
album. (This is because beets wouldn't know how to apply metadata to your
|
||||
files in this case.) `Issue #33`_ proposes adding a system that automatically
|
||||
detects and reports this situation.
|
||||
|
||||
.. _the MusicBrainz database: http://musicbrainz.org/
|
||||
.. _Issue #33: http://code.google.com/p/beets/issues/detail?id=33
|
||||
|
||||
If neither of these situations apply and you're still having trouble tagging
|
||||
something, please `file a bug report`_.
|
||||
|
||||
.. _file a bug report: http://code.google.com/p/beets/issues/entry
|
||||
|
||||
I Hope That Makes Sense
|
||||
-----------------------
|
||||
|
||||
I haven't made the process clear, please `drop me an email`_ and I'll try to
|
||||
improve this guide.
|
||||
|
||||
.. _drop me an email: mailto:adrian@radbox.org
|
||||
36
docs/index.rst
Normal file
36
docs/index.rst
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
beets: the music geek's media organizer
|
||||
=======================================
|
||||
|
||||
Welcome to the documentation for `beets`_, the media library management system
|
||||
for obsessive-compulsive music geeks.
|
||||
|
||||
If you're new to beets, begin with the :doc:`guides/main` guide. That guide
|
||||
walks you through installing beets, setting it up how you like it, and starting
|
||||
to build your music library.
|
||||
|
||||
Then you can get a more detailed look at beets' features in the
|
||||
:doc:`/reference/cli/` and :doc:`/reference/config` references. You might also
|
||||
be interested in exploring the :ref:`included-plugins`.
|
||||
|
||||
If you still need help, your can drop by the ``#beets`` IRC channel on Freenode,
|
||||
`email the author`_, or `file a bug`_ in the issue tracker. Please let me know
|
||||
where you think this documentation can be improved.
|
||||
|
||||
.. _beets: http://beets.radbox.org/
|
||||
.. _email the author: mailto:adrian@radbox.org
|
||||
.. _file a bug: http://code.google.com/p/beets/issues/entry
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
guides/index
|
||||
reference/index
|
||||
plugins/index
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog
|
||||
BIN
docs/plugins/beetsweb.png
Normal file
BIN
docs/plugins/beetsweb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
125
docs/plugins/bpd.rst
Normal file
125
docs/plugins/bpd.rst
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
BPD Plugin
|
||||
==========
|
||||
|
||||
BPD is a music player using music from a beets library. It runs as a daemon and
|
||||
implements the MPD protocol, so it's compatible with all the great MPD clients
|
||||
out there. I'm using `Theremin`_, `gmpc`_, `Sonata`_, and `Ario`_ successfully.
|
||||
|
||||
.. _Theremin: https://theremin.sigterm.eu/
|
||||
.. _gmpc: http://gmpc.wikia.com/wiki/Gnome_Music_Player_Client
|
||||
.. _Sonata: http://sonata.berlios.de/
|
||||
.. _Ario: http://ario-player.sourceforge.net/
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Before you can use BPD, you'll need the media library called GStreamer (along
|
||||
with its Python bindings) on your system.
|
||||
|
||||
* On Mac OS X, you should use `MacPorts`_ and run ``port install
|
||||
py26-gst-python``. (Note that you'll almost certainly need the Mac OS X
|
||||
Developer Tools.)
|
||||
|
||||
* On Linux, it's likely that you already have gst-python. (If not, your
|
||||
distribution almost certainly has a package for it.)
|
||||
|
||||
* On Windows, you may want to try `GStreamer WinBuilds`_ (cavet emptor: I
|
||||
haven't tried this).
|
||||
|
||||
.. _MacPorts: http://www.macports.org/
|
||||
.. _GStreamer WinBuilds: http://www.gstreamer-winbuild.ylatuya.es/
|
||||
|
||||
Using and Configuring
|
||||
---------------------
|
||||
|
||||
BPD is a plugin for beets. It comes with beets, but it's disabled by default. To
|
||||
enable it, you'll need to edit your ``.beetsconfig`` file and add the line
|
||||
``plugins: bpd``. Like so::
|
||||
|
||||
[beets]
|
||||
plugins: bpd
|
||||
|
||||
Then, you can run BPD by invoking::
|
||||
|
||||
$ beet bpd
|
||||
|
||||
Fire up your favorite MPD client to start playing music. The MPD site has `a
|
||||
long list of available clients`_. Here are my favorites:
|
||||
|
||||
.. _a long list of available clients: http://mpd.wikia.com/wiki/Clients
|
||||
|
||||
* Linux: `gmpc`_, `Sonata`_
|
||||
|
||||
* Mac: `Theremin`_
|
||||
|
||||
* Windows: I don't know. Get in touch if you have a recommendation.
|
||||
|
||||
* iPhone/iPod touch: `MPoD`_
|
||||
|
||||
.. _MPoD: http://www.katoemba.net/makesnosenseatall/mpod/
|
||||
|
||||
One nice thing about MPD's (and thus BPD's) client-server architecture is that
|
||||
the client can just as easily on a different computer from the server as it can
|
||||
be run locally. Control your music from your laptop (or phone!) while it plays
|
||||
on your headless server box. Rad!
|
||||
|
||||
To configure the BPD server, add a ``[bpd]`` section to your ``.beetsconfig``
|
||||
file. The configuration values, which are pretty self-explanatory, are ``host``,
|
||||
``port``, and ``password``. Here's an example::
|
||||
|
||||
[bpd]
|
||||
host: 127.0.0.1
|
||||
port: 6600
|
||||
password: seekrit
|
||||
|
||||
Implementation Notes
|
||||
--------------------
|
||||
|
||||
In the real MPD, the user can browse a music directory as it appears on disk. In
|
||||
beets, we like to abstract away from the directory structure. Therefore, BPD
|
||||
creates a "virtual" directory structure (artist/album/track) to present to
|
||||
clients. This is static for now and cannot be reconfigured like the real on-disk
|
||||
directory structure can. (Note that an obvious solution to this is just string
|
||||
matching on items' destination, but this requires examining the entire library
|
||||
Python-side for every query.)
|
||||
|
||||
We don't currently support versioned playlists. Many clients, however, use
|
||||
plchanges instead of playlistinfo to get the current playlist, so plchanges
|
||||
contains a dummy implementation that just calls playlistinfo.
|
||||
|
||||
The ``stats`` command always send zero for ``playtime``, which is supposed to
|
||||
indicate the amount of time the server has spent playing music. BPD doesn't
|
||||
currently keep track of this. Also, because database updates aren't yet
|
||||
supported, ``db_update`` is just the time the server was started.
|
||||
|
||||
Unimplemented Commands
|
||||
----------------------
|
||||
|
||||
These are the commands from `the MPD protocol`_ that have not yet been
|
||||
implemented in BPD.
|
||||
|
||||
.. _the MPD protocol: http://mpd.wikia.com/wiki/MusicPlayerDaemonCommands
|
||||
|
||||
Database:
|
||||
|
||||
* update
|
||||
|
||||
Saved playlists:
|
||||
|
||||
* playlistclear
|
||||
* playlistdelete
|
||||
* playlistmove
|
||||
* playlistadd
|
||||
* playlistsearch
|
||||
* listplaylist
|
||||
* listplaylistinfo
|
||||
* playlistfind
|
||||
* rm
|
||||
* save
|
||||
* load
|
||||
* rename
|
||||
|
||||
Deprecated:
|
||||
|
||||
* playlist
|
||||
* volume
|
||||
46
docs/plugins/embedart.rst
Normal file
46
docs/plugins/embedart.rst
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
EmbedArt Plugin
|
||||
===============
|
||||
|
||||
Typically, beets stores album art in a "file on the side": along with each
|
||||
album, there is a file (named "cover.jpg" by default) that stores the album art.
|
||||
You might want to embed the album art directly into each file's metadata. While
|
||||
this will take more space than the external-file approach, it is necessary for
|
||||
displaying album art in some media players (iPods, for example).
|
||||
|
||||
This plugin was added in beets 1.0b8.
|
||||
|
||||
Embedding Art Automatically
|
||||
---------------------------
|
||||
|
||||
To automatically embed discovered album art into imported files, just
|
||||
:doc:`enable the plugin </plugins/index>`. Art will be embedded after each album
|
||||
is added to the library.
|
||||
|
||||
This behavior can be disabled with the ``autoembed`` config option (see below).
|
||||
|
||||
Manually Embedding and Extracting Art
|
||||
-------------------------------------
|
||||
|
||||
The ``embedart`` plugin provides a couple of commands for manually managing
|
||||
embedded album art:
|
||||
|
||||
* ``beet embedart IMAGE QUERY``: given an image file and a query matching an
|
||||
album, embed the image into the metadata of every track on the album.
|
||||
|
||||
* ``beet extractart [-o FILE] QUERY``: extracts the image from an item matching
|
||||
the query and stores it in a file. You can specify the destination file using
|
||||
the ``-o`` option, but leave off the extension: it will be chosen
|
||||
automatically. The destination filename defaults to ``cover`` if it's not
|
||||
specified.
|
||||
|
||||
* ``beet clearart QUERY``: removes all embedded images from all items matching
|
||||
the query. (Use with caution!)
|
||||
|
||||
Configuring
|
||||
-----------
|
||||
|
||||
The plugin has one configuration option, ``autoembed``, which lets you disable
|
||||
automatic album art embedding. To do so, add this to your ``~/.beetsconfig``::
|
||||
|
||||
[embedart]
|
||||
autoembed: no
|
||||
249
docs/plugins/index.rst
Normal file
249
docs/plugins/index.rst
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
Plugins
|
||||
=======
|
||||
|
||||
As of the 1.0b3 release, beets started supporting plugins to modularize its
|
||||
functionality and allow other developers to add new functionality. Plugins can
|
||||
add new commands to the command-line interface, respond to events in beets, and
|
||||
augment the autotagger.
|
||||
|
||||
Using Plugins
|
||||
-------------
|
||||
|
||||
To use a plugin, you have two options:
|
||||
|
||||
* Make sure it's in the Python path (known as `sys.path` to developers). This
|
||||
just means the plugin has to be installed on your system (e.g., with a
|
||||
`setup.py` script or a command like `pip` or `easy_install`).
|
||||
|
||||
* Set the `pythonpath` config variable to point to the directory containing the
|
||||
plugin. (See :doc:`/reference/cli`.)
|
||||
|
||||
Then, set the `plugins` option in your `~/.beetsconfig` file, like so::
|
||||
|
||||
[beets]
|
||||
plugins = mygreatplugin someotherplugin
|
||||
|
||||
The value for `plugins` should be a space-separated list of plugin module names.
|
||||
|
||||
.. _included-plugins:
|
||||
|
||||
Plugins Included With Beets
|
||||
---------------------------
|
||||
|
||||
There are a few plugins that are included with the beets distribution. They're
|
||||
disabled by default, but you can turn them on as described above:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
lastid
|
||||
bpd
|
||||
mpdupdate
|
||||
embedart
|
||||
web
|
||||
|
||||
.. _other-plugins:
|
||||
|
||||
Other Plugins
|
||||
-------------
|
||||
|
||||
Here are a few of the plugins written by the beets community:
|
||||
|
||||
* `beets-replaygain`_ can analyze and store ReplayGain normalization
|
||||
information.
|
||||
|
||||
* `beets-lyrics`_ searches Web repositories for song lyrics and adds them to your files.
|
||||
|
||||
* `beetFs`_ is a FUSE filesystem for browsing the music in your beets library.
|
||||
(Might be out of date.)
|
||||
|
||||
* `Beet-MusicBrainz-Collection`_ lets you add albums from your library to your
|
||||
MusicBrainz `"music collection"`_.
|
||||
|
||||
* `A cmus plugin`_ integrates with the `cmus`_ console music player.
|
||||
|
||||
.. _beets-replaygain: https://github.com/Lugoues/beets-replaygain/
|
||||
.. _beets-lyrics: https://github.com/Lugoues/beets-lyrics/
|
||||
.. _beetFs: http://code.google.com/p/beetfs/
|
||||
.. _Beet-MusicBrainz-Collection:
|
||||
https://github.com/jeffayle/Beet-MusicBrainz-Collection/
|
||||
.. _"music collection": http://musicbrainz.org/show/collection/
|
||||
.. _A cmus plugin:
|
||||
https://github.com/coolkehon/beets/blob/master/beetsplug/cmus.py
|
||||
.. _cmus: http://cmus.sourceforge.net/
|
||||
|
||||
Writing Plugins
|
||||
---------------
|
||||
|
||||
A beets plugin is just a Python module inside the ``beetsplug`` namespace
|
||||
package. (Check out this `Stack Overflow question about namespace packages`_ if
|
||||
you haven't heard of them.) So, to make one, create a directory called
|
||||
``beetsplug`` and put two files in it: one called ``__init__.py`` and one called
|
||||
``myawesomeplugin.py`` (but don't actually call it that). Your directory
|
||||
structure should look like this::
|
||||
|
||||
beetsplug/
|
||||
__init__.py
|
||||
myawesomeplugin.py
|
||||
|
||||
.. _Stack Overflow question about namespace packages:
|
||||
http://stackoverflow.com/questions/1675734/how-do-i-create-a-namespace-package-in-python/1676069#1676069
|
||||
|
||||
Then, you'll need to put this stuff in ``__init__.py`` to make ``beetsplug`` a
|
||||
namespace package::
|
||||
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
||||
|
||||
That's all for ``__init__.py``; you can can leave it alone. The meat of your
|
||||
plugin goes in ``myawesomeplugin.py``. There, you'll have to import the
|
||||
``beets.plugins`` module and define a subclass of the ``BeetsPlugin`` class
|
||||
found therein. Here's a skeleton of a plugin file::
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
|
||||
class MyPlugin(BeetsPlugin):
|
||||
pass
|
||||
|
||||
Once you have your ``BeetsPlugin`` subclass, there's a variety of things your
|
||||
plugin can do. (Read on!)
|
||||
|
||||
To use your new plugin, make sure your ``beetsplug`` directory is in the Python
|
||||
path (using ``PYTHONPATH`` or by installing in a `virtualenv`_, for example).
|
||||
Then, as described above, edit your ``.beetsconfig`` to include
|
||||
``plugins=myawesomeplugin`` (substituting the name of the Python module
|
||||
containing your plugin).
|
||||
|
||||
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
|
||||
|
||||
Add Commands to the CLI
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Plugins can add new subcommands to the ``beet`` command-line interface. Define
|
||||
the plugin class' ``commands()`` method to return a list of ``Subcommand``
|
||||
objects. (The ``Subcommand`` class is defined in the ``beets.ui`` module.)
|
||||
Here's an example plugin that adds a simple command::
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets.ui import Subcommand
|
||||
|
||||
my_super_command = Subcommand('super', help='do something super')
|
||||
def say_hi(lib, config, opts, args):
|
||||
print "Hello everybody! I'm a plugin!"
|
||||
my_super_command.func = say_hi
|
||||
|
||||
class SuperPlug(BeetsPlugin):
|
||||
def commands(self):
|
||||
return [my_super_command]
|
||||
|
||||
To make a subcommand, invoke the constructor like so: ``Subcommand(name, parser,
|
||||
help, aliases)``. The ``name`` parameter is the only required one and should
|
||||
just be the name of your command. ``parser`` can be an `OptionParser instance`_,
|
||||
but it defaults to an empty parser (you can extend it later). ``help`` is a
|
||||
description of your command, and ``aliases`` is a list of shorthand versions of
|
||||
your command name.
|
||||
|
||||
.. _OptionParser instance: http://docs.python.org/library/optparse.html
|
||||
|
||||
You'll need to add a function to your command by saying ``mycommand.func =
|
||||
myfunction``. This function should take the following parameters: ``lib`` (a
|
||||
beets ``Library`` object), ``config`` (a `ConfigParser object`_ containing the
|
||||
configuration values), and ``opts`` and ``args`` (command-line options and
|
||||
arguments as returned by `OptionParser.parse_args`_).
|
||||
|
||||
.. _ConfigParser object: http://docs.python.org/library/configparser.html
|
||||
.. _OptionParser.parse_args:
|
||||
http://docs.python.org/library/optparse.html#parsing-arguments
|
||||
|
||||
The function should use any of the utility functions defined in ``beets.ui``.
|
||||
Try running ``pydoc beets.ui`` to see what's available.
|
||||
|
||||
You can add command-line options to your new command using the ``parser`` member
|
||||
of the ``Subcommand`` class, which is an ``OptionParser`` instance. Just use it
|
||||
like you would a normal ``OptionParser`` in an independent script.
|
||||
|
||||
Listen for Events
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
As of beets 1.0b5, plugins can also define event handlers. Event handlers allow
|
||||
you to run code whenever something happens in beets' operation. For instance, a
|
||||
plugin could write a log message every time an album is successfully autotagged
|
||||
or update MPD's index whenever the database is changed.
|
||||
|
||||
You can "listen" for events using the ``BeetsPlugin.listen`` decorator. Here's
|
||||
an example::
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
|
||||
class SomePlugin(BeetsPlugin):
|
||||
pass
|
||||
|
||||
@SomePlugin.listen('pluginload')
|
||||
def loaded():
|
||||
print 'Plugin loaded!'
|
||||
|
||||
Pass the name of the event in question to the ``listen`` decorator. The events
|
||||
currently available are:
|
||||
|
||||
* *pluginload*: called after all the plugins have been loaded after the ``beet``
|
||||
command starts
|
||||
|
||||
* *save*: called whenever the library is changed and written to disk (the
|
||||
``lib`` keyword argument is the Library object that was written)
|
||||
|
||||
* *import*: called after a ``beet import`` command fishes (the ``lib`` keyword
|
||||
argument is a Library object; ``paths`` is a list of paths (strings) that were
|
||||
imported)
|
||||
|
||||
* *album_imported*: called with an ``Album`` object every time the ``import``
|
||||
command finishes adding an album to the library
|
||||
|
||||
The included ``mpdupdate`` plugin provides an example use case for event listeners.
|
||||
|
||||
Extend the Autotagger
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Plugins in 1.0b5 can also enhance the functionality of the autotagger. For a
|
||||
comprehensive example, try looking at the ``lastid`` plugin, which is included
|
||||
with beets.
|
||||
|
||||
A plugin can extend three parts of the autotagger's process: the track distance
|
||||
function, the album distance function, and the initial MusicBrainz search. The
|
||||
distance functions determine how "good" a match is at the track and album
|
||||
levels; the initial search controls which candidates are presented to the
|
||||
matching algorithm. Plugins implement these extensions by implementing three
|
||||
methods on the plugin class:
|
||||
|
||||
* ``track_distance(self, item, info)``: adds a component to the distance
|
||||
function (i.e., the similarity metric) for individual tracks. ``item`` is the
|
||||
track to be matched (and Item object) and ``info`` is the !MusicBrainz track
|
||||
entry that is proposed as a match. Should return a ``(dist, dist_max)`` pair
|
||||
of floats indicating the distance.
|
||||
|
||||
* ``album_distance(self, items, info)``: like the above, but compares a list of
|
||||
items (representing an album) to an album-level !MusicBrainz entry. Should
|
||||
only consider album-level metadata (e.g., the artist name and album title) and
|
||||
should not duplicate the factors considered by ``track_distance``.
|
||||
|
||||
* ``candidates(self, items)``: given a list of items comprised by an album to be
|
||||
matched, return a list of !MusicBrainz entries for candidate albums to be
|
||||
compared and matched.
|
||||
|
||||
When implementing these functions, it will probably be very necessary to use the
|
||||
functions from the ``beets.autotag`` and ``beets.autotag.mb`` modules, both of
|
||||
which have somewhat helpful docstrings.
|
||||
|
||||
Read Configuration Options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Plugins can configure themselves using the ``.beetsconfig`` file. Define a
|
||||
``configure`` method on your plugin that takes an ``OptionParser`` object as an
|
||||
argument. Then use the ``beets.ui.config_val`` convenience function to access
|
||||
values from the config file. Like so::
|
||||
|
||||
class MyPlugin(BeetsPlugin):
|
||||
def configure(self, config):
|
||||
number_of_goats = beets.ui.config_val(config, 'myplug', 'goats', '42')
|
||||
|
||||
Try looking at the ``mpdupdate`` plugin (included with beets) for an example of
|
||||
real-world use of this API.
|
||||
65
docs/plugins/lastid.rst
Normal file
65
docs/plugins/lastid.rst
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
LastID Plugin
|
||||
=============
|
||||
|
||||
Acoustic fingerprinting is a technique for identifying songs from the way they
|
||||
"sound" rather from their existing metadata. That means that beets' autotagger
|
||||
can theoretically use fingerprinting to tag files that don't have any ID3
|
||||
information at all (or have completely incorrect data). The MusicBrainz project
|
||||
currently uses a fingerprinting technology called PUIDs, but beets uses a
|
||||
different fingerprinting algorithm provided by `Last.fm`_.
|
||||
|
||||
.. _Last.fm: http://last.fm/
|
||||
|
||||
Turning on fingerprinting can increase the accuracy of the
|
||||
autotagger---especially on files with very poor metadata---but it comes at a
|
||||
cost. First, it can be trickier to set up than beets itself (you need to compile
|
||||
the fingerprinting code, whereas all of the beets core is written in Python).
|
||||
Also, fingerprinting takes significantly more CPU and memory than ordinary
|
||||
tagging---which means that imports will go substantially slower.
|
||||
|
||||
If you're willing to pay the performance cost for fingerprinting, read on!
|
||||
|
||||
Installing Dependencies
|
||||
-----------------------
|
||||
|
||||
To use lastid, you'll need to install the `pylastfp`_ fingerprinting library,
|
||||
which has a few dependencies: `fftw`_, `libsamplerate`_, and `Gstreamer for
|
||||
Python`_. How you install these will depend on your operating system. Here's a
|
||||
few examples:
|
||||
|
||||
.. _pylastfp: http://github.com/sampsyo/pylastfp
|
||||
.. _fftw: http://www.fftw.org/
|
||||
.. _libsamplerate: http://www.mega-nerd.com/SRC/
|
||||
.. _Gstreamer for Python:
|
||||
http://gstreamer.freedesktop.org/modules/gst-python.html
|
||||
|
||||
* On Ubuntu, just run ``apt-get install libfftw3-dev libsamplerate0-dev
|
||||
python-gst0.10-dev``.
|
||||
|
||||
* On Arch Linux, you want
|
||||
``pacman -S fftw libsamplerate gstreamer0.10-python``.
|
||||
|
||||
Let me know if you have a good source for installing the packages on Windows.
|
||||
|
||||
To decode audio formats (MP3, FLAC, etc.), you'll need the standard set of
|
||||
Gstreamer plugins. For example, on Ubuntu, install the packages
|
||||
``gstreamer0.10-plugins-good``, ``gstreamer0.10-plugins-bad``, and
|
||||
``gstreamer0.10-plugins-ugly``.
|
||||
|
||||
Then, install pylastfp itself. You can do this using `pip`_, like so::
|
||||
|
||||
$ pip install pylastfp
|
||||
|
||||
.. _pip: http://pip.openplans.org/
|
||||
|
||||
Using
|
||||
-----
|
||||
|
||||
Once you have all the dependencies sorted out, you can enable fingerprinting by
|
||||
editing your :doc:`/reference/config`. Put ``lastid`` on your ``plugins:``
|
||||
line. Your config file should contain something like this::
|
||||
|
||||
[beets]
|
||||
plugins: lastid
|
||||
|
||||
With that, beets will use fingerprinting the next time you run ``beet import``.
|
||||
24
docs/plugins/mpdupdate.rst
Normal file
24
docs/plugins/mpdupdate.rst
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
MPDUpdate Plugin
|
||||
================
|
||||
|
||||
``mpdupdate`` is a very simple plugin for beets that lets you automatically
|
||||
update `MPD`_'s index whenever you change your beets library. The plugin is
|
||||
included with beets as of version 1.0b5.
|
||||
|
||||
.. _MPD: http://mpd.wikia.com/wiki/Music_Player_Daemon_Wiki
|
||||
|
||||
To use it, enable it in your ``.beetsconfig`` by putting ``mpdupdate`` on your ``plugins`` line. Your ``.beetsconfig`` should look like this::
|
||||
|
||||
[beets]
|
||||
plugins: mpdupdate
|
||||
|
||||
Then, you'll probably want to configure the specifics of your MPD server. You
|
||||
can do that using an ``[mpdupdate]`` section in your ``.beetsconfig``, which
|
||||
looks like this::
|
||||
|
||||
[mpdupdate]
|
||||
host = localhost
|
||||
port = 6600
|
||||
password = seekrit
|
||||
|
||||
With that all in place, you'll see beets send the "update" command to your MPD server every time you change your beets library.
|
||||
71
docs/plugins/web.rst
Normal file
71
docs/plugins/web.rst
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
Web Plugin
|
||||
==========
|
||||
|
||||
The ``web`` plugin is a very basic alternative interface to beets that
|
||||
supplements the CLI. It can't do much right now, and the interface is a little
|
||||
clunky, but you can use it to query and browse your music and---in browsers that
|
||||
support HTML5 Audio---you can even play music.
|
||||
|
||||
While it's not meant to replace the CLI, a graphical interface has a number of
|
||||
advantages in certain situations. For example, when editing a tag, a natural CLI
|
||||
makes you retype the whole thing---common GUI conventions can be used to just
|
||||
edit the part of the tag you want to change. A graphical interface could also
|
||||
drastically increase the number of people who can use beets.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
The Web interface depends on `Flask`_. To get it, just run ``pip install
|
||||
flask``.
|
||||
|
||||
.. _Flask: http://flask.pocoo.org/
|
||||
|
||||
Put ``plugins=web`` in your ``.beetsconfig`` to enable the plugin.
|
||||
|
||||
Run the Server
|
||||
--------------
|
||||
|
||||
Then just type ``beet web`` to start the server and go to
|
||||
http://localhost:8337/. This is what it looks like:
|
||||
|
||||
.. image:: beetsweb.png
|
||||
|
||||
You can also specify the hostname and port number used by the Web server. These
|
||||
can be specified on the command line or in the ``[web]`` section of your
|
||||
[Usage#Configuration .beetsconfig].
|
||||
|
||||
On the command line, use ``beet web [HOSTNAME] [PORT]``. In the config file, use
|
||||
something like this::
|
||||
|
||||
[web]
|
||||
host=127.0.0.1
|
||||
port=8888
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Type queries into the little search box. Double-click a track to play it with
|
||||
`HTML5 Audio`_.
|
||||
|
||||
.. _HTML5 Audio: http://www.w3.org/TR/html-markup/audio.html
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
The Web backend is built using a simple REST+JSON API with the excellent
|
||||
`Flask`_ library. The frontend is a single-page application written with
|
||||
`Backbone.js`_. This allows future non-Web clients to use the same backend API.
|
||||
|
||||
.. _Flask: http://flask.pocoo.org/
|
||||
.. _Backbone.js: http://documentcloud.github.com/backbone/
|
||||
|
||||
Eventually, to make the Web player really viable, we should use a Flash fallback
|
||||
for unsupported formats/browsers. There are a number of options for this:
|
||||
|
||||
* `audio.js`_
|
||||
* `html5media`_
|
||||
* `MediaElement.js`_
|
||||
|
||||
.. _audio.js: http://kolber.github.com/audiojs/
|
||||
.. _html5media: http://html5media.info/
|
||||
.. _MediaElement.js: http://mediaelementjs.com/
|
||||
190
docs/reference/cli.rst
Normal file
190
docs/reference/cli.rst
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
Command-Line Interface
|
||||
======================
|
||||
|
||||
**beet** is the command-line interface to beets.
|
||||
|
||||
You invoke beets by specifying a *command*, like so::
|
||||
|
||||
beet COMMAND [ARGS...]
|
||||
|
||||
The rest of this document describes the available commands. If you ever need a
|
||||
quick list of what's available, just type ``beet help`` or ``beet help COMMAND``
|
||||
or help with a specific command.
|
||||
|
||||
import
|
||||
------
|
||||
::
|
||||
|
||||
beet import [-CWAPRqst] [-l LOGPATH] DIR...
|
||||
beet import [options] -L QUERY
|
||||
|
||||
Add music to your library, attempting to get correct tags for it from
|
||||
MusicBrainz.
|
||||
|
||||
Point the command at a directory full of music. The directory can be a single
|
||||
album or a directory whose leaf subdirectories are albums (the latter case is
|
||||
true of typical Artist/Album organizations and many people's "downloads"
|
||||
folders). The music will be copied to a configurable directory structure (see
|
||||
below) and added to a library database (see below). The command is interactive
|
||||
and will try to get you to verify MusicBrainz tags that it thinks are suspect.
|
||||
(This means that importing a large amount of music is therefore very tedious
|
||||
right now; this is something we need to work on. Read the
|
||||
:doc:`autotagging guide </guides/tagger>` if you need help.)
|
||||
|
||||
* By default, the command copies files your the library directory and
|
||||
updates the ID3 tags on your music. If you'd like to leave your music
|
||||
files untouched, try the ``-C`` (don't copy) and ``-W`` (don't write tags)
|
||||
options. You can also disable this behavior by default in the
|
||||
configuration file (below).
|
||||
|
||||
* Also, you can disable the autotagging behavior entirely using ``-A``
|
||||
(don't autotag) -- then your music will be imported with its existing
|
||||
metadata.
|
||||
|
||||
* During a long tagging import, it can be useful to keep track of albums
|
||||
that weren't tagged successfully -- either because they're not in the
|
||||
MusicBrainz database or because something's wrong with the files. Use the
|
||||
``-l`` option to specify a filename to log every time you skip and album
|
||||
or import it "as-is" or an album gets skipped as a duplicate.
|
||||
|
||||
* Relatedly, the ``-q`` (quiet) option can help with large imports by
|
||||
autotagging without ever bothering to ask for user input. Whenever the
|
||||
normal autotagger mode would ask for confirmation, the quiet mode
|
||||
pessimistically skips the album. The quiet mode also disables the tagger's
|
||||
ability to resume interrupted imports.
|
||||
|
||||
* Speaking of resuming interrupted imports, the tagger will prompt you if it
|
||||
seems like the last import of the directory was interrupted (by you or by
|
||||
a crash). If you want to skip this prompt, you can say "yes" automatically
|
||||
by providing ``-p`` or "no" using ``-P``. The resuming feature can be
|
||||
disabled by default using a configuration option (see below).
|
||||
|
||||
* If you want to import only the *new* stuff from a directory, use the
|
||||
``-i``
|
||||
option to run an *incremental* import. With this flag, beets will keep
|
||||
track of every directory it ever imports and avoid importing them again.
|
||||
This is useful if you have an "incoming" directory that you periodically
|
||||
add things to.
|
||||
|
||||
* By default, beets will proceed without asking if it finds a very close
|
||||
metadata match. To disable this and have the importer as you every time,
|
||||
use the ``-t`` (for *timid*) option.
|
||||
|
||||
* The importer automatically tries to download album art for each album it
|
||||
finds. To disable or enable this, use the ``-r`` or ``-R`` options.
|
||||
|
||||
* The importer typically works in a whole-album-at-a-time mode. If you
|
||||
instead want to import individual, non-album tracks, use the *singleton*
|
||||
mode by supplying the ``-s`` option.
|
||||
|
||||
Reimporting
|
||||
^^^^^^^^^^^
|
||||
|
||||
The ``import`` command can also be used to "reimport" music that you've already
|
||||
added to your library. This is useful for updating tags as they are fixed in the
|
||||
MusicBrainz database, for when you change your mind about some selections you
|
||||
made during the initial import, or if you prefer to import everything "as-is"
|
||||
and then correct tags later.
|
||||
|
||||
Just point the ``beet import`` command at a directory of files that are already
|
||||
catalogged in your library. Beets will automatically detect this situation and
|
||||
avoid duplicating any items. In this situation, the "copy files" option
|
||||
(``-c``/``-C`` on the command line or ``import_copy`` in the config file) has
|
||||
slightly different behavior: it causes files to be *moved*, rather than
|
||||
duplicated, if they're already in your library. That is, your directory
|
||||
structure will be updated to reflect the new tags if copying is enabled; you
|
||||
never end up with two copies of the file. That means that the "delete files"
|
||||
(``-d`` or ``import_delete``) option is ignored when re-importing as well.
|
||||
|
||||
The ``-L`` (``--library``) flag is also useful for retagging. Instead of listing
|
||||
paths you want to import on the command line, specify a :doc:`query string
|
||||
<query>` that matches items from your library. In this case, the ``-s``
|
||||
(singleton) flag controls whether the query matches individual items or full
|
||||
albums. If you want to retag your whole library, just supply a null query, which
|
||||
matches everything: ``beet import -L``
|
||||
|
||||
list
|
||||
----
|
||||
::
|
||||
|
||||
beet list [-ap] QUERY
|
||||
|
||||
:doc:`Queries <query>` the database for music.
|
||||
|
||||
Want to search for "Gronlandic Edit" by of Montreal? Try ``beet list
|
||||
gronlandic``. Maybe you want to see everything released in 2009 with
|
||||
"vegetables" in the title? Try ``beet list year:2009 title:vegetables``. (Read
|
||||
more in :doc:`query`.) You can use the ``-a`` switch to search for
|
||||
albums instead of individual items. The ``-p`` option makes beets print out
|
||||
filenames of matched items, which might be useful for piping into other Unix
|
||||
commands (such as `xargs`_).
|
||||
|
||||
.. _xargs: http://en.wikipedia.org/wiki/Xargs
|
||||
|
||||
remove
|
||||
------
|
||||
::
|
||||
|
||||
beet remove [-ad] QUERY
|
||||
|
||||
Remove music from your library.
|
||||
|
||||
This command uses the same :doc:`query <query>` syntax as the ``list`` command.
|
||||
You'll be shown a list of the files that will be removed and asked to confirm.
|
||||
By default, this just removes entries from the library database; it doesn't
|
||||
touch the files on disk. To actually delete the files, use ``beet remove -d``.
|
||||
|
||||
modify
|
||||
------
|
||||
::
|
||||
|
||||
beet modify [-MWay] QUERY FIELD=VALUE...
|
||||
|
||||
Change the metadata for items or albums in the database.
|
||||
|
||||
Supply a :doc:`query <query>` matching the things you want to change and a
|
||||
series of ``field=value`` pairs. For example, ``beet modify genius of love
|
||||
artist="Tom Tom Club"`` will change the artist for the track "Genius of Love."
|
||||
The ``-a`` switch operates on albums instead of individual tracks. Items will
|
||||
automatically be moved around when necessary if they're in your library
|
||||
directory, but you can disable that with ``-M``. Tags will be written to the
|
||||
files according to the settings you have for imports, but these can be
|
||||
overridden with ``-w`` (write tags, the default) and ``-W`` (don't write tags).
|
||||
Finally, this command politely asks for your permission before making any
|
||||
changes, but you can skip that prompt with the ``-y`` switch.
|
||||
|
||||
move
|
||||
----
|
||||
::
|
||||
|
||||
beet move [-ca] [-d DIR] QUERY
|
||||
|
||||
Move or copy items in your library.
|
||||
|
||||
This command, by default, acts as a library consolidator: items matching the
|
||||
query are renamed into your library directory structure. By specifying a
|
||||
destination directory with ``-d`` manually, you can move items matching a query
|
||||
anywhere in your filesystem. The ``-c`` option copies files instead of moving
|
||||
them. As with other commands, the ``-a`` option matches albums instead of items.
|
||||
|
||||
update
|
||||
------
|
||||
::
|
||||
|
||||
beet update [-aM] QUERY
|
||||
|
||||
Update the library (and, optionally, move files) to reflect out-of-band metadata
|
||||
changes and file deletions.
|
||||
|
||||
This will scan all the matched files and read their tags, populating the
|
||||
database with the new values. By default, files will be renamed according to
|
||||
their new metadata; disable this with ``-M``.
|
||||
|
||||
stats
|
||||
-----
|
||||
::
|
||||
|
||||
beet stats [QUERY]
|
||||
|
||||
Show some statistics on your entire library (if you don't provide a
|
||||
:doc:`query <query>` or the matched items (if you do).
|
||||
134
docs/reference/config.rst
Normal file
134
docs/reference/config.rst
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
.beetsconfig
|
||||
============
|
||||
|
||||
The ``beet`` command reads configuration information from ``~/.beetsconfig``.
|
||||
The file is in INI format, and the following options are available, all of which
|
||||
must appear under the ``[beets]`` section header:
|
||||
|
||||
* ``library``: path to the beets library file. Defaults to
|
||||
``~/.beetsmusic.blb``.
|
||||
|
||||
* ``directory``: the directory to which files will be copied/moved when adding
|
||||
them to the library. Defaults to ``~/Music``.
|
||||
|
||||
* ``import_copy``: either ``yes`` or ``no``, indicating whether to copy files
|
||||
into the library directory when using ``beet import``. Defaults to ``yes``.
|
||||
Can be overridden with the ``-c`` and ``-C`` command-line options.
|
||||
|
||||
* ``import_write``: either ``yes`` or ``no``, controlling whether metadata
|
||||
(e.g., ID3) tags are written to files when using ``beet import``. Defaults to
|
||||
``yes``. The ``-w`` and ``-W`` command-line options override this setting.
|
||||
|
||||
* ``import_delete``: either ``yes`` or ``no``. When enabled in conjunction with
|
||||
``import_copy``, deletes original files after they are copied into your
|
||||
library. This might be useful, for example, if you're low on disk space -- but
|
||||
it's risky! Defaults to ``no``.
|
||||
|
||||
* ``import_resume``: either ``yes``, ``no``, or ``ask``. Controls whether
|
||||
interrupted imports should be resumed. "Yes" means that imports are always
|
||||
resumed when possible; "no" means resuming is disabled entirely; "ask" (the
|
||||
default) means that the user should be prompted when resuming is possible. The
|
||||
``-p`` and ``-P`` flags correspond to the "yes" and "no" settings and override
|
||||
this option.
|
||||
|
||||
* ``import_incremental``: either ``yes`` or ``no``, controlling whether imported
|
||||
directories are recorded and whether these recorded directories are skipped.
|
||||
This corresponds to the ``-i`` flag to ``beet import``.
|
||||
|
||||
* ``import_art``: either ``yes`` or ``no``, indicating whether the autotagger
|
||||
should attempt to find and download album cover art for the files it imports.
|
||||
Defaults to ``yes``. The ``-r`` and ``-R`` command-line options override this
|
||||
setting.
|
||||
|
||||
* ``import_quiet_fallback``: either ``skip`` (default) or ``asis``, specifying
|
||||
what should happen in quiet mode (see the ``-q`` flag to ``import``, above)
|
||||
when there is no strong recommendation.
|
||||
|
||||
* ``import_timid``: either ``yes`` or ``no``, controlling whether the importer
|
||||
runs in *timid* mode, in which it asks for confirmation on every autotagging
|
||||
match, even the ones that seem very close. Defaults to ``no``. The ``-t``
|
||||
command-line flag controls the same setting.
|
||||
|
||||
* ``import_log``: specifies a filename where the importer's log should be kept.
|
||||
By default, no log is written. This can be overridden with the ``-l`` flag to
|
||||
``import``.
|
||||
|
||||
* ``art_filename``: when importing album art, the name of the file (without
|
||||
extension) where the cover art image should be placed. Defaults to ``cover``
|
||||
(i.e., images will be named ``cover.jpg`` or ``cover.png`` and placed in the
|
||||
album's directory).
|
||||
|
||||
* ``plugins``: a space-separated list of plugin module names to load. For
|
||||
instance, beets includes the [BPD BPD] plugin for playing music.
|
||||
|
||||
* ``pluginpath``: a colon-separated list of directories to search for plugins.
|
||||
These paths are just added to ``sys.path`` before the plugins are loaded. The
|
||||
plugins still have to be contained in a ``beetsplug`` namespace package.
|
||||
|
||||
* ``threaded``: either ``yes`` or ``no``, indicating whether the autotagger
|
||||
should use multiple threads. This makes things faster but may behave
|
||||
strangely. Defaults to ``yes``.
|
||||
|
||||
* ``color``: either ``yes`` or ``no``; whether to use color in console output
|
||||
(currently only in the ``import`` command). Turn this off if your terminal
|
||||
doesn't support ANSI colors.
|
||||
|
||||
You can also configure the directory hierarchy beets uses to store music. That
|
||||
uses the ``[paths]`` section instead of the ``[beets]`` section. Each string is
|
||||
a `Python template string`_ that can refer to metadata fields (see below for
|
||||
examples). The extension is added automatically to the end. At the moment, you
|
||||
can specify two special paths: ``default`` (for most releases) and ``comp`` (for
|
||||
"various artist" releases with no dominant artist). You can also specify a
|
||||
different path format for each `MusicBrainz release type`_. The defaults look
|
||||
like this::
|
||||
|
||||
[paths]
|
||||
default: $albumartist/$album/$track $title
|
||||
comp: Compilations/$album/$track title
|
||||
singleton: Non-Album/$artist/$title
|
||||
|
||||
Note the use of ``$albumartist`` instead of ``$artist``; this ensure that albums
|
||||
will be well-organized. (For more about these format strings, see
|
||||
:doc:`pathformat`.)
|
||||
|
||||
.. _Python template string:
|
||||
http://docs.python.org/library/string.html#template-strings
|
||||
.. _MusicBrainz release type:
|
||||
http://wiki.musicbrainz.org/ReleaseType
|
||||
|
||||
Here's an example file::
|
||||
|
||||
[beets]
|
||||
library: /var/music.blb
|
||||
directory: /var/mp3
|
||||
path_format: $genre/$artist/$album/$track $title
|
||||
import_copy: yes
|
||||
import_write: yes
|
||||
import_resume: ask
|
||||
import_art: yes
|
||||
import_quiet_fallback: skip
|
||||
import_timid: no
|
||||
import_log: beetslog.txt
|
||||
art_filename: albumart
|
||||
plugins: bpd
|
||||
pluginpath: ~/beets/myplugins
|
||||
threaded: yes
|
||||
color: yes
|
||||
|
||||
[paths]
|
||||
default: $genre/$albumartist/$album/$track $title
|
||||
soundtrack: Soundtracks/$album/$track $title
|
||||
comp: $genre/$album/$track $title
|
||||
singleton: Singletons/$artist - $track
|
||||
|
||||
[bpd]
|
||||
host: 127.0.0.1
|
||||
port: 6600
|
||||
password: seekrit
|
||||
|
||||
(That ``[bpd]`` section configures the optional :doc:`BPD </plugins/bpd>`
|
||||
plugin.)
|
||||
|
||||
If you want to store your ``.beetsconfig`` file somewhere else for whatever
|
||||
reason, you can specify its path by setting the ``BEETSCONFIG`` environment
|
||||
variable.
|
||||
14
docs/reference/index.rst
Normal file
14
docs/reference/index.rst
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
Reference
|
||||
=========
|
||||
|
||||
This section contains reference materials for various parts of beets. To get
|
||||
started with beets as a new user, though, you may want to read the
|
||||
:doc:`/guides/main` guide first.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
cli
|
||||
config
|
||||
pathformat
|
||||
query
|
||||
83
docs/reference/pathformat.rst
Normal file
83
docs/reference/pathformat.rst
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
Path Formats
|
||||
============
|
||||
|
||||
The ``[paths]`` section of the config file (read more on the [Usage] page) lets
|
||||
you specify the directory and file naming scheme for your music library. You
|
||||
specify templates using Python template string notation---that is, prefixing
|
||||
names with ``$`` characters---and beets fills in the appropriate values.
|
||||
|
||||
For example, consider this path format string: ``$albumartist/$album/$track
|
||||
$title``
|
||||
|
||||
Here are some paths this format will generate:
|
||||
|
||||
* ``Yeah Yeah Yeahs/It's Blitz!/01 Zero.mp3``
|
||||
|
||||
* ``Spank Rock/YoYoYoYoYo/11 Competition.mp3``
|
||||
|
||||
* ``The Magnetic Fields/Realism/01 You Must Be Out of Your Mind.mp3``
|
||||
|
||||
Note that in path formats, you almost certainly want to use ``$albumartist`` and
|
||||
not ``$artist``. The latter refers to the "track artist" when it is present,
|
||||
which means that albums that have tracks from different artists on them (like
|
||||
`Stop Making Sense`_, for example) will be placed into different folders!
|
||||
Continuing with the Stop Making Sense example, you'll end up with most of the
|
||||
tracks in a "Talking Heads" directory and one in a "Tom Tom Club" directory. You
|
||||
probably don't want that! So use ``$albumartist``.
|
||||
|
||||
.. _Stop Making Sense:
|
||||
http://musicbrainz.org/release/798dcaab-0f1a-4f02-a9cb-61d5b0ddfd36.html
|
||||
|
||||
As a convenience, however, beets allows ``$albumartist`` to fall back to the value for ``$artist`` and vice-versa if one tag is present but the other is not.
|
||||
|
||||
Upgrading from 1.0b6
|
||||
--------------------
|
||||
|
||||
Versions of beets prior to 1.0b7 didn't use a ``[paths]`` section. Instead, they
|
||||
used a single ``path_format`` setting for all music. To support old
|
||||
configuration files, this setting is still respected and overrides the default
|
||||
path formats. However, the setting is deprecated and, if you want to use
|
||||
flexible path formats, you need to remove the ``path_format`` setting and use a
|
||||
``[paths]`` section instead.
|
||||
|
||||
Possible Values
|
||||
---------------
|
||||
|
||||
Here's a (comprehensive?) list of the different values available to path
|
||||
formats. (I will try to keep it up to date, but I might forget. The current list
|
||||
can be found definitively `in the source`_.)
|
||||
|
||||
.. _in the source:
|
||||
http://code.google.com/p/beets/source/browse/beets/library.py#36
|
||||
|
||||
Ordinary metadata:
|
||||
|
||||
* title
|
||||
* artist
|
||||
* album
|
||||
* genre
|
||||
* composer
|
||||
* grouping
|
||||
* year
|
||||
* month
|
||||
* day
|
||||
* track
|
||||
* tracktotal
|
||||
* disc
|
||||
* disctotal
|
||||
* lyrics
|
||||
* comments
|
||||
* bpm
|
||||
* comp
|
||||
|
||||
Audio information:
|
||||
|
||||
* length
|
||||
* bitrate
|
||||
* format
|
||||
|
||||
MusicBrainz IDs:
|
||||
|
||||
* mb_trackid
|
||||
* mb_albumid
|
||||
* mb_artistid
|
||||
115
docs/reference/query.rst
Normal file
115
docs/reference/query.rst
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
Queries
|
||||
=======
|
||||
|
||||
Many of beets' :doc:`commands <cli>` are built around **query strings:**
|
||||
searches that select tracks and albums from your library. This page explains the
|
||||
query string syntax, which is meant to vaguely resemble the syntax used by Web
|
||||
search engines.
|
||||
|
||||
Keyword
|
||||
-------
|
||||
|
||||
This command::
|
||||
|
||||
$ beet list love
|
||||
|
||||
will show all tracks matching the query string ``love``. Any unadorned word like this matches *anywhere* in a track's metadata, so you'll see all the tracks with "love" in their title, in their album name, in the artist, and so on.
|
||||
|
||||
For example, this is what I might see when I run the command above::
|
||||
|
||||
Against Me! - Reinventing Axl Rose - I Still Love You Julie
|
||||
Air - Love 2 - Do the Joy
|
||||
Bag Raiders - Turbo Love - Shooting Stars
|
||||
Bat for Lashes - Two Suns - Good Love
|
||||
...
|
||||
|
||||
Combining Keywords
|
||||
------------------
|
||||
|
||||
Multiple keywords are implicitly joined with a Boolean "and." That is, if a
|
||||
query has two keywords, it only matches tracks that contain *both* keywords. For
|
||||
example, this command::
|
||||
|
||||
$ beet ls magnetic tomorrow
|
||||
|
||||
matches songs from the album "The House of Tomorrow" by The Magnetic Fields in
|
||||
my library. It *doesn't* match other songs by the Magnetic Fields, nor does it
|
||||
match "Tomorrowland" by Walter Meego---those songs only have *one* of the two
|
||||
keywords I specified.
|
||||
|
||||
Specific Fields
|
||||
---------------
|
||||
|
||||
Sometimes, a broad keyword match isn't enough. Beets supports a syntax that lets
|
||||
you query a specific field---only the artist, only the track title, and so on.
|
||||
Just say ``field:value``, where ``field`` is the name of the thing you're trying
|
||||
to match (such as ``artist``, ``album``, or ``title``) and ``value`` is the
|
||||
keyword you're searching for.
|
||||
|
||||
For example, while this query::
|
||||
|
||||
$ beet list dream
|
||||
|
||||
matches a lot of songs in my library, this more-specific query::
|
||||
|
||||
$ beet list artist:dream
|
||||
|
||||
only matches songs by the artist The-Dream. One query I especially appreciate is
|
||||
one that matches albums by year::
|
||||
|
||||
$ beet list -a year:2011
|
||||
|
||||
Recall that ``-a`` makes the ``list`` command show albums instead of individual
|
||||
tracks, so this command shows me all the releases I have from this year.
|
||||
|
||||
Phrases
|
||||
-------
|
||||
|
||||
As of beets 1.0b9, you can query for strings with spaces in them by quoting or escaping them using your shell's argument syntax. For example, this command::
|
||||
|
||||
$ beet list the rebel
|
||||
|
||||
shows several tracks in my library, but these (equivalent) commands::
|
||||
|
||||
$ beet list "the rebel"
|
||||
$ beet list the\ rebel
|
||||
|
||||
only match the track "The Rebel" by Buck 65. Note that the quotes and
|
||||
backslashes are not part of beets' syntax; I'm just using the escaping
|
||||
functionality of by shell (bash or zsh, for instance) to pass ``the rebel`` as a
|
||||
single argument instead of two.
|
||||
|
||||
Path Queries
|
||||
------------
|
||||
|
||||
Sometimes it's useful to find all the items in your library that are
|
||||
(recursively) inside a certain directory. With beets 1.0b9, use the ``path:``
|
||||
field to do this::
|
||||
|
||||
$ beet list path:/my/music/directory
|
||||
|
||||
In fact, beets automatically recognizes any query term containing a path
|
||||
separator (``/`` on POSIX systems) as a path query, so this command is
|
||||
equivalent::
|
||||
|
||||
$ beet list /my/music/directory
|
||||
|
||||
Note that this only matches items that are *already in your library*, so a path
|
||||
query won't necessarily find *all* the audio files in a directory---just the
|
||||
ones you've already added to your beets library.
|
||||
|
||||
Future Work
|
||||
-----------
|
||||
|
||||
Here are a few things that the query syntax should eventually support but aren't
|
||||
yet implemented. Please drop me a line if you have other ideas.
|
||||
|
||||
* "Null" queries. It's currently impossible to query for items that have an
|
||||
empty artist. Perhaps the syntax should look like ``artist:NULL`` or
|
||||
``artist:EMPTY``.
|
||||
|
||||
* Regular expressions. Beets queries are based on simple case-insensitive
|
||||
substring matching, but regexes might be useful occasionally as well. Maybe
|
||||
the syntax should look something like ``re:artist:^.*$`` or, perhaps,
|
||||
``artist:/^.*$/``. Having regular expressions could help with null queries
|
||||
(above): ``re:artist:^$``.
|
||||
|
|
@ -57,49 +57,49 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
shutil.rmtree(self.otherdir)
|
||||
|
||||
def test_move_arrives(self):
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
self.assertExists(self.dest)
|
||||
|
||||
def test_move_to_custom_dir(self):
|
||||
self.i.move(self.lib, basedir=self.otherdir)
|
||||
self.lib.move(self.i, basedir=self.otherdir)
|
||||
self.assertExists(join(self.otherdir, 'one', 'two', 'three.mp3'))
|
||||
|
||||
def test_move_departs(self):
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
self.assertNotExists(self.path)
|
||||
|
||||
def test_move_in_lib_prunes_empty_dir(self):
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
old_path = self.i.path
|
||||
self.assertExists(old_path)
|
||||
|
||||
self.i.artist = 'newArtist'
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
self.assertNotExists(old_path)
|
||||
self.assertNotExists(os.path.dirname(old_path))
|
||||
|
||||
def test_copy_arrives(self):
|
||||
self.i.move(self.lib, copy=True)
|
||||
self.lib.move(self.i, copy=True)
|
||||
self.assertExists(self.dest)
|
||||
|
||||
def test_copy_does_not_depart(self):
|
||||
self.i.move(self.lib, copy=True)
|
||||
self.lib.move(self.i, copy=True)
|
||||
self.assertExists(self.path)
|
||||
|
||||
def test_move_changes_path(self):
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
self.assertEqual(self.i.path, util.normpath(self.dest))
|
||||
|
||||
def test_copy_already_at_destination(self):
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
old_path = self.i.path
|
||||
self.i.move(self.lib, copy=True)
|
||||
self.lib.move(self.i, copy=True)
|
||||
self.assertEqual(self.i.path, old_path)
|
||||
|
||||
def test_move_already_at_destination(self):
|
||||
self.i.move(self.lib)
|
||||
self.lib.move(self.i)
|
||||
old_path = self.i.path
|
||||
self.i.move(self.lib, copy=False)
|
||||
self.lib.move(self.i, copy=False)
|
||||
self.assertEqual(self.i.path, old_path)
|
||||
|
||||
def test_read_only_file_copied_writable(self):
|
||||
|
|
@ -107,7 +107,7 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
os.chmod(self.path, 0444)
|
||||
|
||||
try:
|
||||
self.i.move(self.lib, copy=True)
|
||||
self.lib.move(self.i, copy=True)
|
||||
self.assertTrue(os.access(self.i.path, os.W_OK))
|
||||
finally:
|
||||
# Make everything writable so it can be cleaned up.
|
||||
|
|
@ -192,7 +192,7 @@ class AlbumFileTest(unittest.TestCase):
|
|||
def test_albuminfo_move_to_custom_dir(self):
|
||||
self.ai.move(basedir=self.otherdir)
|
||||
self.lib.load(self.i)
|
||||
self.assert_('testotherdir' in self.i.path)
|
||||
self.assertTrue('testotherdir' in self.i.path)
|
||||
|
||||
class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
|
||||
def setUp(self):
|
||||
|
|
@ -256,7 +256,7 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
i2.path = self.i.path
|
||||
i2.artist = 'someArtist'
|
||||
ai = self.lib.add_album((i2,))
|
||||
i2.move(self.lib, True)
|
||||
self.lib.move(i2, True)
|
||||
|
||||
self.assertEqual(ai.artpath, None)
|
||||
ai.set_art(newart)
|
||||
|
|
@ -272,7 +272,7 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
i2.path = self.i.path
|
||||
i2.artist = 'someArtist'
|
||||
ai = self.lib.add_album((i2,))
|
||||
i2.move(self.lib, True)
|
||||
self.lib.move(i2, True)
|
||||
ai.set_art(newart)
|
||||
|
||||
# Set the art again.
|
||||
|
|
@ -286,7 +286,7 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
i2.path = self.i.path
|
||||
i2.artist = 'someArtist'
|
||||
ai = self.lib.add_album((i2,))
|
||||
i2.move(self.lib, True)
|
||||
self.lib.move(i2, True)
|
||||
|
||||
# Copy the art to the destination.
|
||||
artdest = ai.art_destination(newart)
|
||||
|
|
@ -308,7 +308,7 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
i2.path = self.i.path
|
||||
i2.artist = 'someArtist'
|
||||
ai = self.lib.add_album((i2,))
|
||||
i2.move(self.lib, True)
|
||||
self.lib.move(i2, True)
|
||||
ai.set_art(newart)
|
||||
|
||||
mode = stat.S_IMODE(os.stat(ai.artpath).st_mode)
|
||||
|
|
@ -320,6 +320,35 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
os.chmod(newart, 0777)
|
||||
os.chmod(ai.artpath, 0777)
|
||||
|
||||
def test_move_last_file_moves_albumart(self):
|
||||
oldartpath = self.lib.albums()[0].artpath
|
||||
self.assertExists(oldartpath)
|
||||
|
||||
self.ai.album = 'different_album'
|
||||
self.lib.move(self.i)
|
||||
|
||||
artpath = self.lib.albums()[0].artpath
|
||||
self.assertTrue('different_album' in artpath)
|
||||
self.assertExists(artpath)
|
||||
self.assertNotExists(oldartpath)
|
||||
|
||||
def test_move_not_last_file_does_not_move_albumart(self):
|
||||
i2 = item()
|
||||
i2.albumid = self.ai.id
|
||||
self.lib.add(i2)
|
||||
|
||||
oldartpath = self.lib.albums()[0].artpath
|
||||
self.assertExists(oldartpath)
|
||||
|
||||
self.i.album = 'different_album'
|
||||
self.i.album_id = None # detach from album
|
||||
self.lib.move(self.i)
|
||||
|
||||
artpath = self.lib.albums()[0].artpath
|
||||
self.assertFalse('different_album' in artpath)
|
||||
self.assertEqual(artpath, oldartpath)
|
||||
self.assertExists(oldartpath)
|
||||
|
||||
class RemoveTest(unittest.TestCase, _common.ExtraAsserts):
|
||||
def setUp(self):
|
||||
# Make library and item.
|
||||
|
|
@ -399,7 +428,7 @@ class SoftRemoveTest(unittest.TestCase, _common.ExtraAsserts):
|
|||
except OSError:
|
||||
self.fail('OSError when removing path')
|
||||
|
||||
class SafeMoveCopyTest(unittest.TestCase):
|
||||
class SafeMoveCopyTest(unittest.TestCase, _common.ExtraAsserts):
|
||||
def setUp(self):
|
||||
self.path = os.path.join(_common.RSRC, 'testfile')
|
||||
touch(self.path)
|
||||
|
|
@ -420,9 +449,13 @@ class SafeMoveCopyTest(unittest.TestCase):
|
|||
|
||||
def test_successful_move(self):
|
||||
util.move(self.path, self.dest)
|
||||
self.assertExists(self.dest)
|
||||
self.assertNotExists(self.path)
|
||||
|
||||
def test_successful_copy(self):
|
||||
util.copy(self.path, self.dest)
|
||||
self.assertExists(self.dest)
|
||||
self.assertExists(self.path)
|
||||
|
||||
def test_unsuccessful_move(self):
|
||||
with self.assertRaises(OSError):
|
||||
|
|
@ -432,6 +465,34 @@ class SafeMoveCopyTest(unittest.TestCase):
|
|||
with self.assertRaises(OSError):
|
||||
util.copy(self.path, self.otherpath)
|
||||
|
||||
def test_self_move(self):
|
||||
util.move(self.path, self.path)
|
||||
self.assertExists(self.path)
|
||||
|
||||
def test_self_copy(self):
|
||||
util.copy(self.path, self.path)
|
||||
self.assertExists(self.path)
|
||||
|
||||
class PruneTest(unittest.TestCase, _common.ExtraAsserts):
|
||||
def setUp(self):
|
||||
self.base = os.path.join(_common.RSRC, 'testdir')
|
||||
os.mkdir(self.base)
|
||||
self.sub = os.path.join(self.base, 'subdir')
|
||||
os.mkdir(self.sub)
|
||||
def tearDown(self):
|
||||
if os.path.exists(self.base):
|
||||
shutil.rmtree(self.base)
|
||||
|
||||
def test_prune_existent_directory(self):
|
||||
util.prune_dirs(self.sub, self.base)
|
||||
self.assertExists(self.base)
|
||||
self.assertNotExists(self.sub)
|
||||
|
||||
def test_prune_nonexistent_directory(self):
|
||||
util.prune_dirs(os.path.join(self.sub, 'another'), self.base)
|
||||
self.assertExists(self.base)
|
||||
self.assertNotExists(self.sub)
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -502,6 +502,58 @@ class ConfigTest(unittest.TestCase):
|
|||
library: /xxx/yyy/not/a/real/path
|
||||
"""), func)
|
||||
|
||||
class UtilTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.io = _common.DummyIO()
|
||||
self.io.install()
|
||||
def tearDown(self):
|
||||
self.io.restore()
|
||||
|
||||
def test_showdiff_strings(self):
|
||||
commands._showdiff('field', 'old', 'new', True)
|
||||
out = self.io.getoutput()
|
||||
self.assertTrue('field' in out)
|
||||
|
||||
def test_showdiff_identical(self):
|
||||
commands._showdiff('field', 'old', 'old', True)
|
||||
out = self.io.getoutput()
|
||||
self.assertFalse('field' in out)
|
||||
|
||||
def test_showdiff_ints(self):
|
||||
commands._showdiff('field', 2, 3, True)
|
||||
out = self.io.getoutput()
|
||||
self.assertTrue('field' in out)
|
||||
|
||||
def test_showdiff_ints_no_color(self):
|
||||
commands._showdiff('field', 2, 3, False)
|
||||
out = self.io.getoutput()
|
||||
self.assertTrue('field' in out)
|
||||
|
||||
def test_showdiff_shows_both(self):
|
||||
commands._showdiff('field', 'old', 'new', True)
|
||||
out = self.io.getoutput()
|
||||
self.assertTrue('old' in out)
|
||||
self.assertTrue('new' in out)
|
||||
|
||||
def test_showdiff_floats_close_to_identical(self):
|
||||
commands._showdiff('field', 1.999, 2.001, True)
|
||||
out = self.io.getoutput()
|
||||
self.assertFalse('field' in out)
|
||||
|
||||
def test_showdiff_floats_differenct(self):
|
||||
commands._showdiff('field', 1.999, 4.001, True)
|
||||
out = self.io.getoutput()
|
||||
self.assertTrue('field' in out)
|
||||
|
||||
def test_showdiff_ints_colorizing_is_not_stringwise(self):
|
||||
commands._showdiff('field', 222, 333, True)
|
||||
complete_diff = self.io.getoutput().split()[1]
|
||||
|
||||
commands._showdiff('field', 222, 232, True)
|
||||
partial_diff = self.io.getoutput().split()[1]
|
||||
|
||||
self.assertEqual(complete_diff, partial_diff)
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ os.chdir(pkgpath)
|
|||
|
||||
def suite():
|
||||
s = unittest.TestSuite()
|
||||
# get the suite() of every module in this directory begining with test_
|
||||
# Get the suite() of every module in this directory beginning with
|
||||
# "test_".
|
||||
for fname in os.listdir(pkgpath):
|
||||
match = re.match(r'(test_\S+)\.py$', fname)
|
||||
if match:
|
||||
|
|
|
|||
Loading…
Reference in a new issue