This commit is contained in:
Adrian Sampson 2011-09-18 15:39:38 -07:00
commit f48fefdf61
33 changed files with 2727 additions and 100 deletions

View file

@ -2,3 +2,4 @@
^beets\.egg-info/
^build/
^MANIFEST$
^docs/_build/

3
NEWS
View file

@ -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
-----

View file

@ -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

View file

@ -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()

View file

@ -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,

View file

@ -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.

View file

@ -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()

View file

@ -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)

View file

@ -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)}

View file

@ -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]

View file

@ -69,7 +69,7 @@
<dt>Bitrate</dt>
<dd><%= Math.round(bitrate/1000) %> kbps</dd>
<% if (mb_trackid) { %>
<dt>MuscBrainz entry</dt>
<dt>MusicBrainz entry</dt>
<dd>
<a target="_blank" href="http://musicbrainz.org/recording/<%= mb_trackid %>">view</a>
</dd>

130
docs/Makefile Normal file
View file

@ -0,0 +1,130 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/beets.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/beets.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/beets"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/beets"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

625
docs/changelog.rst Normal file
View file

@ -0,0 +1,625 @@
Changelog
=========
1.0b10 (September XX, 2011)
---------------------------
This version of beets focuses on making it easier to manage your metadata
*after* you've imported it. A bumper crop of new commands has been added: a
manual tag editor (``modify``), a tool to pick up out-of-band deletions and
modifications (``update``), and functionality for moving and copying files
around (``move``). Furthermore, the concept of "re-importing" is new: you can
choose to re-run beets' advanced autotagger on any files you already have in
your library if you change your mind after you finish the initial import.
As a couple of added bonuses, imports can now automatically skip
previously-imported directories (with the ``-i`` flag) and there's an
:doc:`experimental Web interface </plugins/web>` to beets in a new standard
plugin.
* A new ``beet modify`` command enables **manual, command-line-based
modification** of music metadata. Pass it a query along with ``field=value``
pairs that specify the changes you want to make.
* A new ``beet update`` command updates the database to reflect **changes in the
on-disk metadata**. You can now use an external program to edit tags on files,
remove files and directories, etc., and then run ``beet update`` to make sure
your beets library is in sync. This will also rename files to reflect their
new metadata.
* A new ``beet move`` command can **copy or move files** into your library
directory or to another specified directory.
* When importing files that are already in the library database, the items are
no longer duplicated---instead, the library is updated to reflect the new
metadata. This way, the import command can be transparently used as a
**re-import**.
* Relatedly, the ``-L`` flag to the "import" command makes it take a query as
its argument instead of a list of directories. The matched albums (or items,
depending on the ``-s`` flag) are then re-imported.
* A new flag ``-i`` to the import command runs **incremental imports**, keeping
track of and skipping previously-imported directories. This has the effect of
making repeated import commands pick up only newly-added directories. The
``import_incremental`` config option makes this the default.
* When pruning directories, "clutter" files such as ``.DS_Store`` and
``Thumbs.db`` are ignored (and removed with otherwise-empty directories).
* The :doc:`/plugins/web` encapsulates a simple **Web-based GUI for beets**. The
current iteration can browse the library and play music in browsers that
support `HTML5 Audio`_.
* When moving items that are part of an album, the album art implicitly moves
too.
* Files are no longer silently overwritten when moving and copying files.
* Handle exceptions thrown when running Mutagen.
* Fix a missing ``__future__`` import in ``embed art`` on Python 2.5.
* Fix ID3 and MPEG-4 tag names for the album-artist field.
* Fix Unicode encoding of album artist, album type, and label.
* Fix crash when "copying" an art file that's already in place.
.. _HTML5 Audio: http://www.w3.org/TR/html-markup/audio.html
1.0b9 (July 9, 2011)
--------------------
This release focuses on a large number of small fixes and improvements that turn
beets into a well-oiled, music-devouring machine. See the full release notes,
below, for a plethora of new features.
* **Queries can now contain whitespace.** Spaces passed as shell arguments are
now preserved, so you can use your shell's escaping syntax (quotes or
backslashes, for instance) to include spaces in queries. For example,
typing``beet ls "the knife"`` or ``beet ls the\ knife``. Read more in
:doc:`/reference/query`.
* Queries can **match items from the library by directory**. A ``path:`` prefix
is optional; any query containing a path separator (/ on POSIX systems) is
assumed to be a path query. Running ``beet ls path/to/music`` will show all
the music in your library under the specified directory. The
:doc:`/reference/query` reference again has more details.
* **Local album art** is now automatically discovered and copied from the
imported directories when available.
* When choosing the "as-is" import album (or doing a non-autotagged import),
**every album either has an "album artist" set or is marked as a compilation
(Various Artists)**. The choice is made based on the homogeneity of the
tracks' artists. This prevents compilations that are imported as-is from being
scattered across many directories after they are imported.
* The release **label** for albums and tracks is now fetched from !MusicBrainz,
written to files, and stored in the database.
* The "list" command now accepts a ``-p`` switch that causes it to **show
paths** instead of titles. This makes the output of ``beet ls -p`` suitable
for piping into another command such as `xargs`_.
* Release year and label are now shown in the candidate selection list to help
disambiguate different releases of the same album.
* Prompts in the importer interface are now colorized for easy reading. The
default option is always highlighted.
* The importer now provides the option to specify a MusicBrainz ID manually if
the built-in searching isn't working for a particular album or track.
* ``$bitrate`` in path formats is now formatted as a human-readable kbps value
instead of as a raw integer.
* The import logger has been improved for "always-on" use. First, it is now
possible to specify a log file in .beetsconfig. Also, logs are now appended
rather than overwritten and contain timestamps.
* Album art fetching and plugin events are each now run in separate pipeline
stages during imports. This should bring additional performance when using
album art plugins like embedart or beets-lyrics.
* Accents and other Unicode decorators on characters are now treated more fairly
by the autotagger. For example, if you're missing the acute accent on the "e"
in "café", that change won't be penalized. This introduces a new dependency
on the `unidecode`_ Python module.
* When tagging a track with no title set, the track's filename is now shown
(instead of nothing at all).
* The bitrate of lossless files is now calculated from their file size (rather
than being fixed at 0 or reflecting the uncompressed audio bitrate).
* Fixed a problem where duplicate albums or items imported at the same time
would fail to be detected.
* BPD now uses a persistent "virtual filesystem" in order to fake a directory
structure. This means that your path format settings are respected in BPD's
browsing hierarchy. This may come at a performance cost, however. The virtual
filesystem used by BPD is available for reuse by plugins (e.g., the FUSE
plugin).
* Singleton imports (``beet import -s``) can now take individual files as
arguments as well as directories.
* Fix Unicode queries given on the command line.
* Fix crasher in quiet singleton imports (``import -qs``).
* Fix crash when autotagging files with no metadata.
* Fix a rare deadlock when finishing the import pipeline.
* Fix an issue that was causing mpdupdate to run twice for every album.
* Fix a bug that caused release dates/years not to be fetched.
* Fix a crasher when setting MBIDs on MP3s file metadata.
* Fix a "broken pipe" error when piping beets' standard output.
* A better error message is given when the database file is unopenable.
* Suppress errors due to timeouts and bad responses from MusicBrainz.
* Fix a crash on album queries with item-only field names.
.. _xargs: http://en.wikipedia.org/wiki/xargs
.. _unidecode: http://pypi.python.org/pypi/Unidecode/0.04.1
1.0b8 (April 28, 2011)
----------------------
This release of beets brings two significant new features. First, beets now has
first-class support for "singleton" tracks. Previously, it was only really meant
to manage whole albums, but many of us have lots of non-album tracks to keep
track of alongside our collections of albums. So now beets makes it easy to tag,
catalog, and manipulate your individual tracks. Second, beets can now
(optionally) embed album art directly into file metadata rather than only
storing it in a "file on the side." Check out the :doc:`/plugins/embedart` for
that functionality.
* Better support for **singleton (non-album) tracks**. Whereas beets previously
only really supported full albums, now it can also keep track of individual,
off-album songs. The "singleton" path format can be used to customize where
these tracks are stored. To import singleton tracks, provide the -s switch to
the import command or, while doing a normal full-album import, choose the "as
Tracks" (T) option to add singletons to your library. To list only singleton
or only album tracks, use the new ``singleton:`` query term: the query
``singleton:true`` matches only singleton tracks; ``singleton:false`` matches
only album tracks. The :doc:`/plugins/lastid` has been extended to support
matching individual items as well.
* The importer/autotagger system has been heavily refactored in this release.
If anything breaks as a result, please get in touch or just file a bug.
* Support for **album art embedded in files**. A new :doc:`/plugins/embedart`
implements this functionality. Enable the plugin to automatically embed
downloaded album art into your music files' metadata. The plugin also provides
the "embedart" and "extractart" commands for moving image files in and out of
metadata. See the wiki for more details. (Thanks, daenney!)
* The "distance" number, which quantifies how different an album's current and
proposed metadata are, is now displayed as "similarity" instead. This should
be less noisy and confusing; you'll now see 99.5% instead of 0.00489323.
* A new "timid mode" in the importer asks the user every time, even when it
makes a match with very high confidence. The ``-t`` flag on the command line
and the ``import_timid`` config option control this mode. (Thanks to mdecker
on GitHub!)
* The multithreaded importer should now abort (either by selecting aBort or by
typing ^C) much more quickly. Previously, it would try to get a lot of work
done before quitting; now it gives up as soon as it can.
* Added a new plugin event, ``album_imported``, which is called every time an
album is added to the library. (Thanks, Lugoues!)
* A new plugin method, ``register_listener``, is an imperative alternative to
the ``@listen`` decorator (Thanks again, Lugoues!)
* In path formats, ``$albumartist`` now falls back to ``$artist`` (as well as
the other way around).
* The importer now prints "(unknown album)" when no tags are present.
* When autotagging, "and" is considered equal to "&".
* Fix some crashes when deleting files that don't exist.
* Fix adding individual tracks in BPD.
* Fix crash when ``~/.beetsconfig`` does not exist.
1.0b7 (April 5, 2011)
---------------------
Beta 7's focus is on better support for "various artists" releases. These albums
can be treated differently via the new ``[paths]`` config section and the
autotagger is better at handling them. It also includes a number of
oft-requested improvements to the ``beet`` command-line tool, including several
new configuration options and the ability to clean up empty directory subtrees.
* **"Various artists" releases** are handled much more gracefully. The
autotagger now sets the ``comp`` flag on albums whenever the album is
identified as a "various artists" release by !MusicBrainz. Also, there is now
a distinction between the "album artist" and the "track artist", the latter of
which is never "Various Artists" or other such bogus stand-in. *(Thanks to
Jonathan for the bulk of the implementation work on this feature!)*
* The directory hierarchy can now be **customized based on release type**. In
particular, the ``path_format`` setting in .beetsconfig has been replaced with
a new ``[paths]`` section, which allows you to specify different path formats
for normal and "compilation" (various artists) releases as well as for each
album type (see below). The default path formats have been changed to use
``$albumartist`` instead of ``$artist``.
* A **new ``albumtype`` field** reflects the release type `as specified by
MusicBrainz`_.
* When deleting files, beets now appropriately "prunes" the directory
tree---empty directories are automatically cleaned up. *(Thanks to
wlof on GitHub for this!)*
* The tagger's output now always shows the album directory that is currently
being tagged. This should help in situations where files' current tags are
missing or useless.
* The logging option (``-l``) to the ``import`` command now logs duplicate
albums.
* A new ``import_resume`` configuration option can be used to disable the
importer's resuming feature or force it to resume without asking. This option
may be either ``yes``, ``no``, or ``ask``, with the obvious meanings. The
``-p`` and ``-P`` command-line flags override this setting and correspond to
the "yes" and "no" settings.
* Resuming is automatically disabled when the importer is in quiet (``-q``)
mode. Progress is still saved, however, and the ``-p`` flag (above) can be
used to force resuming.
* The ``BEETSCONFIG`` environment variable can now be used to specify the
location of the config file that is at ~/.beetsconfig by default.
* A new ``import_quiet_fallback`` config option specifies what should
happen in quiet mode when there is no strong recommendation. The options are
``skip`` (the default) and "asis".
* When importing with the "delete" option and importing files that are already
at their destination, files could be deleted (leaving zero copies afterward).
This is fixed.
* The ``version`` command now lists all the loaded plugins.
* A new plugin, called ``info``, just prints out audio file metadata.
* Fix a bug where some files would be erroneously interpreted as MPEG-4 audio.
* Fix permission bits applied to album art files.
* Fix malformed !MusicBrainz queries caused by null characters.
* Fix a bug with old versions of the Monkey's Audio format.
* Fix a crash on broken symbolic links.
* Retry in more cases when !MusicBrainz servers are slow/overloaded.
* The old "albumify" plugin for upgrading databases was removed.
.. _as specified by MusicBrainz: http://wiki.musicbrainz.org/ReleaseType
1.0b6 (January 20, 2011)
------------------------
This version consists primarily of bug fixes and other small improvements. It's
in preparation for a more feature-ful release in beta 7. The most important
issue involves correct ordering of autotagged albums.
* **Quiet import:** a new "-q" command line switch for the import command
suppresses all prompts for input; it pessimistically skips all albums that the
importer is not completely confident about.
* Added support for the **WavPack** and **Musepack** formats. Unfortunately, due
to a limitation in the Mutagen library (used by beets for metadata
manipulation), Musepack SV8 is not yet supported. Here's the `upstream bug`_
in question.
* BPD now uses a pure-Python socket library and no longer requires
eventlet/greenlet (the latter of which is a C extension). For the curious, the
socket library in question is called `Bluelet`_.
* Non-autotagged imports are now resumable (just like autotagged imports).
* Fix a terrible and long-standing bug where track orderings were never applied.
This manifested when the tagger appeared to be applying a reasonable ordering
to the tracks but, later, the database reflects a completely wrong association
of track names to files. The order applied was always just alphabetical by
filename, which is frequently but not always what you want.
* We now use Windows' "long filename" support. This API is fairly tricky,
though, so some instability may still be present---please file a bug if you
run into pathname weirdness on Windows. Also, filenames on Windows now never
end in spaces.
* Fix crash in lastid when the artist name is not available.
* Fixed a spurious crash when ``LANG`` or a related environment variable is set
to an invalid value (such as ``'UTF-8'`` on some installations of Mac OS X).
* Fixed an error when trying to copy a file that is already at its destination.
* When copying read-only files, the importer now tries to make the copy
writable. (Previously, this would just crash the import.)
* Fixed an ``UnboundLocalError`` when no matches are found during autotag.
* Fixed a Unicode encoding error when entering special characters into the
"manual search" prompt.
* Added `` beet version`` command that just shows the current release version.
.. _upstream bug: http://code.google.com/p/mutagen/issues/detail?id=7
.. _Bluelet: https://github.com/sampsyo/bluelet
1.0b5 (September 28, 2010)
--------------------------
This version of beets focuses on increasing the accuracy of the autotagger. The
main addition is an included plugin that uses acoustic fingerprinting to match
based on the audio content (rather than existing metadata). Additional
heuristics were also added to the metadata-based tagger as well that should make
it more reliable. This release also greatly expands the capabilities of beets'
:doc:`plugin API </plugins/index>`. A host of other little features and fixes
are also rolled into this release.
* The :doc:`/plugins/lastid` adds Last.fm **acoustic fingerprinting
support** to the autotagger. Similar to the PUIDs used by !MusicBrainz Picard,
this system allows beets to recognize files that don't have any metadata at
all. You'll need to install some dependencies for this plugin to work.
* To support the above, there's also a new system for **extending the autotagger
via plugins**. Plugins can currently add components to the track and album
distance functions as well as augment the MusicBrainz search. The new API is
documented at :doc:`/plugins/index`.
* **String comparisons** in the autotagger have been augmented to act more
intuitively. Previously, if your album had the title "Something (EP)" and it
was officially called "Something", then beets would think this was a fairly
significant change. It now checks for and appropriately reweights certain
parts of each string. As another example, the title "The Great Album" is
considered equal to "Great Album, The".
* New **event system for plugins** (thanks, Jeff!). Plugins can now get
callbacks from beets when certain events occur in the core. Again, the API is
documented in :doc:`/plugins/index`.
* The BPD plugin is now disabled by default. This greatly simplifies
installation of the beets core, which is now 100% pure Python. To use BPD,
though, you'll need to set ``plugins: bpd`` in your .beetsconfig.
* The ``import`` command can now remove original files when it copies items into
your library. (This might be useful if you're low on disk space.) Set the
``import_delete`` option in your .beetsconfig to ``yes``.
* Importing without autotagging (``beet import -A``) now prints out album names
as it imports them to indicate progress.
* The new :doc:`/plugins/mpdupdate` will automatically update your MPD server's
index whenever your beets library changes.
* Efficiency tweak should reduce the number of !MusicBrainz queries per
autotagged album.
* A new ``-v`` command line switch enables debugging output.
* Fixed bug that completely broke non-autotagged imports (``import -A``).
* Fixed bug that logged the wrong paths when using ``import -l``.
* Fixed autotagging for the creatively-named band `!!!`_.
* Fixed normalization of relative paths.
* Fixed escaping of ``/`` characters in paths on Windows.
.. _!!!: http://musicbrainz.org/artist/f26c72d3-e52c-467b-b651-679c73d8e1a7.html
1.0b4 (August 9, 2010)
----------------------
This thrilling new release of beets focuses on making the tagger more usable in
a variety of ways. First and foremost, it should now be much faster: the tagger
now uses a multithreaded algorithm by default (although, because the new tagger
is experimental, a single-threaded version is still available via a config
option). Second, the tagger output now uses a little bit of ANSI terminal
coloring to make changes stand out. This way, it should be faster to decide what
to do with a proposed match: the more red you see, the worse the match is.
Finally, the tagger can be safely interrupted (paused) and restarted later at
the same point. Just enter ``b`` for aBort at any prompt to stop the tagging
process and save its progress. (The progress-saving also works in the
unthinkable event that beets crashes while tagging.)
Among the under-the-hood changes in 1.0b4 is a major change to the way beets
handles paths (filenames). This should make the whole system more tolerant to
special characters in filenames, but it may break things (especially databases
created with older versions of beets). As always, let me know if you run into
weird problems with this release.
Finally, this release's ``setup.py`` should install a ``beet.exe`` startup stub
for Windows users. This should make running beets much easier: just type
``beet`` if you have your ``PATH`` environment variable set up correctly. The
:doc:`/guides/main` guide has some tips on installing beets on Windows.
Here's the detailed list of changes:
* **Parallel tagger.** The autotagger has been reimplemented to use multiple
threads. This means that it can concurrently read files from disk, talk to the
user, communicate with MusicBrainz, and write data back to disk. Not only does
this make the tagger much faster because independent work may be performed in
parallel, but it makes the tagging process much more pleasant for large
imports. The user can let albums queue up in the background while making a
decision rather than waiting for beets between each question it asks. The
parallel tagger is on by default but a sequential (single- threaded) version
is still available by setting the ``threaded`` config value to ``no`` (because
the parallel version is still quite experimental).
* **Colorized tagger output.** The autotagger interface now makes it a little
easier to see what's going on at a glance by highlighting changes with
terminal colors. This feature is on by default, but you can turn it off by
setting ``color`` to ``no`` in your ``.beetsconfig`` (if, for example, your
terminal doesn't understand colors and garbles the output).
* **Pause and resume imports.** The ``import`` command now keeps track of its
progress, so if you're interrupted (beets crashes, you abort the process, an
alien devours your motherboard, etc.), beets will try to resume from the point
where you left off. The next time you run ``import`` on the same directory, it
will ask if you want to resume. It accomplishes this by "fast-forwarding"
through the albums in the directory until it encounters the last one it saw.
(This means it might fail if that album can't be found.) Also, you can now
abort the tagging process by entering ``b`` (for aBort) at any of the prompts.
* Overhauled methods for handling fileystem paths to allow filenames that have
badly encoded special characters. These changes are pretty fragile, so please
report any bugs involving ``UnicodeError`` or SQLite ``ProgrammingError``
messages in this version.
* The destination paths (the library directory structure) now respect
album-level metadata. This means that if you have an album in which two tracks
have different album-level attributes (like year, for instance), they will
still wind up in the same directory together. (There's currently not a very
smart method for picking the "correct" album-level metadata, but we'll fix
that later.)
* Fixed a bug where the CLI would fail completely if the ``LANG`` environment
variable was not set.
* Fixed removal of albums (``beet remove -a``): previously, the album record
would stay around although the items were deleted.
* The setup script now makes a ``beet.exe`` startup stub on Windows; Windows
users can now just type ``beet`` at the prompt to run beets.
* Fixed an occasional bug where Mutagen would complain that a tag was already
present.
* Fixed a bug with reading invalid integers from ID3 tags.
* The tagger should now be a little more reluctant to reorder tracks that
already have indices.
1.0b3 (July 22, 2010)
---------------------
This release features two major additions to the autotagger's functionality:
album art fetching and MusicBrainz ID tags. It also contains some important
under-the-hood improvements: a new plugin architecture is introduced
and the database schema is extended with explicit support for albums.
This release has one major backwards-incompatibility. Because of the new way
beets handles albums in the library, databases created with an old version of
beets might have trouble with operations that deal with albums (like the ``-a``
switch to ``beet list`` and ``beet remove``, as well as the file browser for
BPD). To "upgrade" an old database, you can use the included ``albumify`` plugin
(see the fourth bullet point below).
* **Album art.** The tagger now, by default, downloads album art from Amazon
that is referenced in the MusicBrainz database. It places the album art
alongside the audio files in a file called (for example) ``cover.jpg``. The
``import_art`` config option controls this behavior, as do the ``-r`` and
``-R`` options to the import command. You can set the name (minus extension)
of the album art file with the ``art_filename`` config option. (See
:doc:`/reference/config` for more information about how to configure the album
art downloader.)
* **Support for MusicBrainz ID tags.** The autotagger now keeps track of the
MusicBrainz track, album, and artist IDs it matched for each file. It also
looks for album IDs in new files it's importing and uses those to look up data
in MusicBrainz. Furthermore, track IDs are used as a component of the tagger's
distance metric now. (This obviously lays the groundwork for a utility that
can update tags if the MB database changes, but that's `for the future`_.)
Tangentially, this change required the database code to support a lightweight
form of migrations so that new columns could be added to old databases--this
is a delicate feature, so it would be very wise to make a backup of your
database before upgrading to this version.
* **Plugin architecture.** Add-on modules can now add new commands to the beets
command-line interface. The ``bpd`` and ``dadd`` commands were removed from
the beets core and turned into plugins; BPD is loaded by default. To load the
non-default plugins, use the config options ``plugins`` (a space-separated
list of plugin names) and ``pluginpath`` (a colon-separated list of
directories to search beyond ``sys.path``). Plugins are just Python modules
under the ``beetsplug`` namespace package containing subclasses of
``beets.plugins.BeetsPlugin``. See `the beetsplug directory`_ for examples or
:doc:`/plugins/index` for instructions.
* As a consequence of adding album art, the database was significantly
refactored to keep track of some information at an album (rather than item)
granularity. Databases created with earlier versions of beets should work
fine, but they won't have any "albums" in them--they'll just be a bag of
items. This means that commands like ``beet ls -a`` and ``beet rm -a`` won't
match anything. To "upgrade" your database, you can use the included
``albumify`` plugin. Running ``beets albumify`` with the plugin activated (set
``plugins=albumify`` in your config file) will group all your items into
albums, making beets behave more or less as it did before.
* Fixed some bugs with encoding paths on Windows. Also, ``:`` is now replaced
with ``-`` in path names (instead of ``_``) for readability.
* ``MediaFile``s now have a ``format`` attribute, so you can use ``$format`` in
your library path format strings like ``$artist - $album ($format)`` to get
directories with names like ``Paul Simon - Graceland (FLAC)``.
.. _for the future: http://code.google.com/p/beets/issues/detail?id=69
.. _the beetsplug directory:
http://code.google.com/p/beets/source/browse/#hg/beetsplug
Beets also now has its first third-party plugin: `beetfs`_, by Martin Eve! It
exposes your music in a FUSE filesystem using a custom directory structure. Even
cooler: it lets you keep your files intact on-disk while correcting their tags
when accessed through FUSE. Check it out!
.. _beetfs: http://code.google.com/p/beetfs/
1.0b2 (July 7, 2010)
--------------------
This release focuses on high-priority fixes and conspicuously missing features.
Highlights include support for two new audio formats (Monkey's Audio and Ogg
Vorbis) and an option to log untaggable albums during import.
* **Support for Ogg Vorbis and Monkey's Audio** files and their tags. (This
support should be considered preliminary: I haven't tested it heavily because
I don't use either of these formats regularly.)
* An option to the ``beet import`` command for **logging albums that are
untaggable** (i.e., are skipped or taken "as-is"). Use ``beet import -l
LOGFILE PATHS``. The log format is very simple: it's just a status (either
"skip" or "asis") followed by the path to the album in question. The idea is
that you can tag a large collection and automatically keep track of the albums
that weren't found in MusicBrainz so you can come back and look at them later.
* Fixed a ``UnicodeEncodeError`` on terminals that don't (or don't claim to)
support UTF-8.
* Importing without autotagging (``beet import -A``) is now faster and doesn't
print out a bunch of whitespace. It also lets you specify single files on the
command line (rather than just directories).
* Fixed importer crash when attempting to read a corrupt file.
* Reorganized code for CLI in preparation for adding pluggable subcommands. Also
removed dependency on the aging ``cmdln`` module in favor of `a hand-rolled
solution`_.
.. _a hand-rolled solution: http://gist.github.com/462717
1.0b1 (June 17, 2010)
---------------------
Initial release.

40
docs/conf.py Normal file
View file

@ -0,0 +1,40 @@
AUTHOR = u'Adrian Sampson'
# -- General configuration -----------------------------------------------------
extensions = []
#templates_path = ['_templates']
exclude_patterns = ['_build']
source_suffix = '.rst'
master_doc = 'index'
project = u'beets'
copyright = u'2011, Adrian Sampson'
version = '1.0b10'
release = '1.0b10'
pygments_style = 'sphinx'
# -- Options for HTML output ---------------------------------------------------
html_theme = 'default'
#html_static_path = ['_static']
htmlhelp_basename = 'beetsdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_documents = [
('index', 'beets.tex', u'beets Documentation',
AUTHOR, 'manual'),
]
# -- Options for manual page output --------------------------------------------
man_pages = [
('reference/cli', 'beet', u'beets command-line interface',
[AUTHOR], 1),
('reference/config', 'beetsconfig', u'beets configuration file',
[AUTHOR], 5),
]

