From 5b2653bb53f9dbddef60cdf75006d4a8f7041a50 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 22 Jul 2010 10:52:43 -0700 Subject: [PATCH] albumify plugin for upgrading old databases --- NEWS | 9 +++--- beetsplug/albumify.py | 49 +++++++++++++++++++++++++++++++ beetsplug/device.py | 1 - test/test_albumify.py | 68 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 beetsplug/albumify.py create mode 100644 test/test_albumify.py diff --git a/NEWS b/NEWS index ab4b58316..4b96f5751 100644 --- a/NEWS +++ b/NEWS @@ -29,11 +29,10 @@ 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. "One way to "upgrade" your - database so it contains albums is to remove your old database file - and use "beet import -A" to import without autotagging. This will use - the existing tags on the files but use their location in the directory - tree to cluster items into albums. + 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 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. * MediaFiles now have a "format" attribute, so you can use $format in diff --git a/beetsplug/albumify.py b/beetsplug/albumify.py new file mode 100644 index 000000000..d12060846 --- /dev/null +++ b/beetsplug/albumify.py @@ -0,0 +1,49 @@ +# This file is part of beets. +# Copyright 2010, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Provides the albumify command, which "upgrades" databases created +with beets <1.0b3. It walks through the database and groups all items +not yet associated with albums into albums based on metadata. +""" + +from collections import defaultdict + +from beets.plugins import BeetsPlugin +import beets.ui + +# Main functionality. +def albumify(lib): + # Associate every (albumless) item with a unique artist/album + # pair. + albums = defaultdict(list) + for item in lib.get(): + if item.album_id is None: + albums[(item.artist, item.album)].append(item) + + # Create an album for each group. + for (artist, album), items in albums.items(): + beets.ui.print_('%s - %s (%i tracks)' % (artist, album, len(items))) + lib.add_album(items) + +# Plugin hook. +class AlbumifyPlugin(BeetsPlugin): + def commands(self): + cmd = beets.ui.Subcommand('albumify', help='group items into albums') + def func(lib, config, opts, args): + if args: + raise beets.ui.UserError('albumify takes no arguments') + albumify(lib) + lib.save() + cmd.func = func + return [cmd] diff --git a/beetsplug/device.py b/beetsplug/device.py index 8860e0f45..f3a3dae43 100644 --- a/beetsplug/device.py +++ b/beetsplug/device.py @@ -120,4 +120,3 @@ class DevicePlugin(BeetsPlugin): pod.save() cmd.func = func return [cmd] - diff --git a/test/test_albumify.py b/test/test_albumify.py new file mode 100644 index 000000000..e2f7a6307 --- /dev/null +++ b/test/test_albumify.py @@ -0,0 +1,68 @@ +# This file is part of beets. +# Copyright 2010, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Tests for the albumify upgrader plugin.""" + +import unittest +import sys + +sys.path.append('..') +from beets import library +from beets import ui +from beetsplug.albumify import albumify + +from test_db import item + +# Silence UI output. +ui.print_ = lambda s: None + +class AlbumifyTest(unittest.TestCase): + def setUp(self): + self.lib = library.Library(':memory:') + i1, i2, i3 = item(), item(), item() + i1.album = 'album1' + i2.album = 'album1' + i3.album = 'album2' + self.lib.add(i1) + self.lib.add(i2) + self.lib.add(i3) + + def test_albumify_creates_albums(self): + albumify(self.lib) + albums = [a.album for a in self.lib.albums()] + self.assert_('album1' in albums) + self.assert_('album2' in albums) + + def test_albumify_groups_items(self): + albumify(self.lib) + q = library.MatchQuery('album', 'album1') + album1 = self.lib.albums(query=q)[0] + self.assertEqual(len( list(album1.items()) ), 2) + q = library.MatchQuery('album', 'album2') + album2 = self.lib.albums(query=q)[0] + self.assertEqual(len( list(album2.items()) ), 1) + + def test_albumify_does_not_duplicate_existing_albums(self): + i4 = item() + i4.album = 'album3' + self.lib.add_album([i4]) + self.assertEqual(len( list(self.lib.albums()) ), 1) + albumify(self.lib) + self.assertEqual(len( list(self.lib.albums()) ), 3) + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == '__main__': + unittest.main(defaultTest='suite')