diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py index 948da6291..51d401af8 100644 --- a/beetsplug/embedart.py +++ b/beetsplug/embedart.py @@ -20,13 +20,35 @@ import os.path from beets.plugins import BeetsPlugin from beets import ui -from beets.ui import decargs +from beets.ui import print_, decargs from beets.util import syspath, normpath, displayable_path, bytestring_path from beets.util.artresizer import ArtResizer from beets import config from beets import art +def _confirm(objs, album): + """Show the list of affected objects (items or albums) and confirm + that the user wants to modify their artwork. + + `album` is a Boolean indicating whether these are albums (as opposed + to items). + """ + noun = u'album' if album else u'file' + prompt = u'Modify artwork for {} {}{} (Y/n)?'.format( + len(objs), + noun, + u's' if len(objs) > 1 else u'' + ) + + # Show all the items or albums. + for obj in objs: + print_(format(obj)) + + # Confirm with user. + return ui.input_yn(prompt) + + class EmbedCoverArtPlugin(BeetsPlugin): """Allows albumart to be embedded into the actual files. """ @@ -60,6 +82,9 @@ class EmbedCoverArtPlugin(BeetsPlugin): embed_cmd.parser.add_option( u'-f', u'--file', metavar='PATH', help=u'the image file to embed' ) + embed_cmd.parser.add_option( + u"-y", u"--yes", action="store_true", help=u"skip confirmation" + ) maxwidth = self.config['maxwidth'].get(int) compare_threshold = self.config['compare_threshold'].get(int) ifempty = self.config['ifempty'].get(bool) @@ -71,11 +96,24 @@ class EmbedCoverArtPlugin(BeetsPlugin): raise ui.UserError(u'image file {0} not found'.format( displayable_path(imagepath) )) - for item in lib.items(decargs(args)): + + items = lib.items(decargs(args)) + + # Confirm with user. + if not opts.yes and not _confirm(items, not opts.file): + return + + for item in items: art.embed_item(self._log, item, imagepath, maxwidth, None, compare_threshold, ifempty) else: - for album in lib.albums(decargs(args)): + albums = lib.albums(decargs(args)) + + # Confirm with user. + if not opts.yes and not _confirm(albums, not opts.file): + return + + for album in albums: art.embed_album(self._log, album, maxwidth, False, compare_threshold, ifempty) self.remove_artfile(album) diff --git a/docs/changelog.rst b/docs/changelog.rst index f72d22f9a..cb2934874 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -31,6 +31,9 @@ New features: * A new :ref:`hardlink` config option instructs the importer to create hard links on filesystems that support them. Thanks to :user:`jacobwgillespie`. :bug:`2445` +* :doc:`/plugins/embedart`: The explicit ``embedart`` command now asks for + confirmation before embedding art into music files. Thanks to + :user:`Stunner`. :bug:`1999` * You can now run beets by typing `python -m beets`. :bug:`2453` * A new :doc:`/plugins/kodiupdate` lets you keep your Kodi library in sync with beets. Thanks to :user:`Pauligrinder`. :bug:`2411` diff --git a/docs/plugins/embedart.rst b/docs/plugins/embedart.rst index 94e91d995..68ea0f664 100644 --- a/docs/plugins/embedart.rst +++ b/docs/plugins/embedart.rst @@ -81,7 +81,8 @@ embedded album art: * ``beet embedart [-f IMAGE] QUERY``: embed images into the every track on the albums matching the query. If the ``-f`` (``--file``) option is given, then use a specific image file from the filesystem; otherwise, each album embeds - its own currently associated album art. + its own currently associated album art. The command prompts for confirmation + before making the change unless you specify the ``-y`` (``--yes``) option. * ``beet extractart [-a] [-n FILE] QUERY``: extracts the images for all albums matching the query. The images are placed inside the album folder. You can diff --git a/test/test_embedart.py b/test/test_embedart.py index ee08ecb4e..1622fffb4 100644 --- a/test/test_embedart.py +++ b/test/test_embedart.py @@ -51,6 +51,8 @@ class EmbedartCliTest(_common.TestCase, TestHelper): abbey_differentpath = os.path.join(_common.RSRC, b'abbey-different.jpg') def setUp(self): + super(EmbedartCliTest, self).setUp() + self.io.install() self.setup_beets() # Converter is threaded self.load_plugins('embedart') @@ -64,11 +66,30 @@ class EmbedartCliTest(_common.TestCase, TestHelper): self.unload_plugins() self.teardown_beets() + def test_embed_art_from_file_with_yes_input(self): + self._setup_data() + album = self.add_album_fixture() + item = album.items()[0] + self.io.addinput('y') + self.run_command('embedart', '-f', self.small_artpath) + mediafile = MediaFile(syspath(item.path)) + self.assertEqual(mediafile.images[0].data, self.image_data) + + def test_embed_art_from_file_with_no_input(self): + self._setup_data() + album = self.add_album_fixture() + item = album.items()[0] + self.io.addinput('n') + self.run_command('embedart', '-f', self.small_artpath) + mediafile = MediaFile(syspath(item.path)) + # make sure that images array is empty (nothing embedded) + self.assertEqual(len(mediafile.images), 0) + def test_embed_art_from_file(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] - self.run_command('embedart', '-f', self.small_artpath) + self.run_command('embedart', '-y', '-f', self.small_artpath) mediafile = MediaFile(syspath(item.path)) self.assertEqual(mediafile.images[0].data, self.image_data) @@ -78,7 +99,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): item = album.items()[0] album.artpath = self.small_artpath album.store() - self.run_command('embedart') + self.run_command('embedart', '-y') mediafile = MediaFile(syspath(item.path)) self.assertEqual(mediafile.images[0].data, self.image_data) @@ -96,7 +117,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): album.store() config['embedart']['remove_art_file'] = True - self.run_command('embedart') + self.run_command('embedart', '-y') if os.path.isfile(tmp_path): os.remove(tmp_path) @@ -106,7 +127,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): self.add_album_fixture() logging.getLogger('beets.embedart').setLevel(logging.DEBUG) with self.assertRaises(ui.UserError): - self.run_command('embedart', '-f', '/doesnotexist') + self.run_command('embedart', '-y', '-f', '/doesnotexist') def test_embed_non_image_file(self): album = self.add_album_fixture() @@ -117,7 +138,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): os.close(handle) try: - self.run_command('embedart', '-f', tmp_path) + self.run_command('embedart', '-y', '-f', tmp_path) finally: os.remove(tmp_path) @@ -129,9 +150,9 @@ class EmbedartCliTest(_common.TestCase, TestHelper): self._setup_data(self.abbey_artpath) album = self.add_album_fixture() item = album.items()[0] - self.run_command('embedart', '-f', self.abbey_artpath) + self.run_command('embedart', '-y', '-f', self.abbey_artpath) config['embedart']['compare_threshold'] = 20 - self.run_command('embedart', '-f', self.abbey_differentpath) + self.run_command('embedart', '-y', '-f', self.abbey_differentpath) mediafile = MediaFile(syspath(item.path)) self.assertEqual(mediafile.images[0].data, self.image_data, @@ -143,9 +164,9 @@ class EmbedartCliTest(_common.TestCase, TestHelper): self._setup_data(self.abbey_similarpath) album = self.add_album_fixture() item = album.items()[0] - self.run_command('embedart', '-f', self.abbey_artpath) + self.run_command('embedart', '-y', '-f', self.abbey_artpath) config['embedart']['compare_threshold'] = 20 - self.run_command('embedart', '-f', self.abbey_similarpath) + self.run_command('embedart', '-y', '-f', self.abbey_similarpath) mediafile = MediaFile(syspath(item.path)) self.assertEqual(mediafile.images[0].data, self.image_data,