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