diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 439a93f55f..942459738e 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -10,6 +10,7 @@ import: delete: no resume: ask incremental: no + from_scratch: no quiet_fallback: skip none_rec_action: ask timid: no diff --git a/beets/importer.py b/beets/importer.py index e91b356563..186d824b65 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -534,6 +534,10 @@ def imported_items(self): def apply_metadata(self): """Copy metadata from match info to the items. """ + if config['import']['from_scratch']: + for item in self.match.mapping: + item.clear() + autotag.apply_metadata(self.match.info, self.match.mapping) def duplicate_items(self, lib): diff --git a/beets/library.py b/beets/library.py index 597cfe625b..64035e6422 100644 --- a/beets/library.py +++ b/beets/library.py @@ -561,6 +561,11 @@ def update(self, values): if self.mtime == 0 and 'mtime' in values: self.mtime = values['mtime'] + def clear(self): + """Set all key/value pairs to None.""" + for key in self._media_fields: + setattr(self, key, None) + def get_album(self): """Get the Album object that this item belongs to, if any, or None if the item is a singleton or is not associated with a diff --git a/beets/ui/commands.py b/beets/ui/commands.py index c8beb11e22..3a1811cf3f 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1004,6 +1004,10 @@ def import_func(lib, opts, args): u'-I', u'--noincremental', dest='incremental', action='store_false', help=u'do not skip already-imported directories' ) +import_cmd.parser.add_option( + u'--from-scratch', dest='from_scratch', action='store_true', + help=u'erase existing metadata before applying new metadata' +) import_cmd.parser.add_option( u'--flat', dest='flat', action='store_true', help=u'import an entire tree as a single album' diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index e84b775dce..c32f0f37de 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -88,14 +88,13 @@ def im_resize(maxwidth, path_in, path_out=None): log.debug(u'artresizer: ImageMagick resizing {0} to {1}', util.displayable_path(path_in), util.displayable_path(path_out)) - # "-resize widthxheight>" shrinks images with dimension(s) larger - # than the corresponding width and/or height dimension(s). The > - # "only shrink" flag is prefixed by ^ escape char for Windows - # compatibility. + # "-resize WIDTHx>" shrinks images with the width larger + # than the given width while maintaining the aspect ratio + # with regards to the height. try: util.command_output([ 'convert', util.syspath(path_in, prefix=False), - '-resize', '{0}x^>'.format(maxwidth), + '-resize', '{0}x>'.format(maxwidth), util.syspath(path_out, prefix=False), ]) except subprocess.CalledProcessError: diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index 2584e66289..b316cfda63 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -253,20 +253,19 @@ def _order(self, objs, tiebreak=None): "completeness" (objects with more non-null fields come first) and Albums are ordered by their track count. """ - if tiebreak: - kind = 'items' if all(isinstance(o, Item) - for o in objs) else 'albums' + kind = 'items' if all(isinstance(o, Item) for o in objs) else 'albums' + + if tiebreak and kind in tiebreak.keys(): key = lambda x: tuple(getattr(x, k) for k in tiebreak[kind]) else: - kind = Item if all(isinstance(o, Item) for o in objs) else Album - if kind is Item: + if kind == 'items': def truthy(v): # Avoid a Unicode warning by avoiding comparison # between a bytes object and the empty Unicode # string ''. return v is not None and \ (v != '' if isinstance(v, six.text_type) else True) - fields = kind.all_keys() + fields = Item.all_keys() key = lambda x: sum(1 for f in fields if truthy(getattr(x, f))) else: key = lambda x: len(x.items()) diff --git a/docs/changelog.rst b/docs/changelog.rst index a5c0651313..dbcca7b4a9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -34,6 +34,10 @@ New features: * :doc:`/plugins/acousticbrainz`: The plugin can now be configured to write only a specific list of tags. Thanks to :user:`woparry`. +* A new :ref:`from_scratch` configuration option makes the importer remove old + metadata before applying new metadata. + Thanks to :user:`tummychow`. + :bug:`934` :bug:`2755` Fixes: @@ -70,6 +74,10 @@ Fixes: Python 3 on Windows with non-ASCII filenames. :bug:`2671` * :doc:`/plugins/absubmit`: Fix an occasional crash on Python 3 when the AB analysis tool produced non-ASCII metadata. :bug:`2673` +* :doc:`/plugins/duplicates`: Use default tiebreak for any kind (item/album) that + does not have a tiebreak specified in the configuration. + Thanks to :user:`cgevans`. + :bug:`2758` * :doc:`/plugins/duplicates`: Fix the `--key` command line option, which was ignored. * :doc:`/plugins/replaygain`: Fix album replaygain calculation with the @@ -82,6 +90,9 @@ Fixes: with no following numbers. Thanks to :user:`eigengrau`. :bug:`2741` * :doc:`/plugins/fromfilename`: Allow file names such as "01.mp3" to extract the track number. Also allow "_" as a separator. Refactor some regular expressions. :bug:`2738` +* Fixed an issue where images would be resized according to their longest edge, + instead of their width. Thanks to :user:`sekjun9878`. :bug:`2729` + For developers: diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index 3e668f013d..39a4b3f10a 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -111,6 +111,12 @@ Optional command flags: time, when no subdirectories will be skipped. So consider enabling the ``incremental`` configuration option. +* When beets applies metadata to your music, it will retain the value of any + existing tags that weren't overwritten, and import them into the database. You + may prefer to only use existing metadata for finding matches, and to erase it + completely when new metadata is applied. You can enforce this behavior with + the ``--from-scratch`` option, or the ``from_scratch`` configuration option. + * By default, beets will proceed without asking if it finds a very close metadata match. To disable this and have the importer ask you every time, use the ``-t`` (for *timid*) option. diff --git a/docs/reference/config.rst b/docs/reference/config.rst index ce45a94ee6..4015a4fc2f 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -475,6 +475,15 @@ 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``. +.. _from_scratch: + +from_scratch +~~~~~~~~~~~~ + +Either ``yes`` or ``no`` (default), controlling whether existing metadata is +discarded when a match is applied. This corresponds to the ``--from_scratch`` +flag to ``beet import``. + quiet_fallback ~~~~~~~~~~~~~~ diff --git a/test/test_importer.py b/test/test_importer.py index c6b021f335..e30f5609c8 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -633,6 +633,17 @@ def test_apply_candidate_adds_album_path(self): self.assert_file_in_lib( b'Applied Artist', b'Applied Album', b'Applied Title 1.mp3') + def test_apply_from_scratch_removes_other_metadata(self): + config['import']['from_scratch'] = True + + for mediafile in self.import_media: + mediafile.genre = u'Tag Genre' + mediafile.save() + + self.importer.add_choice(importer.action.APPLY) + self.importer.run() + self.assertEqual(self.lib.items().get().genre, u'') + def test_apply_with_move_deletes_import(self): config['import']['move'] = True