mirror of
https://github.com/beetbox/beets.git
synced 2026-01-08 17:08:12 +01:00
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:
parent
27eeb94f2c
commit
d12a4b20da
3 changed files with 99 additions and 10 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue