diff --git a/.hgignore b/.hgignore
index 1453aed71..a49037122 100644
--- a/.hgignore
+++ b/.hgignore
@@ -2,3 +2,4 @@
^beets\.egg-info/
^build/
^MANIFEST$
+^docs/_build/
diff --git a/NEWS b/NEWS
index 0745302d6..5b4ee48c5 100644
--- a/NEWS
+++ b/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
-----
diff --git a/README.rst b/README.rst
index cf1ce355a..93b3bed35 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/beets/importer.py b/beets/importer.py
index a6edfc949..66d35314d 100644
--- a/beets/importer.py
+++ b/beets/importer.py
@@ -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()
diff --git a/beets/library.py b/beets/library.py
index 90f7952a3..546099568 100644
--- a/beets/library.py
+++ b/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,
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index cc4b7d50d..4ae9900e4 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -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.
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 6bd015576..284c2124d 100755
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -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()
diff --git a/beets/util/__init__.py b/beets/util/__init__.py
index 67a878342..0895b8c2d 100644
--- a/beets/util/__init__.py
+++ b/beets/util/__init__.py
@@ -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)
diff --git a/beetsplug/bpd/bluelet.py b/beetsplug/bpd/bluelet.py
index b6f9e7277..626587813 100644
--- a/beetsplug/bpd/bluelet.py
+++ b/beetsplug/bpd/bluelet.py
@@ -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)}
diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py
index 01277914b..43ccd9742 100644
--- a/beetsplug/web/__init__.py
+++ b/beetsplug/web/__init__.py
@@ -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]
diff --git a/beetsplug/web/templates/index.html b/beetsplug/web/templates/index.html
index 27f9ce30a..63a0b152a 100644
--- a/beetsplug/web/templates/index.html
+++ b/beetsplug/web/templates/index.html
@@ -69,7 +69,7 @@
Bitrate
<%= Math.round(bitrate/1000) %> kbps
<% if (mb_trackid) { %>
- MuscBrainz entry
+ MusicBrainz entry
view
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 000000000..3484f95c4
--- /dev/null
+++ b/docs/Makefile
@@ -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 ' where 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."
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 000000000..63af172d5
--- /dev/null
+++ b/docs/changelog.rst
@@ -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 ` 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 `. 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.
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 000000000..bb28f7942
--- /dev/null
+++ b/docs/conf.py
@@ -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),
+]
diff --git a/docs/guides/index.rst b/docs/guides/index.rst
new file mode 100644
index 000000000..0544e2b55
--- /dev/null
+++ b/docs/guides/index.rst
@@ -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
diff --git a/docs/guides/main.rst b/docs/guides/main.rst
new file mode 100644
index 000000000..2304f01b2
--- /dev/null
+++ b/docs/guides/main.rst
@@ -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
+`.
+
+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 `,
+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 `). 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
diff --git a/docs/guides/tagger.rst b/docs/guides/tagger.rst
new file mode 100644
index 000000000..96f72d3a9
--- /dev/null
+++ b/docs/guides/tagger.rst
@@ -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 ` 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 `, 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
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 000000000..4ee7995d4
--- /dev/null
+++ b/docs/index.rst
@@ -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
diff --git a/docs/plugins/beetsweb.png b/docs/plugins/beetsweb.png
new file mode 100644
index 000000000..7b04d9586
Binary files /dev/null and b/docs/plugins/beetsweb.png differ
diff --git a/docs/plugins/bpd.rst b/docs/plugins/bpd.rst
new file mode 100644
index 000000000..5d30781d5
--- /dev/null
+++ b/docs/plugins/bpd.rst
@@ -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
diff --git a/docs/plugins/embedart.rst b/docs/plugins/embedart.rst
new file mode 100644
index 000000000..ee5a8363d
--- /dev/null
+++ b/docs/plugins/embedart.rst
@@ -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 `. 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
diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst
new file mode 100644
index 000000000..436973286
--- /dev/null
+++ b/docs/plugins/index.rst
@@ -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.
diff --git a/docs/plugins/lastid.rst b/docs/plugins/lastid.rst
new file mode 100644
index 000000000..ce29eab6b
--- /dev/null
+++ b/docs/plugins/lastid.rst
@@ -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``.
diff --git a/docs/plugins/mpdupdate.rst b/docs/plugins/mpdupdate.rst
new file mode 100644
index 000000000..fda36e5b9
--- /dev/null
+++ b/docs/plugins/mpdupdate.rst
@@ -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.
diff --git a/docs/plugins/web.rst b/docs/plugins/web.rst
new file mode 100644
index 000000000..1874a9381
--- /dev/null
+++ b/docs/plugins/web.rst
@@ -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/
diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst
new file mode 100644
index 000000000..b02ce2aa1
--- /dev/null
+++ b/docs/reference/cli.rst
@@ -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 ` 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
+` 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 ` 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 ` 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 ` 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 ` or the matched items (if you do).
diff --git a/docs/reference/config.rst b/docs/reference/config.rst
new file mode 100644
index 000000000..736869d78
--- /dev/null
+++ b/docs/reference/config.rst
@@ -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 `
+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.
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
new file mode 100644
index 000000000..42600dc93
--- /dev/null
+++ b/docs/reference/index.rst
@@ -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
diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst
new file mode 100644
index 000000000..7e2e344a0
--- /dev/null
+++ b/docs/reference/pathformat.rst
@@ -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
diff --git a/docs/reference/query.rst b/docs/reference/query.rst
new file mode 100644
index 000000000..67af2ab50
--- /dev/null
+++ b/docs/reference/query.rst
@@ -0,0 +1,115 @@
+Queries
+=======
+
+Many of beets' :doc:`commands ` 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:^$``.
diff --git a/test/test_files.py b/test/test_files.py
index d99292959..f3dbb4915 100644
--- a/test/test_files.py
+++ b/test/test_files.py
@@ -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__)
diff --git a/test/test_ui.py b/test/test_ui.py
index 6b56046bd..eaef1c1c0 100644
--- a/test/test_ui.py
+++ b/test/test_ui.py
@@ -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__)
diff --git a/test/testall.py b/test/testall.py
index 136c510fe..88333121e 100755
--- a/test/testall.py
+++ b/test/testall.py
@@ -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: