Repository: beetbox/beets

Issue:
embedart: Logging is too verbose in new infrastructure
The recent plugin log refactoring has made the `embedart` plugin too noisy. Since it uses an event handler rather than an import stage, its `self._log.info` calls (e.g., "compare PHASH score…") are printed during the import process. We need a way to quiet this down, either special-purpose for this plugin on its own.

A quick grep reveals that the plugins using `album_imported` and `item_imported` are: `embedart`, `freedesktop`, `importadded`, `importfeeds`, and `permissions`. All of them could use a second look to make sure they don't have the same problem.

I don't have any bright ideas at the moment, but I'll keep thinking. @brunal, any thoughts?



Discussion:
Other relevant events include the `import_task_*` family. In each case, we should probably audit the plugins for anything that needs to share code with non-import functionality—if it happens a lot, we may want to consider providing the same level-shifting as we do for import stages.

I note another bug: multiple instantiations of the same plugin will prefix its log messages with its name multiple times. This is visible when running tests, e.g. `python test/test_embedart.py`.

Good catch. I guess we should clear out the filters on plugin construction.

I guess import_stages should somehow just be an alias for `item_imported` and `album_imported`, around which the log levels should be managed.

Moreover `embedart` logging levels seem widely incorrect, with unexpected errors being logged at level `debug` and "normal" fails (e.g. the image is not similar) being logged as `warn`.

Yeah, there's definitely some weirdness in how the levels were selected.

I'm not totally sure if I get your proposal for import_stages and the events. The embedart plugin in particular cannot use the ordinary import stage hooks because it needs to run later in the process—after files have been moved/copied. (Other stages run after applying metadata but before manipulating files.)

I just pushed a commit fixing the multiple prefixes (bug forgot referencing the issue in the commit message).

Thanks! For future reference, that was d6a4046245420dc4725a513ebb221de4880bce76.


Resolving patch:
diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py
--- a/beetsplug/embedart.py
+++ b/beetsplug/embedart.py
@@ -19,7 +19,6 @@
 import platform
 from tempfile import NamedTemporaryFile
 
-from beets import logging
 from beets.plugins import BeetsPlugin
 from beets import mediafile
 from beets import ui
@@ -27,6 +26,12 @@
 from beets.util import syspath, normpath, displayable_path
 from beets.util.artresizer import ArtResizer
 from beets import config
+from beets.util.functemplate import Template
+
+__item_template = Template(ui._pick_format(False))
+fmt_item = lambda item: item.evaluate_template(__item_template)
+__album_template = Template(ui._pick_format(True))
+fmt_album = lambda item: item.evaluate_template(__album_template)
 
 
 class EmbedCoverArtPlugin(BeetsPlugin):
@@ -43,13 +48,13 @@ def __init__(self):
 
         if self.config['maxwidth'].get(int) and not ArtResizer.shared.local:
             self.config['maxwidth'] = 0
-            self._log.warn(u"ImageMagick or PIL not found; "
-                           u"'maxwidth' option ignored")
+            self._log.warning(u"ImageMagick or PIL not found; "
+                              u"'maxwidth' option ignored")
         if self.config['compare_threshold'].get(int) and not \
                 ArtResizer.shared.can_compare:
             self.config['compare_threshold'] = 0
-            self._log.warn(u"ImageMagick 6.8.7 or higher not installed; "
-                           u"'compare_threshold' option ignored")
+            self._log.warning(u"ImageMagick 6.8.7 or higher not installed; "
+                              u"'compare_threshold' option ignored")
 
         self.register_listener('album_imported', self.album_imported)
 
@@ -118,15 +123,10 @@ def embed_item(self, item, imagepath, maxwidth=None, itempath=None,
         if compare_threshold:
             if not self.check_art_similarity(item, imagepath,
                                              compare_threshold):
-                self._log.warn(u'Image not similar; skipping.')
+                self._log.info(u'Image not similar; skipping.')
                 return
-        if ifempty:
-            art = self.get_art(item)
-            if not art:
-                pass
-            else:
-                self._log.debug(u'media file contained art already {0}',
-                                displayable_path(imagepath))
+        if ifempty and self.get_art(item):
+                self._log.info(u'media file already contained art')
                 return
         if maxwidth and not as_album:
             imagepath = self.resize_image(imagepath, maxwidth)
@@ -135,7 +135,7 @@ def embed_item(self, item, imagepath, maxwidth=None, itempath=None,
             self._log.debug(u'embedding {0}', displayable_path(imagepath))
             item['images'] = [self._mediafile_image(imagepath, maxwidth)]
         except IOError as exc:
-            self._log.error(u'could not read image file: {0}', exc)
+            self._log.warning(u'could not read image file: {0}', exc)
         else:
             # We don't want to store the image in the database.
             item.try_write(itempath)
@@ -146,20 +146,16 @@ def embed_album(self, album, maxwidth=None, quiet=False):
         """
         imagepath = album.artpath
         if not imagepath:
-            self._log.info(u'No album art present: {0} - {1}',
-                           album.albumartist, album.album)
+            self._log.info(u'No album art present for {0}', fmt_album(album))
             return
         if not os.path.isfile(syspath(imagepath)):
-            self._log.error(u'Album art not found at {0}',
-                            displayable_path(imagepath))
+            self._log.info(u'Album art not found at {0} for {1}',
+                           displayable_path(imagepath), fmt_album(album))
             return
         if maxwidth:
             imagepath = self.resize_image(imagepath, maxwidth)
 
-        self._log.log(
-            logging.DEBUG if quiet else logging.INFO,
-            u'Embedding album art into {0.albumartist} - {0.album}.', album
-        )
+        self._log.info(u'Embedding album art into {0}', fmt_album(album))
 
         for item in album.items():
             thresh = self.config['compare_threshold'].get(int)
@@ -170,7 +166,7 @@ def embed_album(self, album, maxwidth=None, quiet=False):
     def resize_image(self, imagepath, maxwidth):
         """Returns path to an image resized to maxwidth.
         """
-        self._log.info(u'Resizing album art to {0} pixels wide', maxwidth)
+        self._log.debug(u'Resizing album art to {0} pixels wide', maxwidth)
         imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath))
         return imagepath
 
@@ -218,9 +214,8 @@ def check_art_similarity(self, item, imagepath, compare_threshold):
                                     out_str)
                     return
 
-                self._log.info(u'compare PHASH score is {0}', phash_diff)
-                if phash_diff > compare_threshold:
-                    return False
+                self._log.debug(u'compare PHASH score is {0}', phash_diff)
+                return phash_diff <= compare_threshold
 
         return True
 
@@ -237,8 +232,8 @@ def get_art(self, item):
         try:
             mf = mediafile.MediaFile(syspath(item.path))
         except mediafile.UnreadableFileError as exc:
-            self._log.error(u'Could not extract art from {0}: {1}',
-                            displayable_path(item.path), exc)
+            self._log.warning(u'Could not extract art from {0}: {1}',
+                              displayable_path(item.path), exc)
             return
 
         return mf.art
@@ -246,41 +241,40 @@ def get_art(self, item):
     # 'extractart' command.
 
     def extract(self, outpath, item):
-        if not item:
-            self._log.error(u'No item matches query.')
-            return
-
         art = self.get_art(item)
 
         if not art:
-            self._log.error(u'No album art present in {0} - {1}.',
-                            item.artist, item.title)
+            self._log.info(u'No album art present in {0}, skipping.',
+                           fmt_item(item))
             return
 
         # Add an extension to the filename.
         ext = imghdr.what(None, h=art)
         if not ext:
-            self._log.error(u'Unknown image type.')
+            self._log.warning(u'Unknown image type in {0}.',
+                              displayable_path(item.path))
             return
         outpath += '.' + ext
 
-        self._log.info(u'Extracting album art from: {0.artist} - {0.title} '
-                       u'to: {1}', item, displayable_path(outpath))
+        self._log.info(u'Extracting album art from: {0} to: {1}',
+                       fmt_item(item), displayable_path(outpath))
         with open(syspath(outpath), 'wb') as f:
             f.write(art)
         return outpath
 
     # 'clearart' command.
     def clear(self, lib, query):
-        self._log.info(u'Clearing album art from items:')
-        for item in lib.items(query):
-            self._log.info(u'{0} - {1}', item.artist, item.title)
+        id3v23 = config['id3v23'].get(bool)
+
+        items = lib.items(query)
+        self._log.info(u'Clearing album art from {0} items', len(items))
+        for item in items:
+            self._log.debug(u'Clearing art for {0}', fmt_item(item))
             try:
-                mf = mediafile.MediaFile(syspath(item.path),
-                                         config['id3v23'].get(bool))
+                mf = mediafile.MediaFile(syspath(item.path), id3v23)
             except mediafile.UnreadableFileError as exc:
-                self._log.error(u'Could not clear art from {0}: {1}',
-                                displayable_path(item.path), exc)
-                continue
-            del mf.art
-            mf.save()
+                self._log.warning(u'Could not read file {0}: {1}',
+                                  displayable_path(item.path), exc)
+            else:
+                del mf.art
+                mf.save()


Test patch: