diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 47afe70ce..78f16d051 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -21,6 +21,7 @@ import: detail: no flat: no group_albums: no + pretend: false clutter: ["Thumbs.DB", ".DS_Store"] ignore: [".*", "*~", "System Volume Information"] diff --git a/beets/importer.py b/beets/importer.py index e8e28b3bd..0877b5508 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -277,20 +277,25 @@ class ImportSession(object): else: stages = [query_tasks(self)] - if self.config['group_albums'] and \ - not self.config['singletons']: - # Split directory tasks into one task for each album - stages += [group_albums(self)] - if self.config['autotag']: - # FIXME We should also resolve duplicates when not - # autotagging. This is currently handled in `user_query` - stages += [lookup_candidates(self), user_query(self)] + if self.config['pretend']: + # Only log the imported files and end the pipeline + stages += [log_files(self)] else: - stages += [import_asis(self)] - stages += [apply_choices(self)] - for stage_func in plugins.import_stages(): - stages.append(plugin_stage(self, stage_func)) - stages += [manipulate_files(self)] + if self.config['group_albums'] and \ + not self.config['singletons']: + # Split directory tasks into one task for each album + stages += [group_albums(self)] + if self.config['autotag']: + # FIXME We should also resolve duplicates when not + # autotagging. This is currently handled in `user_query` + stages += [lookup_candidates(self), user_query(self)] + else: + stages += [import_asis(self)] + stages += [apply_choices(self)] + + for stage_func in plugins.import_stages(): + stages.append(plugin_stage(self, stage_func)) + stages += [manipulate_files(self)] pl = pipeline.Pipeline(stages) # Run the pipeline. @@ -1290,6 +1295,17 @@ def manipulate_files(session, task): task.finalize(session) +@pipeline.stage +def log_files(session, task): + """A coroutine (pipeline stage) to log each file which will be imported + """ + if isinstance(task, SingletonImportTask): + log.info(displayable_path(task.item['path'])) + elif task.items: + for item in task.items: + log.info(displayable_path(item['path'])) + + def group_albums(session): """Group the items of a task by albumartist and album name and create a new task for each album. Yield the tasks as a multi message. diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 910fe0243..c8e217d3c 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -943,6 +943,10 @@ import_cmd.parser.add_option( '-g', '--group-albums', dest='group_albums', action='store_true', help='group tracks in a folder into separate albums' ) +import_cmd.parser.add_option( + '--pretend', dest='pretend', action='store_true', + help='only print files to import, but don\'t import' +) import_cmd.func = import_func default_commands.append(import_cmd) diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index fc734c492..a11ed394e 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -128,6 +128,10 @@ Optional command flags: ``--group-albums`` option to split the files based on their metadata before matching them as separate albums. +* If you just want to know which files would be imported, you can use the + ``--pretend`` option. If set, beets will only print a list of file + it will import when the option is removed and won't do anything else. + .. _rarfile: https://pypi.python.org/pypi/rarfile/2.2 .. only:: html diff --git a/test/test_importer.py b/test/test_importer.py index f026adf35..0ffa9aaf3 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -25,6 +25,7 @@ from mock import patch import _common from _common import unittest +from beets.util import displayable_path from helper import TestImportSession, TestHelper, has_program, capture_log from beets import importer from beets.importer import albums_in_dir @@ -1530,6 +1531,68 @@ class ReimportTest(unittest.TestCase, ImportHelper): self.assertEqual(self._item().added, 4747.0) +class ImportPretendTest(_common.TestCase, ImportHelper): + """ Test the pretend commandline option + """ + + def __init__(self, method_name='runTest'): + super(ImportPretendTest, self).__init__(method_name) + self.matcher = None + + def setUp(self): + super(ImportPretendTest, self).setUp() + self.setup_beets() + self._create_import_dir(1) + self._setup_import_session() + config['import']['pretend'] = True + self.matcher = AutotagStub().install() + self.io.install() + + def tearDown(self): + self.teardown_beets() + self.matcher.restore() + + def test_import_pretend(self): + resource_path = os.path.join(_common.RSRC, u'empty.mp3') + single_path = os.path.join(self.import_dir, u'track_2.mp3') + + shutil.copy(resource_path, single_path) + import_files = [ + os.path.join(self.import_dir, u'the_album'), + single_path + ] + self._setup_import_session(singletons=True) + self.importer.paths = import_files + + with capture_log() as logs: + self.importer.run() + + self.assertEqual(len(self.lib.items()), 0) + self.assertEqual(len(self.lib.albums()), 0) + + self.assertEqual(len(logs), 3) + self.assertEqual(logs[1], os.path.join(import_files[0], + u'track_1.mp3')) + self.assertEqual(logs[2], import_files[1]) + + def test_import_pretend_empty(self): + path = os.path.join(self.temp_dir, 'empty') + os.makedirs(path) + + self._setup_import_session(singletons=True) + self.importer.paths = [path] + + with capture_log() as logs: + self.importer.run() + + self.assertEqual(len(self.lib.items()), 0) + self.assertEqual(len(self.lib.albums()), 0) + + self.assertEqual(len(logs), 2) + self.assertEqual(logs[1], 'No files imported from {0}' + .format(displayable_path(path))) + + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)