12
docs/guides/index.rst Normal file
View file

@ -0,0 +1,12 @@
Guides
======
This section contains a couple of walkthroughs that will help you get familiar
with beets. If you're new to beets, you'll want to begin with the :doc:`main`
guide.
.. toctree::
:maxdepth: 1
main
tagger

239
docs/guides/main.rst Normal file
View file

@ -0,0 +1,239 @@
Getting Started
===============
Welcome to `beets`_! This guide will help you begin using it to make your music
collection better.
.. _beets: http://beets.radbox.org/
Installing
----------
You will need Python. (Beets is written for `Python 2.7`_, but it works with
2.5 and 2.6 as well. Python 3.x is not yet supported.)
.. _Python 2.7: http://www.python.org/download/releases/2.7.1/
* **Mac OS X** v10.7 (Lion) includes Python 2.7 out of the box; Snow Leopard
ships with Python 2.6.
* On **Ubuntu**, you can get everything you need by running:
``apt-get install python-dev python-setuptools python-pip``
* For **Arch Linux**, try getting `beets from AUR`_. (There's also a `dev
package`_, which is likely broken.) If you don't want to use the AUR build,
this suffices to get the dependencies: ``pacman -S base-devel python2-pip``
* If you're on **CentOS** 5, you have Python 2.4. To get 2.6,
`try this yum repository`_.
.. _try this yum repository:
http://chrislea.com/2009/09/09/easy-python-2-6-django-on-centos-5/
.. _beets from AUR: http://aur.archlinux.org/packages.php?ID=39577
.. _dev package: http://aur.archlinux.org/packages.php?ID=48617
If you have `pip`_, just say ``pip install beets`` (you might need ``sudo`` in
front of that). Otherwise, head over to the `Downloads`_ area, download the most
recent source distribution, and run ``python setup.py install`` in the directory
therein.
.. _pip: http://pip.openplans.org/
.. _Downloads: http://code.google.com/p/beets/downloads/list
The best way to upgrade beets to a new version is by running ``pip install -U
beets``. You may want to follow `@b33ts`_ on Twitter to hear about progress on
new versions.
.. _@b33ts: http://twitter.com/b33ts
Installing on Windows
^^^^^^^^^^^^^^^^^^^^^
Installing beets on Windows can be tricky. Following these steps might help you
get it right:
1. If you don't have it, `install Python`_ (you want Python 2.7).
.. _install Python: http://python.org/download/
2. Install `Setuptools`_ from PyPI. To do this, scroll to the bottom of that
page and download the Windows installer (``.exe``, not ``.egg``) for your
Python version (for example: ``setuptools-0.6c11.win32-py2.7.exe``).
.. _Setuptools: http://pypi.python.org/pypi/setuptools
3. If you haven't done so already, set your ``PATH`` environment variable to
include Python and its scripts. To do so, you have to get the "Properties"
window for "My Computer", then choose the "Advanced" tab, then hit the
"Environment Variables" button, and then look for the ``PATH`` variable in
the table. Add the following to the end of the variable's value:
``;C:\Python27;C:\Python27\Scripts``.
4. Open a command prompt and install pip by running: ``easy_install pip``
5. Now install beets by running: ``pip install beets``
6. You're all set! Type ``beet`` at the command prompt to make sure everything's
in order.
Because I don't use Windows myself, I may have missed something. If you have
trouble or you have more detail to contribute here, please `let me know`_.
.. _let me know: mailto:adrian@radbox.org
Configuring
-----------
You'll want to set a few basic options before you start using beets. To do this,
create and edit the file ``~/.beetsconfig`` with your favorite text editor. This
file will start out empty, but here's good place to start::
[beets]
directory: ~/music
library: ~/data/musiclibrary.blb
Change that first path to a directory where you'd like to keep your music. Then,
for ``library``, choose a good place to keep a database file that keeps an index
of your music.
Here, you can also change a few more options: you can leave files in place
instead of copying everything to your library folder; you can customize the
library's directory structure and naming scheme; you can also choose not to
write updated tags to files you import. If you're curious,
see :doc:`/reference/config`.
Importing Your Library
----------------------
There are two good ways to bring your existing library into beets. You can
either: (a) quickly bring all your files with all their current metadata into
beets' database, or (b) use beets' highly-refined autotagger to find canonical
metadata for every album you import. Option (a) is really fast, but option (b)
makes sure all your songs' tags are exactly right from the get-go. The point
about speed bears repeating: using the autotagger on a large library can take a
very long time, and it's an interactive process. So set aside a good chunk of
time if you're going to go that route. (I'm working on improving the
autotagger's performance and automation.) For more information on the
interactive tagging process, see :doc:`tagger`.
If you've got time and want to tag all your music right once and for all, do
this::
$ beet import /path/to/my/music
(Note that by default, this command will *copy music into the directory you
specified above*. If you want to use your current directory structure, set the
``import_copy`` config option.) To take the fast,
un-autotagged path, just say::
$ beet import -A /my/huge/mp3/library
Note that you just need to add ``-A`` for "don't autotag".
Adding More Music
-----------------
If you've ripped or... otherwise obtained some new music, you can add it with
the ``beet import`` command, the same way you imported your library. Like so::
$ beet import ~/some_great_album
This will attempt to autotag the new album (interactively) and add it to your
library. There are, of course, more options for this command---just type ``beet
help import`` to see what's available.
By default, the ``import`` command will try to find and download album art for
every album it finds. It will store the art in a file called ``cover.jpg``
alongside the songs. If you don't like that, you can disable it with the ``-R``
switch or by setting a value in the :doc:`configuration file
</reference/config>`.
Seeing Your Music
-----------------
If you want to query your music library, the ``beet list`` (shortened to ``beet
ls``) command is for you. You give it a :doc:`query string </reference/query>`,
which is formatted something like a Google search, and it gives you a list of
songs. Thus::
$ beet ls the magnetic fields
The Magnetic Fields - Distortion - Three-Way
The Magnetic Fields - Distortion - California Girls
The Magnetic Fields - Distortion - Old Fools
$ beet ls hissing gronlandic
of Montreal - Hissing Fauna, Are You the Destroyer? - Gronlandic Edit
$ beet ls bird
The Knife - The Knife - Bird
The Mae Shi - Terrorbird - Revelation Six
$ beet ls album:bird
The Mae Shi - Terrorbird - Revelation Six
As you can see, search terms by default search all attributes of songs. (They're
also implicitly joined by ANDs: a track must match *all* criteria in order to
match the query.) To narrow a search term to a particular metadata field, just
put the field before the term, separated by a : character. So ``album:bird``
only looks for ``bird`` in the "album" field of your songs. (Need to know more?
:doc:`/reference/query/` will answer all your questions.)
The ``beet list`` command has another useful option worth mentioning, ``-a``,
which searches for albums instead of songs::
$ beet ls -a forever
Bon Iver - For Emma, Forever Ago
Freezepop - Freezepop Forever
So handy!
Beets also has a ``stats`` command, just in case you want to see how much music
you have::
$ ./beet stats
Tracks: 13019
Total time: 4.9 weeks
Total size: 71.1 GB
Artists: 548
Albums: 1094
Playing Music
-------------
Beets is primarily intended as a music organizer, not a player. It's designed to
be used in conjunction with other players (consider `Decibel`_ or `cmus`_;
there's even :ref:`a cmus plugin for beets <other-plugins>`). However, it does
include a simple music player---it doesn't have a ton of features, but it gets
the job done.
.. _Decibel: http://decibel.silent-blade.org/
.. _cmus: http://cmus.sourceforge.net/
The player, called BPD, is a clone of an excellent music player called `MPD`_.
Like MPD, it runs as a daemon (i.e., without a user interface). Another program,
called an MPD client, controls the player and provides the user with an
interface. You'll need to enable the BPD plugin before you can use it. Check out
:doc:`/plugins/bpd`.
.. _MPD: http://mpd.wikia.com/
You can, of course, use the bona fide MPD server with your beets library. MPD is
a great player and has more features than BPD. BPD just provides a convenient,
built-in player that integrates tightly with your beets database.
Keep Playing
------------
The :doc:`/reference/cli` page has more detailed description of all of beets'
functionality. (Like deleting music! That's important.) Start exploring!
Also, check out :ref:`included-plugins` as well as :ref:`other-plugins`. The
real power of beets is in its extensibility---with plugins, beets can do almost
anything for your music collection.
You can always get help using the ``beet help`` command. The plain ``beet help``
command lists all the available commands; then, for example, ``beet help
import`` gives more specific help about the ``import`` command.
Please let me know what you think of beets via `email`_ or `Twitter`_.
.. _email: mailto:adrian@radbox.org
.. _twitter: http://twitter.com/b33ts

241
docs/guides/tagger.rst Normal file
View file

@ -0,0 +1,241 @@
Using the Auto-Tagger
=====================
Beets' automatic metadata correcter is sophisticated but complicated and
cryptic. This is a guide to help you through its myriad inputs and options.
An Apology and a Brief Interlude
--------------------------------
I would like to sincerely apologize that the autotagger in beets is so fussy. It
asks you a *lot* of complicated questions, insecurely asking that you verify
nearly every assumption it makes. This means importing and correcting the tags
for a large library can be an endless, tedious process. I'm sorry for this.
Maybe it will help to think of it as a tradeoff. By carefully examining every
album you own, you get to become more familiar with your library, its extent,
its variation, and its quirks. People used to spend hours lovingly sorting and
resorting their shelves of LPs. In the iTunes age, many of us toss our music
into a heap and forget about it. This is great for some people. But there's
value in intimate, complete familiarity with your collection. So instead of a
chore, try thinking of correcting tags as quality time with your music
collection. That's what I do.
One practical piece of advice: because beets' importer runs in multiple threads,
it queues up work in the background while it's waiting for you to respond. So if
you find yourself waiting for beets for a few seconds between every question it
asks you, try walking away from the computer for a while, making some tea, and
coming back. Beets will have a chance to catch up with you and will ask you
questions much more quickly.
Back to the guide.
Overview
--------
Beets' tagger is invoked using the ``beet import`` command. Point it at a
directory and it imports the files into your library, tagging them as it goes
(unless you pass ``--noautotag``, of course). There are several assumptions
beets currently makes about the music you import. In time, we'd like to remove
all of these limitations.
* Your music should be organized by album into directories. That is, the tagger
assumes that each album is in a single directory. These directories can be
arbitrarily deep (like ``music/2010/hiphop/seattle/freshespresso/glamour``),
but any directory with music files in it is interpreted as a separate album.
This means that your flat directory of six thousand uncategorized MP3s won't
currently be autotaggable. This will change eventually.
* The music may have bad tags, but it's not completely untagged. (This is
actually not a hard-and-fast rule: using the *E* option described below, it's
entirely possible to search for a release to tag a given album.) This is
because beets by default infers tags based on existing metadata. The
:doc:`LastID plugin </plugins/lastid>` extends the autotagger to use acoustic
fingerprinting to find information for arbitrary audio. Install that plugin if
you're willing to spend a little more CPU power to get tags for unidentified
albums.
* There isn't currently a good solution for multi-disc albums. Currently, every
disc is treated as a separate release, so you'll see "69 Love Songs (disc 1)",
"69 Love Songs (disc 2)" and such. We should be more flexible about this.
* Currently MP3, AAC, FLAC, Ogg Vorbis, Monkey's Audio, WavPack, and Musepack
files are supported. (Do you use some other format?
`Let me know!`_
.. _Let me know!: mailto:adrian@radbox.org
Now that that's out of the way, let's tag some music.
Options
-------
To import music, just say ``beet import MUSICDIR``. There are, of course, a few
command-line options you should know:
* ``beet import -A``: don't try to autotag anything; just import files (this
goes much faster than with autotagging enabled)
* ``beet import -W``: when autotagging, don't write new tags to the files
themselves (just keep the new metadata in beets' database)
* ``beet import -C``: don't copy imported files to your music directory; leave
them where they are
* ``beet import -R``: don't fetch album art.
* ``beet import -l LOGFILE``: write a message to ``LOGFILE`` every time you skip
an album or choose to take its tags "as-is" (see below) or the album is
skipped as a duplicate; this lets you come back later and reexamine albums
that weren't tagged successfully
* ``beet import -q``: quiet mode. Never prompt for input and, instead,
conservatively skip any albums that need your opinion. The ``-ql`` combination
is recommended.
* ``beet import -t``: timid mode, which is sort of the opposite of "quiet." The
importer will ask your permission for everything it does, confirming even very
good matches with a prompt.
* ``beet import -p``: automatically resume an interrupted import. The importer
keeps track of imports that don't finish completely (either due to a crash or
because you stop them halfway through) and, by default, prompts you to decide
whether to resume them. The ``-p`` flag automatically says "yes" to this
question. Relatedly, ``-P`` flag automatically says "no."
* ``beet import -s``: run in *singleton* mode, tagging individual tracks instead
of whole albums at a time. See the "as Tracks" choice below. This means you
can use ``beet import -AC`` to quickly add a bunch of files to your library
without doing anything to them.
Similarity
----------
So you import an album into your beets library. It goes like this::
$ beet imp witchinghour
Tagging: Ladytron - Witching Hour
(Similarity: 98.4%)
* Last One Standing -> The Last One Standing
* Beauty -> Beauty*2
* White Light Generation -> Whitelightgenerator
* All the Way -> All the Way...
Here, beets gives you a preview of the album match it has found. It shows you
which track titles will be changed if the match is applied. In this case, beets
has found a match and thinks it's a good enough match to proceed without asking
your permission. It has reported the *similarity* for the match it's found.
Similarity is a measure of how well-matched beets thinks a tagging option is.
100% similarity means a perfect match 0% indicates a truly horrible match.
In this case, beets has proceeded automatically because it found an option with
very high similarity (98.4%). But, as you'll notice, if the similarity isn't
quite so high, beets will ask you to confirm changes. This is because beets
can't be very confident about more dissimilar matches, and you (as a human) are
better at making the call than a computer. So it occasionally asks for help.
Choices
-------
When beets needs your input about a match, it says something like this::
Tagging: Beirut - Lon Gisland
(Similarity: 94.4%)
* Scenic World (Second Version) -> Scenic World
[A]pply, More candidates, Skip, Use as-is, as Tracks, Enter search, or aBort?
When beets asks you this question, it wants you to enter one of the capital letters: A, M, S, U, T, E, or B. That is, you can choose one of the following:
* *A*: Apply the suggested changes shown and move on.
* *M*: Show more options. (See the Candidates section, below.)
* *S*: Skip this album entirely and move on to the next one.
* *U*: Import the album without changing any tags. This is a good option for
albums that aren't in the MusicBrainz database, like your friend's operatic
faux-goth solo record that's only on two CD-Rs in the universe.
* *T*: Import the directory as *singleton* tracks, not as an album. Choose this
if the tracks don't form a real release---you just have one or more loner
tracks that aren't a full album. This will temporarily flip the tagger into
*singleton* mode, which attempts to match each track individually.
* *E*: Enter an artist and album to use as a search in the database. Use this
option if beets hasn't found any good options because the album is mistagged
or untagged.
* *B*: Cancel this import task altogether. No further albums will be tagged;
beets shuts down immediately. The next time you attempt to import the same
directory, though, beets will ask you if you want to resume tagging where you
left off.
Note that the option with ``[B]rackets`` is the default---so if you want to
apply the changes, you can just hit return without entering anything.
Candidates
----------
If you choose the M option, or if beets isn't very confident about any of the
choices it found, it will present you with a list of choices (called
candidates), like so::
Finding tags for "Panther - Panther".
Candidates:
1. Panther - Yourself (66.8%)
2. Tav Falco's Panther Burns - Return of the Blue Panther (30.4%)
# selection (default 1), Skip, Use as-is, or Enter search, or aBort?
Here, you have many of the same options as before, but you can also enter a
number to choose one of the options that beets has found. Don't worry about
guessing---beets will show you the proposed changes and ask you to confirm
them, just like the earlier example. As the prompt suggests, you can just hit
return to select the first candidate.
Fingerprinting
--------------
You may have noticed by now that beets' autotagger works pretty well for most
files, but can get confused when files don't have any metadata (or have wildly
incorrect metadata). In this case, you need *acoustic fingerprinting*, a
technology that identifies songs from the audio itself. With fingerprinting,
beets can autotag files that have very bad or missing tags. The :doc:`"lastid"
plugin </plugins/lastid>`, distributed with beets, uses `Last.fm's open-source
fingerprinting implementation`_, but it's disabled by default. That's because
it's sort of tricky to install. See the :doc:`/plugins/lastid` page for a guide
to getting it set up.
.. _Last.fm's open-source fingerprinting implementation:
http://github.com/lastfm/Fingerprinter
Missing Albums?
---------------
If you're having trouble tagging a particular album with beets, you might want to check the following possibilities:
* Is the album present in `the MusicBrainz database`_? You can search on their
site to make sure it's cataloged there. If not, anyone can edit
MusicBrainz---so consider adding the data yourself.
* Beets won't show you possibilities from MusicBrainz with a mismatched number
of tracks. That is, if your album is missing tracks or has additional tracks
beyond what the MB database reflects, then you'll never see a match for that
album. (This is because beets wouldn't know how to apply metadata to your
files in this case.) `Issue #33`_ proposes adding a system that automatically
detects and reports this situation.
.. _the MusicBrainz database: http://musicbrainz.org/
.. _Issue #33: http://code.google.com/p/beets/issues/detail?id=33
If neither of these situations apply and you're still having trouble tagging
something, please `file a bug report`_.
.. _file a bug report: http://code.google.com/p/beets/issues/entry
I Hope That Makes Sense
-----------------------
I haven't made the process clear, please `drop me an email`_ and I'll try to
improve this guide.
.. _drop me an email: mailto:adrian@radbox.org

36
docs/index.rst Normal file
View file

@ -0,0 +1,36 @@
beets: the music geek's media organizer
=======================================
Welcome to the documentation for `beets`_, the media library management system
for obsessive-compulsive music geeks.
If you're new to beets, begin with the :doc:`guides/main` guide. That guide
walks you through installing beets, setting it up how you like it, and starting
to build your music library.
Then you can get a more detailed look at beets' features in the
:doc:`/reference/cli/` and :doc:`/reference/config` references. You might also
be interested in exploring the :ref:`included-plugins`.
If you still need help, your can drop by the ``#beets`` IRC channel on Freenode,
`email the author`_, or `file a bug`_ in the issue tracker. Please let me know
where you think this documentation can be improved.
.. _beets: http://beets.radbox.org/
.. _email the author: mailto:adrian@radbox.org
.. _file a bug: http://code.google.com/p/beets/issues/entry
Contents
--------
.. toctree::
:maxdepth: 2
guides/index
reference/index
plugins/index
.. toctree::
:maxdepth: 1
changelog

BIN
docs/plugins/beetsweb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

125
docs/plugins/bpd.rst Normal file
View file

@ -0,0 +1,125 @@
BPD Plugin
==========
BPD is a music player using music from a beets library. It runs as a daemon and
implements the MPD protocol, so it's compatible with all the great MPD clients
out there. I'm using `Theremin`_, `gmpc`_, `Sonata`_, and `Ario`_ successfully.
.. _Theremin: https://theremin.sigterm.eu/
.. _gmpc: http://gmpc.wikia.com/wiki/Gnome_Music_Player_Client
.. _Sonata: http://sonata.berlios.de/
.. _Ario: http://ario-player.sourceforge.net/
Dependencies
------------
Before you can use BPD, you'll need the media library called GStreamer (along
with its Python bindings) on your system.
* On Mac OS X, you should use `MacPorts`_ and run ``port install
py26-gst-python``. (Note that you'll almost certainly need the Mac OS X
Developer Tools.)
* On Linux, it's likely that you already have gst-python. (If not, your
distribution almost certainly has a package for it.)
* On Windows, you may want to try `GStreamer WinBuilds`_ (cavet emptor: I
haven't tried this).
.. _MacPorts: http://www.macports.org/
.. _GStreamer WinBuilds: http://www.gstreamer-winbuild.ylatuya.es/
Using and Configuring
---------------------
BPD is a plugin for beets. It comes with beets, but it's disabled by default. To
enable it, you'll need to edit your ``.beetsconfig`` file and add the line
``plugins: bpd``. Like so::
[beets]
plugins: bpd
Then, you can run BPD by invoking::
$ beet bpd
Fire up your favorite MPD client to start playing music. The MPD site has `a
long list of available clients`_. Here are my favorites:
.. _a long list of available clients: http://mpd.wikia.com/wiki/Clients
* Linux: `gmpc`_, `Sonata`_
* Mac: `Theremin`_
* Windows: I don't know. Get in touch if you have a recommendation.
* iPhone/iPod touch: `MPoD`_
.. _MPoD: http://www.katoemba.net/makesnosenseatall/mpod/
One nice thing about MPD's (and thus BPD's) client-server architecture is that
the client can just as easily on a different computer from the server as it can
be run locally. Control your music from your laptop (or phone!) while it plays
on your headless server box. Rad!
To configure the BPD server, add a ``[bpd]`` section to your ``.beetsconfig``
file. The configuration values, which are pretty self-explanatory, are ``host``,
``port``, and ``password``. Here's an example::
[bpd]
host: 127.0.0.1
port: 6600
password: seekrit
Implementation Notes
--------------------
In the real MPD, the user can browse a music directory as it appears on disk. In
beets, we like to abstract away from the directory structure. Therefore, BPD
creates a "virtual" directory structure (artist/album/track) to present to
clients. This is static for now and cannot be reconfigured like the real on-disk
directory structure can. (Note that an obvious solution to this is just string
matching on items' destination, but this requires examining the entire library
Python-side for every query.)
We don't currently support versioned playlists. Many clients, however, use
plchanges instead of playlistinfo to get the current playlist, so plchanges
contains a dummy implementation that just calls playlistinfo.
The ``stats`` command always send zero for ``playtime``, which is supposed to
indicate the amount of time the server has spent playing music. BPD doesn't
currently keep track of this. Also, because database updates aren't yet
supported, ``db_update`` is just the time the server was started.
Unimplemented Commands
----------------------
These are the commands from `the MPD protocol`_ that have not yet been
implemented in BPD.
.. _the MPD protocol: http://mpd.wikia.com/wiki/MusicPlayerDaemonCommands
Database:
* update
Saved playlists:
* playlistclear
* playlistdelete
* playlistmove
* playlistadd
* playlistsearch
* listplaylist
* listplaylistinfo
* playlistfind
* rm
* save
* load
* rename
Deprecated:
* playlist
* volume

46
docs/plugins/embedart.rst Normal file
View file

@ -0,0 +1,46 @@
EmbedArt Plugin
===============
Typically, beets stores album art in a "file on the side": along with each
album, there is a file (named "cover.jpg" by default) that stores the album art.
You might want to embed the album art directly into each file's metadata. While
this will take more space than the external-file approach, it is necessary for
displaying album art in some media players (iPods, for example).
This plugin was added in beets 1.0b8.
Embedding Art Automatically
---------------------------
To automatically embed discovered album art into imported files, just
:doc:`enable the plugin </plugins/index>`. Art will be embedded after each album
is added to the library.
This behavior can be disabled with the ``autoembed`` config option (see below).
Manually Embedding and Extracting Art
-------------------------------------
The ``embedart`` plugin provides a couple of commands for manually managing
embedded album art:
* ``beet embedart IMAGE QUERY``: given an image file and a query matching an
album, embed the image into the metadata of every track on the album.
* ``beet extractart [-o FILE] QUERY``: extracts the image from an item matching
the query and stores it in a file. You can specify the destination file using
the ``-o`` option, but leave off the extension: it will be chosen
automatically. The destination filename defaults to ``cover`` if it's not
specified.
* ``beet clearart QUERY``: removes all embedded images from all items matching
the query. (Use with caution!)
Configuring
-----------
The plugin has one configuration option, ``autoembed``, which lets you disable
automatic album art embedding. To do so, add this to your ``~/.beetsconfig``::
[embedart]
autoembed: no

249
docs/plugins/index.rst Normal file
View file

@ -0,0 +1,249 @@
Plugins
=======
As of the 1.0b3 release, beets started supporting plugins to modularize its
functionality and allow other developers to add new functionality. Plugins can
add new commands to the command-line interface, respond to events in beets, and
augment the autotagger.
Using Plugins
-------------
To use a plugin, you have two options:
* Make sure it's in the Python path (known as `sys.path` to developers). This
just means the plugin has to be installed on your system (e.g., with a
`setup.py` script or a command like `pip` or `easy_install`).
* Set the `pythonpath` config variable to point to the directory containing the
plugin. (See :doc:`/reference/cli`.)
Then, set the `plugins` option in your `~/.beetsconfig` file, like so::
[beets]
plugins = mygreatplugin someotherplugin
The value for `plugins` should be a space-separated list of plugin module names.
.. _included-plugins:
Plugins Included With Beets
---------------------------
There are a few plugins that are included with the beets distribution. They're
disabled by default, but you can turn them on as described above:
.. toctree::
:maxdepth: 1
lastid
bpd
mpdupdate
embedart
web
.. _other-plugins:
Other Plugins
-------------
Here are a few of the plugins written by the beets community:
* `beets-replaygain`_ can analyze and store ReplayGain normalization
information.
* `beets-lyrics`_ searches Web repositories for song lyrics and adds them to your files.
* `beetFs`_ is a FUSE filesystem for browsing the music in your beets library.
(Might be out of date.)
* `Beet-MusicBrainz-Collection`_ lets you add albums from your library to your
MusicBrainz `"music collection"`_.
* `A cmus plugin`_ integrates with the `cmus`_ console music player.
.. _beets-replaygain: https://github.com/Lugoues/beets-replaygain/
.. _beets-lyrics: https://github.com/Lugoues/beets-lyrics/
.. _beetFs: http://code.google.com/p/beetfs/
.. _Beet-MusicBrainz-Collection:
https://github.com/jeffayle/Beet-MusicBrainz-Collection/
.. _"music collection": http://musicbrainz.org/show/collection/
.. _A cmus plugin:
https://github.com/coolkehon/beets/blob/master/beetsplug/cmus.py
.. _cmus: http://cmus.sourceforge.net/
Writing Plugins
---------------
A beets plugin is just a Python module inside the ``beetsplug`` namespace
package. (Check out this `Stack Overflow question about namespace packages`_ if
you haven't heard of them.) So, to make one, create a directory called
``beetsplug`` and put two files in it: one called ``__init__.py`` and one called
``myawesomeplugin.py`` (but don't actually call it that). Your directory
structure should look like this::
beetsplug/
__init__.py
myawesomeplugin.py
.. _Stack Overflow question about namespace packages:
http://stackoverflow.com/questions/1675734/how-do-i-create-a-namespace-package-in-python/1676069#1676069
Then, you'll need to put this stuff in ``__init__.py`` to make ``beetsplug`` a
namespace package::
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
That's all for ``__init__.py``; you can can leave it alone. The meat of your
plugin goes in ``myawesomeplugin.py``. There, you'll have to import the
``beets.plugins`` module and define a subclass of the ``BeetsPlugin`` class
found therein. Here's a skeleton of a plugin file::
from beets.plugins import BeetsPlugin
class MyPlugin(BeetsPlugin):
pass
Once you have your ``BeetsPlugin`` subclass, there's a variety of things your
plugin can do. (Read on!)
To use your new plugin, make sure your ``beetsplug`` directory is in the Python
path (using ``PYTHONPATH`` or by installing in a `virtualenv`_, for example).
Then, as described above, edit your ``.beetsconfig`` to include
``plugins=myawesomeplugin`` (substituting the name of the Python module
containing your plugin).
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
Add Commands to the CLI
^^^^^^^^^^^^^^^^^^^^^^^
Plugins can add new subcommands to the ``beet`` command-line interface. Define
the plugin class' ``commands()`` method to return a list of ``Subcommand``
objects. (The ``Subcommand`` class is defined in the ``beets.ui`` module.)
Here's an example plugin that adds a simple command::
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand
my_super_command = Subcommand('super', help='do something super')
def say_hi(lib, config, opts, args):
print "Hello everybody! I'm a plugin!"
my_super_command.func = say_hi
class SuperPlug(BeetsPlugin):
def commands(self):
return [my_super_command]
To make a subcommand, invoke the constructor like so: ``Subcommand(name, parser,
help, aliases)``. The ``name`` parameter is the only required one and should
just be the name of your command. ``parser`` can be an `OptionParser instance`_,
but it defaults to an empty parser (you can extend it later). ``help`` is a
description of your command, and ``aliases`` is a list of shorthand versions of
your command name.
.. _OptionParser instance: http://docs.python.org/library/optparse.html
You'll need to add a function to your command by saying ``mycommand.func =
myfunction``. This function should take the following parameters: ``lib`` (a
beets ``Library`` object), ``config`` (a `ConfigParser object`_ containing the
configuration values), and ``opts`` and ``args`` (command-line options and
arguments as returned by `OptionParser.parse_args`_).
.. _ConfigParser object: http://docs.python.org/library/configparser.html
.. _OptionParser.parse_args:
http://docs.python.org/library/optparse.html#parsing-arguments
The function should use any of the utility functions defined in ``beets.ui``.
Try running ``pydoc beets.ui`` to see what's available.
You can add command-line options to your new command using the ``parser`` member
of the ``Subcommand`` class, which is an ``OptionParser`` instance. Just use it
like you would a normal ``OptionParser`` in an independent script.
Listen for Events
^^^^^^^^^^^^^^^^^
As of beets 1.0b5, plugins can also define event handlers. Event handlers allow
you to run code whenever something happens in beets' operation. For instance, a
plugin could write a log message every time an album is successfully autotagged
or update MPD's index whenever the database is changed.
You can "listen" for events using the ``BeetsPlugin.listen`` decorator. Here's
an example::
from beets.plugins import BeetsPlugin
class SomePlugin(BeetsPlugin):
pass
@SomePlugin.listen('pluginload')
def loaded():
print 'Plugin loaded!'
Pass the name of the event in question to the ``listen`` decorator. The events
currently available are:
* *pluginload*: called after all the plugins have been loaded after the ``beet``
command starts
* *save*: called whenever the library is changed and written to disk (the
``lib`` keyword argument is the Library object that was written)
* *import*: called after a ``beet import`` command fishes (the ``lib`` keyword
argument is a Library object; ``paths`` is a list of paths (strings) that were
imported)
* *album_imported*: called with an ``Album`` object every time the ``import``
command finishes adding an album to the library
The included ``mpdupdate`` plugin provides an example use case for event listeners.
Extend the Autotagger
^^^^^^^^^^^^^^^^^^^^^
Plugins in 1.0b5 can also enhance the functionality of the autotagger. For a
comprehensive example, try looking at the ``lastid`` plugin, which is included
with beets.
A plugin can extend three parts of the autotagger's process: the track distance
function, the album distance function, and the initial MusicBrainz search. The
distance functions determine how "good" a match is at the track and album
levels; the initial search controls which candidates are presented to the
matching algorithm. Plugins implement these extensions by implementing three
methods on the plugin class:
* ``track_distance(self, item, info)``: adds a component to the distance
function (i.e., the similarity metric) for individual tracks. ``item`` is the
track to be matched (and Item object) and ``info`` is the !MusicBrainz track
entry that is proposed as a match. Should return a ``(dist, dist_max)`` pair
of floats indicating the distance.
* ``album_distance(self, items, info)``: like the above, but compares a list of
items (representing an album) to an album-level !MusicBrainz entry. Should
only consider album-level metadata (e.g., the artist name and album title) and
should not duplicate the factors considered by ``track_distance``.
* ``candidates(self, items)``: given a list of items comprised by an album to be
matched, return a list of !MusicBrainz entries for candidate albums to be
compared and matched.
When implementing these functions, it will probably be very necessary to use the
functions from the ``beets.autotag`` and ``beets.autotag.mb`` modules, both of
which have somewhat helpful docstrings.
Read Configuration Options
^^^^^^^^^^^^^^^^^^^^^^^^^^
Plugins can configure themselves using the ``.beetsconfig`` file. Define a
``configure`` method on your plugin that takes an ``OptionParser`` object as an
argument. Then use the ``beets.ui.config_val`` convenience function to access
values from the config file. Like so::
class MyPlugin(BeetsPlugin):
def configure(self, config):
number_of_goats = beets.ui.config_val(config, 'myplug', 'goats', '42')
Try looking at the ``mpdupdate`` plugin (included with beets) for an example of
real-world use of this API.

65
docs/plugins/lastid.rst Normal file
View file

@ -0,0 +1,65 @@
LastID Plugin
=============
Acoustic fingerprinting is a technique for identifying songs from the way they
"sound" rather from their existing metadata. That means that beets' autotagger
can theoretically use fingerprinting to tag files that don't have any ID3
information at all (or have completely incorrect data). The MusicBrainz project
currently uses a fingerprinting technology called PUIDs, but beets uses a
different fingerprinting algorithm provided by `Last.fm`_.
.. _Last.fm: http://last.fm/
Turning on fingerprinting can increase the accuracy of the
autotagger---especially on files with very poor metadata---but it comes at a
cost. First, it can be trickier to set up than beets itself (you need to compile
the fingerprinting code, whereas all of the beets core is written in Python).
Also, fingerprinting takes significantly more CPU and memory than ordinary
tagging---which means that imports will go substantially slower.
If you're willing to pay the performance cost for fingerprinting, read on!
Installing Dependencies
-----------------------
To use lastid, you'll need to install the `pylastfp`_ fingerprinting library,
which has a few dependencies: `fftw`_, `libsamplerate`_, and `Gstreamer for
Python`_. How you install these will depend on your operating system. Here's a
few examples:
.. _pylastfp: http://github.com/sampsyo/pylastfp
.. _fftw: http://www.fftw.org/
.. _libsamplerate: http://www.mega-nerd.com/SRC/
.. _Gstreamer for Python:
http://gstreamer.freedesktop.org/modules/gst-python.html
* On Ubuntu, just run ``apt-get install libfftw3-dev libsamplerate0-dev
python-gst0.10-dev``.
* On Arch Linux, you want
``pacman -S fftw libsamplerate gstreamer0.10-python``.
Let me know if you have a good source for installing the packages on Windows.
To decode audio formats (MP3, FLAC, etc.), you'll need the standard set of
Gstreamer plugins. For example, on Ubuntu, install the packages
``gstreamer0.10-plugins-good``, ``gstreamer0.10-plugins-bad``, and
``gstreamer0.10-plugins-ugly``.
Then, install pylastfp itself. You can do this using `pip`_, like so::
$ pip install pylastfp
.. _pip: http://pip.openplans.org/
Using
-----
Once you have all the dependencies sorted out, you can enable fingerprinting by
editing your :doc:`/reference/config`. Put ``lastid`` on your ``plugins:``
line. Your config file should contain something like this::
[beets]
plugins: lastid
With that, beets will use fingerprinting the next time you run ``beet import``.

View file

@ -0,0 +1,24 @@
MPDUpdate Plugin
================
``mpdupdate`` is a very simple plugin for beets that lets you automatically
update `MPD`_'s index whenever you change your beets library. The plugin is
included with beets as of version 1.0b5.
.. _MPD: http://mpd.wikia.com/wiki/Music_Player_Daemon_Wiki
To use it, enable it in your ``.beetsconfig`` by putting ``mpdupdate`` on your ``plugins`` line. Your ``.beetsconfig`` should look like this::
[beets]
plugins: mpdupdate
Then, you'll probably want to configure the specifics of your MPD server. You
can do that using an ``[mpdupdate]`` section in your ``.beetsconfig``, which
looks like this::
[mpdupdate]
host = localhost
port = 6600
password = seekrit
With that all in place, you'll see beets send the "update" command to your MPD server every time you change your beets library.

71
docs/plugins/web.rst Normal file
View file

@ -0,0 +1,71 @@
Web Plugin
==========
The ``web`` plugin is a very basic alternative interface to beets that
supplements the CLI. It can't do much right now, and the interface is a little
clunky, but you can use it to query and browse your music and---in browsers that
support HTML5 Audio---you can even play music.
While it's not meant to replace the CLI, a graphical interface has a number of
advantages in certain situations. For example, when editing a tag, a natural CLI
makes you retype the whole thing---common GUI conventions can be used to just
edit the part of the tag you want to change. A graphical interface could also
drastically increase the number of people who can use beets.
Install
-------
The Web interface depends on `Flask`_. To get it, just run ``pip install
flask``.
.. _Flask: http://flask.pocoo.org/
Put ``plugins=web`` in your ``.beetsconfig`` to enable the plugin.
Run the Server
--------------
Then just type ``beet web`` to start the server and go to
http://localhost:8337/. This is what it looks like:
.. image:: beetsweb.png
You can also specify the hostname and port number used by the Web server. These
can be specified on the command line or in the ``[web]`` section of your
[Usage#Configuration .beetsconfig].
On the command line, use ``beet web [HOSTNAME] [PORT]``. In the config file, use
something like this::
[web]
host=127.0.0.1
port=8888
Usage
-----
Type queries into the little search box. Double-click a track to play it with
`HTML5 Audio`_.
.. _HTML5 Audio: http://www.w3.org/TR/html-markup/audio.html
Implementation
--------------
The Web backend is built using a simple REST+JSON API with the excellent
`Flask`_ library. The frontend is a single-page application written with
`Backbone.js`_. This allows future non-Web clients to use the same backend API.
.. _Flask: http://flask.pocoo.org/
.. _Backbone.js: http://documentcloud.github.com/backbone/
Eventually, to make the Web player really viable, we should use a Flash fallback
for unsupported formats/browsers. There are a number of options for this:
* `audio.js`_
* `html5media`_
* `MediaElement.js`_
.. _audio.js: http://kolber.github.com/audiojs/
.. _html5media: http://html5media.info/
.. _MediaElement.js: http://mediaelementjs.com/

190
docs/reference/cli.rst Normal file
View file

@ -0,0 +1,190 @@
Command-Line Interface
======================
**beet** is the command-line interface to beets.
You invoke beets by specifying a *command*, like so::
beet COMMAND [ARGS...]
The rest of this document describes the available commands. If you ever need a
quick list of what's available, just type ``beet help`` or ``beet help COMMAND``
or help with a specific command.
import
------
::
beet import [-CWAPRqst] [-l LOGPATH] DIR...
beet import [options] -L QUERY
Add music to your library, attempting to get correct tags for it from
MusicBrainz.
Point the command at a directory full of music. The directory can be a single
album or a directory whose leaf subdirectories are albums (the latter case is
true of typical Artist/Album organizations and many people's "downloads"
folders). The music will be copied to a configurable directory structure (see
below) and added to a library database (see below). The command is interactive
and will try to get you to verify MusicBrainz tags that it thinks are suspect.
(This means that importing a large amount of music is therefore very tedious
right now; this is something we need to work on. Read the
:doc:`autotagging guide </guides/tagger>` if you need help.)
* By default, the command copies files your the library directory and
updates the ID3 tags on your music. If you'd like to leave your music
files untouched, try the ``-C`` (don't copy) and ``-W`` (don't write tags)
options. You can also disable this behavior by default in the
configuration file (below).
* Also, you can disable the autotagging behavior entirely using ``-A``
(don't autotag) -- then your music will be imported with its existing
metadata.
* During a long tagging import, it can be useful to keep track of albums
that weren't tagged successfully -- either because they're not in the
MusicBrainz database or because something's wrong with the files. Use the
``-l`` option to specify a filename to log every time you skip and album
or import it "as-is" or an album gets skipped as a duplicate.
* Relatedly, the ``-q`` (quiet) option can help with large imports by
autotagging without ever bothering to ask for user input. Whenever the
normal autotagger mode would ask for confirmation, the quiet mode
pessimistically skips the album. The quiet mode also disables the tagger's
ability to resume interrupted imports.
* Speaking of resuming interrupted imports, the tagger will prompt you if it
seems like the last import of the directory was interrupted (by you or by
a crash). If you want to skip this prompt, you can say "yes" automatically
by providing ``-p`` or "no" using ``-P``. The resuming feature can be
disabled by default using a configuration option (see below).
* If you want to import only the *new* stuff from a directory, use the
``-i``
option to run an *incremental* import. With this flag, beets will keep
track of every directory it ever imports and avoid importing them again.
This is useful if you have an "incoming" directory that you periodically
add things to.
* By default, beets will proceed without asking if it finds a very close
metadata match. To disable this and have the importer as you every time,
use the ``-t`` (for *timid*) option.
* The importer automatically tries to download album art for each album it
finds. To disable or enable this, use the ``-r`` or ``-R`` options.
* The importer typically works in a whole-album-at-a-time mode. If you
instead want to import individual, non-album tracks, use the *singleton*
mode by supplying the ``-s`` option.
Reimporting
^^^^^^^^^^^
The ``import`` command can also be used to "reimport" music that you've already
added to your library. This is useful for updating tags as they are fixed in the
MusicBrainz database, for when you change your mind about some selections you
made during the initial import, or if you prefer to import everything "as-is"
and then correct tags later.
Just point the ``beet import`` command at a directory of files that are already
catalogged in your library. Beets will automatically detect this situation and
avoid duplicating any items. In this situation, the "copy files" option
(``-c``/``-C`` on the command line or ``import_copy`` in the config file) has
slightly different behavior: it causes files to be *moved*, rather than
duplicated, if they're already in your library. That is, your directory
structure will be updated to reflect the new tags if copying is enabled; you
never end up with two copies of the file. That means that the "delete files"
(``-d`` or ``import_delete``) option is ignored when re-importing as well.
The ``-L`` (``--library``) flag is also useful for retagging. Instead of listing
paths you want to import on the command line, specify a :doc:`query string
<query>` that matches items from your library. In this case, the ``-s``
(singleton) flag controls whether the query matches individual items or full
albums. If you want to retag your whole library, just supply a null query, which
matches everything: ``beet import -L``
list
----
::
beet list [-ap] QUERY
:doc:`Queries <query>` the database for music.
Want to search for "Gronlandic Edit" by of Montreal? Try ``beet list
gronlandic``. Maybe you want to see everything released in 2009 with
"vegetables" in the title? Try ``beet list year:2009 title:vegetables``. (Read
more in :doc:`query`.) You can use the ``-a`` switch to search for
albums instead of individual items. The ``-p`` option makes beets print out
filenames of matched items, which might be useful for piping into other Unix
commands (such as `xargs`_).
.. _xargs: http://en.wikipedia.org/wiki/Xargs
remove
------
::
beet remove [-ad] QUERY
Remove music from your library.
This command uses the same :doc:`query <query>` syntax as the ``list`` command.
You'll be shown a list of the files that will be removed and asked to confirm.
By default, this just removes entries from the library database; it doesn't
touch the files on disk. To actually delete the files, use ``beet remove -d``.
modify
------
::
beet modify [-MWay] QUERY FIELD=VALUE...
Change the metadata for items or albums in the database.
Supply a :doc:`query <query>` matching the things you want to change and a
series of ``field=value`` pairs. For example, ``beet modify genius of love
artist="Tom Tom Club"`` will change the artist for the track "Genius of Love."
The ``-a`` switch operates on albums instead of individual tracks. Items will
automatically be moved around when necessary if they're in your library
directory, but you can disable that with ``-M``. Tags will be written to the
files according to the settings you have for imports, but these can be
overridden with ``-w`` (write tags, the default) and ``-W`` (don't write tags).
Finally, this command politely asks for your permission before making any
changes, but you can skip that prompt with the ``-y`` switch.
move
----
::
beet move [-ca] [-d DIR] QUERY
Move or copy items in your library.
This command, by default, acts as a library consolidator: items matching the
query are renamed into your library directory structure. By specifying a
destination directory with ``-d`` manually, you can move items matching a query
anywhere in your filesystem. The ``-c`` option copies files instead of moving
them. As with other commands, the ``-a`` option matches albums instead of items.
update
------
::
beet update [-aM] QUERY
Update the library (and, optionally, move files) to reflect out-of-band metadata
changes and file deletions.
This will scan all the matched files and read their tags, populating the
database with the new values. By default, files will be renamed according to
their new metadata; disable this with ``-M``.
stats
-----
::
beet stats [QUERY]
Show some statistics on your entire library (if you don't provide a
:doc:`query <query>` or the matched items (if you do).

134
docs/reference/config.rst Normal file
View file

@ -0,0 +1,134 @@
.beetsconfig
============
The ``beet`` command reads configuration information from ``~/.beetsconfig``.
The file is in INI format, and the following options are available, all of which
must appear under the ``[beets]`` section header:
* ``library``: path to the beets library file. Defaults to
``~/.beetsmusic.blb``.
* ``directory``: the directory to which files will be copied/moved when adding
them to the library. Defaults to ``~/Music``.
* ``import_copy``: either ``yes`` or ``no``, indicating whether to copy files
into the library directory when using ``beet import``. Defaults to ``yes``.
Can be overridden with the ``-c`` and ``-C`` command-line options.
* ``import_write``: either ``yes`` or ``no``, controlling whether metadata
(e.g., ID3) tags are written to files when using ``beet import``. Defaults to
``yes``. The ``-w`` and ``-W`` command-line options override this setting.
* ``import_delete``: either ``yes`` or ``no``. When enabled in conjunction with
``import_copy``, deletes original files after they are copied into your
library. This might be useful, for example, if you're low on disk space -- but
it's risky! Defaults to ``no``.
* ``import_resume``: either ``yes``, ``no``, or ``ask``. Controls whether
interrupted imports should be resumed. "Yes" means that imports are always
resumed when possible; "no" means resuming is disabled entirely; "ask" (the
default) means that the user should be prompted when resuming is possible. The
``-p`` and ``-P`` flags correspond to the "yes" and "no" settings and override
this option.
* ``import_incremental``: either ``yes`` or ``no``, controlling whether imported
directories are recorded and whether these recorded directories are skipped.
This corresponds to the ``-i`` flag to ``beet import``.
* ``import_art``: either ``yes`` or ``no``, indicating whether the autotagger
should attempt to find and download album cover art for the files it imports.
Defaults to ``yes``. The ``-r`` and ``-R`` command-line options override this
setting.
* ``import_quiet_fallback``: either ``skip`` (default) or ``asis``, specifying
what should happen in quiet mode (see the ``-q`` flag to ``import``, above)
when there is no strong recommendation.
* ``import_timid``: either ``yes`` or ``no``, controlling whether the importer
runs in *timid* mode, in which it asks for confirmation on every autotagging
match, even the ones that seem very close. Defaults to ``no``. The ``-t``
command-line flag controls the same setting.
* ``import_log``: specifies a filename where the importer's log should be kept.
By default, no log is written. This can be overridden with the ``-l`` flag to
``import``.
* ``art_filename``: when importing album art, the name of the file (without
extension) where the cover art image should be placed. Defaults to ``cover``
(i.e., images will be named ``cover.jpg`` or ``cover.png`` and placed in the
album's directory).
* ``plugins``: a space-separated list of plugin module names to load. For
instance, beets includes the [BPD BPD] plugin for playing music.
* ``pluginpath``: a colon-separated list of directories to search for plugins.
These paths are just added to ``sys.path`` before the plugins are loaded. The
plugins still have to be contained in a ``beetsplug`` namespace package.
* ``threaded``: either ``yes`` or ``no``, indicating whether the autotagger
should use multiple threads. This makes things faster but may behave
strangely. Defaults to ``yes``.
* ``color``: either ``yes`` or ``no``; whether to use color in console output
(currently only in the ``import`` command). Turn this off if your terminal
doesn't support ANSI colors.
You can also configure the directory hierarchy beets uses to store music. That
uses the ``[paths]`` section instead of the ``[beets]`` section. Each string is
a `Python template string`_ that can refer to metadata fields (see below for
examples). The extension is added automatically to the end. At the moment, you
can specify two special paths: ``default`` (for most releases) and ``comp`` (for
"various artist" releases with no dominant artist). You can also specify a
different path format for each `MusicBrainz release type`_. The defaults look
like this::
[paths]
default: $albumartist/$album/$track $title
comp: Compilations/$album/$track title
singleton: Non-Album/$artist/$title
Note the use of ``$albumartist`` instead of ``$artist``; this ensure that albums
will be well-organized. (For more about these format strings, see
:doc:`pathformat`.)
.. _Python template string:
http://docs.python.org/library/string.html#template-strings
.. _MusicBrainz release type:
http://wiki.musicbrainz.org/ReleaseType
Here's an example file::
[beets]
library: /var/music.blb
directory: /var/mp3
path_format: $genre/$artist/$album/$track $title
import_copy: yes
import_write: yes
import_resume: ask
import_art: yes
import_quiet_fallback: skip
import_timid: no
import_log: beetslog.txt
art_filename: albumart
plugins: bpd
pluginpath: ~/beets/myplugins
threaded: yes
color: yes
[paths]
default: $genre/$albumartist/$album/$track $title
soundtrack: Soundtracks/$album/$track $title
comp: $genre/$album/$track $title
singleton: Singletons/$artist - $track
[bpd]
host: 127.0.0.1
port: 6600
password: seekrit
(That ``[bpd]`` section configures the optional :doc:`BPD </plugins/bpd>`
plugin.)
If you want to store your ``.beetsconfig`` file somewhere else for whatever
reason, you can specify its path by setting the ``BEETSCONFIG`` environment
variable.

14
docs/reference/index.rst Normal file
View file

@ -0,0 +1,14 @@
Reference
=========
This section contains reference materials for various parts of beets. To get
started with beets as a new user, though, you may want to read the
:doc:`/guides/main` guide first.
.. toctree::
:maxdepth: 2
cli
config
pathformat
query

View file

@ -0,0 +1,83 @@
Path Formats
============
The ``[paths]`` section of the config file (read more on the [Usage] page) lets
you specify the directory and file naming scheme for your music library. You
specify templates using Python template string notation---that is, prefixing
names with ``$`` characters---and beets fills in the appropriate values.
For example, consider this path format string: ``$albumartist/$album/$track
$title``
Here are some paths this format will generate:
* ``Yeah Yeah Yeahs/It's Blitz!/01 Zero.mp3``
* ``Spank Rock/YoYoYoYoYo/11 Competition.mp3``
* ``The Magnetic Fields/Realism/01 You Must Be Out of Your Mind.mp3``
Note that in path formats, you almost certainly want to use ``$albumartist`` and
not ``$artist``. The latter refers to the "track artist" when it is present,
which means that albums that have tracks from different artists on them (like
`Stop Making Sense`_, for example) will be placed into different folders!
Continuing with the Stop Making Sense example, you'll end up with most of the
tracks in a "Talking Heads" directory and one in a "Tom Tom Club" directory. You
probably don't want that! So use ``$albumartist``.
.. _Stop Making Sense:
http://musicbrainz.org/release/798dcaab-0f1a-4f02-a9cb-61d5b0ddfd36.html
As a convenience, however, beets allows ``$albumartist`` to fall back to the value for ``$artist`` and vice-versa if one tag is present but the other is not.
Upgrading from 1.0b6
--------------------
Versions of beets prior to 1.0b7 didn't use a ``[paths]`` section. Instead, they
used a single ``path_format`` setting for all music. To support old
configuration files, this setting is still respected and overrides the default
path formats. However, the setting is deprecated and, if you want to use
flexible path formats, you need to remove the ``path_format`` setting and use a
``[paths]`` section instead.
Possible Values
---------------
Here's a (comprehensive?) list of the different values available to path
formats. (I will try to keep it up to date, but I might forget. The current list
can be found definitively `in the source`_.)
.. _in the source:
http://code.google.com/p/beets/source/browse/beets/library.py#36
Ordinary metadata:
* title
* artist
* album
* genre
* composer
* grouping
* year
* month
* day
* track
* tracktotal
* disc
* disctotal
* lyrics
* comments
* bpm
* comp
Audio information:
* length
* bitrate
* format
MusicBrainz IDs:
* mb_trackid
* mb_albumid
* mb_artistid

115
docs/reference/query.rst Normal file
View file

@ -0,0 +1,115 @@
Queries
=======
Many of beets' :doc:`commands <cli>` are built around **query strings:**
searches that select tracks and albums from your library. This page explains the
query string syntax, which is meant to vaguely resemble the syntax used by Web
search engines.
Keyword
-------
This command::
$ beet list love
will show all tracks matching the query string ``love``. Any unadorned word like this matches *anywhere* in a track's metadata, so you'll see all the tracks with "love" in their title, in their album name, in the artist, and so on.
For example, this is what I might see when I run the command above::
Against Me! - Reinventing Axl Rose - I Still Love You Julie
Air - Love 2 - Do the Joy
Bag Raiders - Turbo Love - Shooting Stars
Bat for Lashes - Two Suns - Good Love
...
Combining Keywords
------------------
Multiple keywords are implicitly joined with a Boolean "and." That is, if a
query has two keywords, it only matches tracks that contain *both* keywords. For
example, this command::
$ beet ls magnetic tomorrow
matches songs from the album "The House of Tomorrow" by The Magnetic Fields in
my library. It *doesn't* match other songs by the Magnetic Fields, nor does it
match "Tomorrowland" by Walter Meego---those songs only have *one* of the two
keywords I specified.
Specific Fields
---------------
Sometimes, a broad keyword match isn't enough. Beets supports a syntax that lets
you query a specific field---only the artist, only the track title, and so on.
Just say ``field:value``, where ``field`` is the name of the thing you're trying
to match (such as ``artist``, ``album``, or ``title``) and ``value`` is the
keyword you're searching for.
For example, while this query::
$ beet list dream
matches a lot of songs in my library, this more-specific query::
$ beet list artist:dream
only matches songs by the artist The-Dream. One query I especially appreciate is
one that matches albums by year::
$ beet list -a year:2011
Recall that ``-a`` makes the ``list`` command show albums instead of individual
tracks, so this command shows me all the releases I have from this year.
Phrases
-------
As of beets 1.0b9, you can query for strings with spaces in them by quoting or escaping them using your shell's argument syntax. For example, this command::
$ beet list the rebel
shows several tracks in my library, but these (equivalent) commands::
$ beet list "the rebel"
$ beet list the\ rebel
only match the track "The Rebel" by Buck 65. Note that the quotes and
backslashes are not part of beets' syntax; I'm just using the escaping
functionality of by shell (bash or zsh, for instance) to pass ``the rebel`` as a
single argument instead of two.
Path Queries
------------
Sometimes it's useful to find all the items in your library that are
(recursively) inside a certain directory. With beets 1.0b9, use the ``path:``
field to do this::
$ beet list path:/my/music/directory
In fact, beets automatically recognizes any query term containing a path
separator (``/`` on POSIX systems) as a path query, so this command is
equivalent::
$ beet list /my/music/directory
Note that this only matches items that are *already in your library*, so a path
query won't necessarily find *all* the audio files in a directory---just the
ones you've already added to your beets library.
Future Work
-----------
Here are a few things that the query syntax should eventually support but aren't
yet implemented. Please drop me a line if you have other ideas.
* "Null" queries. It's currently impossible to query for items that have an
empty artist. Perhaps the syntax should look like ``artist:NULL`` or
``artist:EMPTY``.
* Regular expressions. Beets queries are based on simple case-insensitive
substring matching, but regexes might be useful occasionally as well. Maybe
the syntax should look something like ``re:artist:^.*$`` or, perhaps,
``artist:/^.*$/``. Having regular expressions could help with null queries
(above): ``re:artist:^$``.

View file

@ -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__)

View file

@ -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__)

View file

@ -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: