Import multiple albums from single directory

If a directory contains multiple albums we can select the ALBUMS action to group
the tracks by album artist and album name and import those seperately.
This commit is contained in:
Thomas Scholtes 2014-01-28 23:22:00 +01:00
parent 27eeb94f2c
commit d12a4b20da
3 changed files with 99 additions and 10 deletions

View file

@ -20,6 +20,7 @@ from __future__ import print_function
import os
import logging
import pickle
import itertools
from collections import defaultdict
from beets import autotag
@ -35,7 +36,7 @@ from beets import mediafile
action = enum(
'SKIP', 'ASIS', 'TRACKS', 'MANUAL', 'APPLY', 'MANUAL_ID',
name='action'
'ALBUMS', name='action'
)
QUEUE_SIZE = 128
@ -423,7 +424,7 @@ class ImportTask(object):
# Not part of the task structure:
assert choice not in (action.MANUAL, action.MANUAL_ID)
assert choice != action.APPLY # Only used internally.
if choice in (action.SKIP, action.ASIS, action.TRACKS):
if choice in (action.SKIP, action.ASIS, action.TRACKS, action.ALBUMS):
self.choice_flag = choice
self.match = None
else:
@ -689,6 +690,30 @@ def user_query(session):
task = pipeline.multiple(item_tasks)
continue
# As albums: group items by albums and create task for each album
if choice is action.ALBUMS:
album_tasks = []
def group(item):
return (item.albumartist or item.artist, item.album)
def emitter():
for _, items in itertools.groupby(task.items, group):
yield ImportTask(items=list(items))
yield ImportTask.progress_sentinel(task.toppath, task.paths)
def collector():
while True:
album_task = yield
album_tasks.append(album_task)
ipl = pipeline.Pipeline([
emitter(),
initial_lookup(session),
user_query(session),
collector()
])
ipl.run_sequential()
task = pipeline.multiple(album_tasks)
continue
# Check for duplicates if we have a match (or ASIS).
if task.choice_flag in (action.ASIS, action.APPLY):
ident = task.chosen_ident()

View file

@ -472,7 +472,7 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None,
.format(itemcount))
print_('For help, see: '
'http://beets.readthedocs.org/en/latest/faq.html#nomatch')
opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search',
opts = ('Use as-is', 'as Tracks', 'as albuMs', 'Skip', 'Enter search',
'enter Id', 'aBort')
sel = ui.input_options(opts)
if sel == 'u':
@ -488,6 +488,8 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None,
raise importer.ImportAbort()
elif sel == 'i':
return importer.action.MANUAL_ID
elif sel == 'm':
return importer.action.ALBUMS
else:
assert False
@ -538,8 +540,8 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None,
opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id',
'aBort')
else:
opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search',
'enter Id', 'aBort')
opts = ('Skip', 'Use as-is', 'as Tracks', 'as albuMs',
'Enter search', 'enter Id', 'aBort')
sel = ui.input_options(opts, numrange=(1, len(candidates)))
if sel == 's':
return importer.action.SKIP
@ -554,6 +556,8 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None,
raise importer.ImportAbort()
elif sel == 'i':
return importer.action.MANUAL_ID
elif sel == 'm':
return importer.action.ALBUMS
else: # Numerical selection.
match = candidates[sel - 1]
if sel != 1:
@ -577,8 +581,8 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None,
opts = ('Apply', 'More candidates', 'Skip', 'Use as-is',
'Enter search', 'enter Id', 'aBort')
else:
opts = ('Apply', 'More candidates', 'Skip', 'Use as-is',
'as Tracks', 'Enter search', 'enter Id', 'aBort')
opts = ('Apply', 'more Candidates', 'Skip', 'Use as-is',
'as Tracks', 'as albuMs', 'Enter search', 'enter Id', 'aBort')
default = config['import']['default_action'].as_choice({
'apply': 'a',
'skip': 's',
@ -591,7 +595,7 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None,
if sel == 'a':
return match
elif sel == 'm':
pass
return importer.action.ALBUMS
elif sel == 's':
return importer.action.SKIP
elif sel == 'u':
@ -651,7 +655,7 @@ class TerminalImportSession(importer.ImportSession):
# Choose which tags to use.
if choice in (importer.action.SKIP, importer.action.ASIS,
importer.action.TRACKS):
importer.action.TRACKS, importer.action.ALBUMS):
# Pass selection to main control flow.
return choice
elif choice is importer.action.MANUAL:

View file

@ -153,13 +153,20 @@ class TestImportSession(importer.ImportSession):
def choose_match(self, task):
if self.choice:
return self.choice
if hasattr(self.choice, 'pop'):
return self.choice.pop(0)
else:
return self.choice
else:
return task.candidates[0]
def choose_item(self, task):
if self.item_choice:
return self.item_choice
if hasattr(self.item_choice, 'pop'):
return self.item_choice.pop(0)
else:
return self.choice
else:
return task.candidates[0]
@ -511,6 +518,59 @@ class ImportExistingTest(_common.TestCase, ImportHelper):
self.importer.run()
self.assertNotExists(self.import_media[0].path)
class ImportFlatAlbumTest(_common.TestCase, ImportHelper):
def setUp(self):
super(ImportFlatAlbumTest, self).setUp()
self._setup_library()
self._create_import_dir(3)
autotag.mb.match_album = self._match_album
autotag.mb.match_track = self._match_track
self._setup_import_session(copy=True)
self.importer.choice = [
importer.action.ALBUMS,
importer.action.ASIS,
importer.action.ASIS]
def test_add_album_for_different_artist_and_different_album(self):
self.import_media[0].artist = "Artist B"
self.import_media[0].album = "Album B"
self.import_media[0].save()
self.importer.run()
albums = set([album.album for album in self.lib.albums()])
self.assertEqual(albums, set(['Album B', 'Tag Album']))
def test_add_album_for_different_artist_and_same_albumartist(self):
self.import_media[0].artist = "Artist B"
self.import_media[0].albumartist = "Album Artist"
self.import_media[0].save()
self.import_media[1].artist = "Artist C"
self.import_media[1].albumartist = "Album Artist"
self.import_media[1].save()
self.importer.run()
artists = set([album.albumartist for album in self.lib.albums()])
self.assertEqual(artists, set(['Album Artist', 'Tag Artist']))
def test_add_album_for_same_artist_and_different_album(self):
self.import_media[0].album = "Album B"
self.import_media[0].save()
self.importer.run()
albums = set([album.album for album in self.lib.albums()])
self.assertEqual(albums, set(['Album B', 'Tag Album']))
def test_add_album_for_same_album_and_different_artist(self):
self.import_media[0].artist = "Artist B"
self.import_media[0].save()
self.importer.run()
artists = set([album.albumartist for album in self.lib.albums()])
self.assertEqual(artists, set(['Artist B', 'Tag Artist']))
class InferAlbumDataTest(_common.TestCase):
def setUp(self):
super(InferAlbumDataTest, self).setUp()