We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Improve extractart to extract cover art of all albums for a query
The current `extractart` command of the `embedart` plugin is only able to extract the cover art of a single file. If the query the user specifies results in more than one item, only the art of the first one is extracted.

I would like to improve the command such the user queries for albums (like `ls -a` command) and one cover art is extracted per album and placed inside the album folder. The command should try all files of an album until an art is found. The command could be executed like `beet extractart -a <query>`.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `beetsplug/embedart.py`
Content:
```
1 # This file is part of beets.
2 # Copyright 2015, Adrian Sampson.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14 
15 """Allows beets to embed album art into file metadata."""
16 import os.path
17 import imghdr
18 import subprocess
19 import platform
20 from tempfile import NamedTemporaryFile
21 
22 from beets import logging
23 from beets.plugins import BeetsPlugin
24 from beets import mediafile
25 from beets import ui
26 from beets.ui import decargs
27 from beets.util import syspath, normpath, displayable_path
28 from beets.util.artresizer import ArtResizer
29 from beets import config
30 
31 
32 class EmbedCoverArtPlugin(BeetsPlugin):
33     """Allows albumart to be embedded into the actual files.
34     """
35     def __init__(self):
36         super(EmbedCoverArtPlugin, self).__init__()
37         self.config.add({
38             'maxwidth': 0,
39             'auto': True,
40             'compare_threshold': 0,
41             'ifempty': False,
42         })
43 
44         if self.config['maxwidth'].get(int) and not ArtResizer.shared.local:
45             self.config['maxwidth'] = 0
46             self._log.warn(u"ImageMagick or PIL not found; "
47                            u"'maxwidth' option ignored")
48         if self.config['compare_threshold'].get(int) and not \
49                 ArtResizer.shared.can_compare:
50             self.config['compare_threshold'] = 0
51             self._log.warn(u"ImageMagick 6.8.7 or higher not installed; "
52                            u"'compare_threshold' option ignored")
53 
54         self.register_listener('album_imported', self.album_imported)
55 
56     def commands(self):
57         # Embed command.
58         embed_cmd = ui.Subcommand(
59             'embedart', help='embed image files into file metadata'
60         )
61         embed_cmd.parser.add_option(
62             '-f', '--file', metavar='PATH', help='the image file to embed'
63         )
64         maxwidth = self.config['maxwidth'].get(int)
65         compare_threshold = self.config['compare_threshold'].get(int)
66         ifempty = self.config['ifempty'].get(bool)
67 
68         def embed_func(lib, opts, args):
69             if opts.file:
70                 imagepath = normpath(opts.file)
71                 if not os.path.isfile(syspath(imagepath)):
72                     raise ui.UserError(u'image file {0} not found'.format(
73                         displayable_path(imagepath)
74                     ))
75                 for item in lib.items(decargs(args)):
76                     self.embed_item(item, imagepath, maxwidth, None,
77                                     compare_threshold, ifempty)
78             else:
79                 for album in lib.albums(decargs(args)):
80                     self.embed_album(album, maxwidth)
81 
82         embed_cmd.func = embed_func
83 
84         # Extract command.
85         extract_cmd = ui.Subcommand('extractart',
86                                     help='extract an image from file metadata')
87         extract_cmd.parser.add_option('-o', dest='outpath',
88                                       help='image output file')
89 
90         def extract_func(lib, opts, args):
91             outpath = normpath(opts.outpath or (config['art_filename'].get()
92                                                 or 'cover'))
93             item = lib.items(decargs(args)).get()
94             self.extract(outpath, item)
95         extract_cmd.func = extract_func
96 
97         # Clear command.
98         clear_cmd = ui.Subcommand('clearart',
99                                   help='remove images from file metadata')
100 
101         def clear_func(lib, opts, args):
102             self.clear(lib, decargs(args))
103         clear_cmd.func = clear_func
104 
105         return [embed_cmd, extract_cmd, clear_cmd]
106 
107     def album_imported(self, lib, album):
108         """Automatically embed art into imported albums.
109         """
110         if album.artpath and self.config['auto']:
111             max_width = self.config['maxwidth'].get(int)
112             self.embed_album(album, max_width, True)
113 
114     def embed_item(self, item, imagepath, maxwidth=None, itempath=None,
115                    compare_threshold=0, ifempty=False, as_album=False):
116         """Embed an image into the item's media file.
117         """
118         if compare_threshold:
119             if not self.check_art_similarity(item, imagepath,
120                                              compare_threshold):
121                 self._log.warn(u'Image not similar; skipping.')
122                 return
123         if ifempty:
124             art = self.get_art(item)
125             if not art:
126                 pass
127             else:
128                 self._log.debug(u'media file contained art already {0}',
129                                 displayable_path(imagepath))
130                 return
131         if maxwidth and not as_album:
132             imagepath = self.resize_image(imagepath, maxwidth)
133 
134         try:
135             self._log.debug(u'embedding {0}', displayable_path(imagepath))
136             item['images'] = [self._mediafile_image(imagepath, maxwidth)]
137         except IOError as exc:
138             self._log.error(u'could not read image file: {0}', exc)
139         else:
140             # We don't want to store the image in the database.
141             item.try_write(itempath)
142             del item['images']
143 
144     def embed_album(self, album, maxwidth=None, quiet=False):
145         """Embed album art into all of the album's items.
146         """
147         imagepath = album.artpath
148         if not imagepath:
149             self._log.info(u'No album art present: {0} - {1}',
150                            album.albumartist, album.album)
151             return
152         if not os.path.isfile(syspath(imagepath)):
153             self._log.error(u'Album art not found at {0}',
154                             displayable_path(imagepath))
155             return
156         if maxwidth:
157             imagepath = self.resize_image(imagepath, maxwidth)
158 
159         self._log.log(
160             logging.DEBUG if quiet else logging.INFO,
161             u'Embedding album art into {0.albumartist} - {0.album}.', album
162         )
163 
164         for item in album.items():
165             thresh = self.config['compare_threshold'].get(int)
166             ifempty = self.config['ifempty'].get(bool)
167             self.embed_item(item, imagepath, maxwidth, None,
168                             thresh, ifempty, as_album=True)
169 
170     def resize_image(self, imagepath, maxwidth):
171         """Returns path to an image resized to maxwidth.
172         """
173         self._log.info(u'Resizing album art to {0} pixels wide', maxwidth)
174         imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath))
175         return imagepath
176 
177     def check_art_similarity(self, item, imagepath, compare_threshold):
178         """A boolean indicating if an image is similar to embedded item art.
179         """
180         with NamedTemporaryFile(delete=True) as f:
181             art = self.extract(f.name, item)
182 
183             if art:
184                 is_windows = platform.system() == "Windows"
185 
186                 # Converting images to grayscale tends to minimize the weight
187                 # of colors in the diff score.
188                 convert_proc = subprocess.Popen(
189                     ['convert', syspath(imagepath), syspath(art),
190                      '-colorspace', 'gray', 'MIFF:-'],
191                     stdout=subprocess.PIPE,
192                     close_fds=not is_windows,
193                 )
194                 compare_proc = subprocess.Popen(
195                     ['compare', '-metric', 'PHASH', '-', 'null:'],
196                     stdin=convert_proc.stdout,
197                     stdout=subprocess.PIPE,
198                     stderr=subprocess.PIPE,
199                     close_fds=not is_windows,
200                 )
201                 convert_proc.stdout.close()
202 
203                 stdout, stderr = compare_proc.communicate()
204                 if compare_proc.returncode:
205                     if compare_proc.returncode != 1:
206                         self._log.debug(u'IM phashes compare failed for {0}, '
207                                         u'{1}', displayable_path(imagepath),
208                                         displayable_path(art))
209                         return
210                     out_str = stderr
211                 else:
212                     out_str = stdout
213 
214                 try:
215                     phash_diff = float(out_str)
216                 except ValueError:
217                     self._log.debug(u'IM output is not a number: {0!r}',
218                                     out_str)
219                     return
220 
221                 self._log.info(u'compare PHASH score is {0}', phash_diff)
222                 if phash_diff > compare_threshold:
223                     return False
224 
225         return True
226 
227     def _mediafile_image(self, image_path, maxwidth=None):
228         """Return a `mediafile.Image` object for the path.
229         """
230 
231         with open(syspath(image_path), 'rb') as f:
232             data = f.read()
233         return mediafile.Image(data, type=mediafile.ImageType.front)
234 
235     def get_art(self, item):
236         # Extract the art.
237         try:
238             mf = mediafile.MediaFile(syspath(item.path))
239         except mediafile.UnreadableFileError as exc:
240             self._log.error(u'Could not extract art from {0}: {1}',
241                             displayable_path(item.path), exc)
242             return
243 
244         return mf.art
245 
246     # 'extractart' command.
247 
248     def extract(self, outpath, item):
249         if not item:
250             self._log.error(u'No item matches query.')
251             return
252 
253         art = self.get_art(item)
254 
255         if not art:
256             self._log.error(u'No album art present in {0} - {1}.',
257                             item.artist, item.title)
258             return
259 
260         # Add an extension to the filename.
261         ext = imghdr.what(None, h=art)
262         if not ext:
263             self._log.error(u'Unknown image type.')
264             return
265         outpath += '.' + ext
266 
267         self._log.info(u'Extracting album art from: {0.artist} - {0.title} '
268                        u'to: {1}', item, displayable_path(outpath))
269         with open(syspath(outpath), 'wb') as f:
270             f.write(art)
271         return outpath
272 
273     # 'clearart' command.
274     def clear(self, lib, query):
275         self._log.info(u'Clearing album art from items:')
276         for item in lib.items(query):
277             self._log.info(u'{0} - {1}', item.artist, item.title)
278             try:
279                 mf = mediafile.MediaFile(syspath(item.path),
280                                          config['id3v23'].get(bool))
281             except mediafile.UnreadableFileError as exc:
282                 self._log.error(u'Could not clear art from {0}: {1}',
283                                 displayable_path(item.path), exc)
284                 continue
285             del mf.art
286             mf.save()
287
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py
--- a/beetsplug/embedart.py
+++ b/beetsplug/embedart.py
@@ -89,8 +89,9 @@
 
         def extract_func(lib, opts, args):
             outpath = normpath(opts.outpath or 'cover')
-            item = lib.items(decargs(args)).get()
-            self.extract(outpath, item)
+            for item in lib.items(decargs(args)):
+                if self.extract(outpath, item):
+                    return
         extract_cmd.func = extract_func
 
         # Clear command.

{"golden_diff": "diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py\n--- a/beetsplug/embedart.py\n+++ b/beetsplug/embedart.py\n@@ -89,8 +89,9 @@\n \n         def extract_func(lib, opts, args):\n             outpath = normpath(opts.outpath or 'cover')\n-            item = lib.items(decargs(args)).get()\n-            self.extract(outpath, item)\n+            for item in lib.items(decargs(args)):\n+                if self.extract(outpath, item):\n+                    return\n         extract_cmd.func = extract_func\n \n         # Clear command.\n", "issue": "Improve extractart to extract cover art of all albums for a query\nThe current `extractart` command of the `embedart` plugin is only able to extract the cover art of a single file. If the query the user specifies results in more than one item, only the art of the first one is extracted.\n\nI would like to improve the command such the user queries for albums (like `ls -a` command) and one cover art is extracted per album and placed inside the album folder. The command should try all files of an album until an art is found. The command could be executed like `beet extractart -a <query>`.\n\n", "before_files": [{"content": "# This file is part of beets.\n# Copyright 2015, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Allows beets to embed album art into file metadata.\"\"\"\nimport os.path\nimport imghdr\nimport subprocess\nimport platform\nfrom tempfile import NamedTemporaryFile\n\nfrom beets import logging\nfrom beets.plugins import BeetsPlugin\nfrom beets import mediafile\nfrom beets import ui\nfrom beets.ui import decargs\nfrom beets.util import syspath, normpath, displayable_path\nfrom beets.util.artresizer import ArtResizer\nfrom beets import config\n\n\nclass EmbedCoverArtPlugin(BeetsPlugin):\n    \"\"\"Allows albumart to be embedded into the actual files.\n    \"\"\"\n    def __init__(self):\n        super(EmbedCoverArtPlugin, self).__init__()\n        self.config.add({\n            'maxwidth': 0,\n            'auto': True,\n            'compare_threshold': 0,\n            'ifempty': False,\n        })\n\n        if self.config['maxwidth'].get(int) and not ArtResizer.shared.local:\n            self.config['maxwidth'] = 0\n            self._log.warn(u\"ImageMagick or PIL not found; \"\n                           u\"'maxwidth' option ignored\")\n        if self.config['compare_threshold'].get(int) and not \\\n                ArtResizer.shared.can_compare:\n            self.config['compare_threshold'] = 0\n            self._log.warn(u\"ImageMagick 6.8.7 or higher not installed; \"\n                           u\"'compare_threshold' option ignored\")\n\n        self.register_listener('album_imported', self.album_imported)\n\n    def commands(self):\n        # Embed command.\n        embed_cmd = ui.Subcommand(\n            'embedart', help='embed image files into file metadata'\n        )\n        embed_cmd.parser.add_option(\n            '-f', '--file', metavar='PATH', help='the image file to embed'\n        )\n        maxwidth = self.config['maxwidth'].get(int)\n        compare_threshold = self.config['compare_threshold'].get(int)\n        ifempty = self.config['ifempty'].get(bool)\n\n        def embed_func(lib, opts, args):\n            if opts.file:\n                imagepath = normpath(opts.file)\n                if not os.path.isfile(syspath(imagepath)):\n                    raise ui.UserError(u'image file {0} not found'.format(\n                        displayable_path(imagepath)\n                    ))\n                for item in lib.items(decargs(args)):\n                    self.embed_item(item, imagepath, maxwidth, None,\n                                    compare_threshold, ifempty)\n            else:\n                for album in lib.albums(decargs(args)):\n                    self.embed_album(album, maxwidth)\n\n        embed_cmd.func = embed_func\n\n        # Extract command.\n        extract_cmd = ui.Subcommand('extractart',\n                                    help='extract an image from file metadata')\n        extract_cmd.parser.add_option('-o', dest='outpath',\n                                      help='image output file')\n\n        def extract_func(lib, opts, args):\n            outpath = normpath(opts.outpath or (config['art_filename'].get()\n                                                or 'cover'))\n            item = lib.items(decargs(args)).get()\n            self.extract(outpath, item)\n        extract_cmd.func = extract_func\n\n        # Clear command.\n        clear_cmd = ui.Subcommand('clearart',\n                                  help='remove images from file metadata')\n\n        def clear_func(lib, opts, args):\n            self.clear(lib, decargs(args))\n        clear_cmd.func = clear_func\n\n        return [embed_cmd, extract_cmd, clear_cmd]\n\n    def album_imported(self, lib, album):\n        \"\"\"Automatically embed art into imported albums.\n        \"\"\"\n        if album.artpath and self.config['auto']:\n            max_width = self.config['maxwidth'].get(int)\n            self.embed_album(album, max_width, True)\n\n    def embed_item(self, item, imagepath, maxwidth=None, itempath=None,\n                   compare_threshold=0, ifempty=False, as_album=False):\n        \"\"\"Embed an image into the item's media file.\n        \"\"\"\n        if compare_threshold:\n            if not self.check_art_similarity(item, imagepath,\n                                             compare_threshold):\n                self._log.warn(u'Image not similar; skipping.')\n                return\n        if ifempty:\n            art = self.get_art(item)\n            if not art:\n                pass\n            else:\n                self._log.debug(u'media file contained art already {0}',\n                                displayable_path(imagepath))\n                return\n        if maxwidth and not as_album:\n            imagepath = self.resize_image(imagepath, maxwidth)\n\n        try:\n            self._log.debug(u'embedding {0}', displayable_path(imagepath))\n            item['images'] = [self._mediafile_image(imagepath, maxwidth)]\n        except IOError as exc:\n            self._log.error(u'could not read image file: {0}', exc)\n        else:\n            # We don't want to store the image in the database.\n            item.try_write(itempath)\n            del item['images']\n\n    def embed_album(self, album, maxwidth=None, quiet=False):\n        \"\"\"Embed album art into all of the album's items.\n        \"\"\"\n        imagepath = album.artpath\n        if not imagepath:\n            self._log.info(u'No album art present: {0} - {1}',\n                           album.albumartist, album.album)\n            return\n        if not os.path.isfile(syspath(imagepath)):\n            self._log.error(u'Album art not found at {0}',\n                            displayable_path(imagepath))\n            return\n        if maxwidth:\n            imagepath = self.resize_image(imagepath, maxwidth)\n\n        self._log.log(\n            logging.DEBUG if quiet else logging.INFO,\n            u'Embedding album art into {0.albumartist} - {0.album}.', album\n        )\n\n        for item in album.items():\n            thresh = self.config['compare_threshold'].get(int)\n            ifempty = self.config['ifempty'].get(bool)\n            self.embed_item(item, imagepath, maxwidth, None,\n                            thresh, ifempty, as_album=True)\n\n    def resize_image(self, imagepath, maxwidth):\n        \"\"\"Returns path to an image resized to maxwidth.\n        \"\"\"\n        self._log.info(u'Resizing album art to {0} pixels wide', maxwidth)\n        imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath))\n        return imagepath\n\n    def check_art_similarity(self, item, imagepath, compare_threshold):\n        \"\"\"A boolean indicating if an image is similar to embedded item art.\n        \"\"\"\n        with NamedTemporaryFile(delete=True) as f:\n            art = self.extract(f.name, item)\n\n            if art:\n                is_windows = platform.system() == \"Windows\"\n\n                # Converting images to grayscale tends to minimize the weight\n                # of colors in the diff score.\n                convert_proc = subprocess.Popen(\n                    ['convert', syspath(imagepath), syspath(art),\n                     '-colorspace', 'gray', 'MIFF:-'],\n                    stdout=subprocess.PIPE,\n                    close_fds=not is_windows,\n                )\n                compare_proc = subprocess.Popen(\n                    ['compare', '-metric', 'PHASH', '-', 'null:'],\n                    stdin=convert_proc.stdout,\n                    stdout=subprocess.PIPE,\n                    stderr=subprocess.PIPE,\n                    close_fds=not is_windows,\n                )\n                convert_proc.stdout.close()\n\n                stdout, stderr = compare_proc.communicate()\n                if compare_proc.returncode:\n                    if compare_proc.returncode != 1:\n                        self._log.debug(u'IM phashes compare failed for {0}, '\n                                        u'{1}', displayable_path(imagepath),\n                                        displayable_path(art))\n                        return\n                    out_str = stderr\n                else:\n                    out_str = stdout\n\n                try:\n                    phash_diff = float(out_str)\n                except ValueError:\n                    self._log.debug(u'IM output is not a number: {0!r}',\n                                    out_str)\n                    return\n\n                self._log.info(u'compare PHASH score is {0}', phash_diff)\n                if phash_diff > compare_threshold:\n                    return False\n\n        return True\n\n    def _mediafile_image(self, image_path, maxwidth=None):\n        \"\"\"Return a `mediafile.Image` object for the path.\n        \"\"\"\n\n        with open(syspath(image_path), 'rb') as f:\n            data = f.read()\n        return mediafile.Image(data, type=mediafile.ImageType.front)\n\n    def get_art(self, item):\n        # Extract the art.\n        try:\n            mf = mediafile.MediaFile(syspath(item.path))\n        except mediafile.UnreadableFileError as exc:\n            self._log.error(u'Could not extract art from {0}: {1}',\n                            displayable_path(item.path), exc)\n            return\n\n        return mf.art\n\n    # 'extractart' command.\n\n    def extract(self, outpath, item):\n        if not item:\n            self._log.error(u'No item matches query.')\n            return\n\n        art = self.get_art(item)\n\n        if not art:\n            self._log.error(u'No album art present in {0} - {1}.',\n                            item.artist, item.title)\n            return\n\n        # Add an extension to the filename.\n        ext = imghdr.what(None, h=art)\n        if not ext:\n            self._log.error(u'Unknown image type.')\n            return\n        outpath += '.' + ext\n\n        self._log.info(u'Extracting album art from: {0.artist} - {0.title} '\n                       u'to: {1}', item, displayable_path(outpath))\n        with open(syspath(outpath), 'wb') as f:\n            f.write(art)\n        return outpath\n\n    # 'clearart' command.\n    def clear(self, lib, query):\n        self._log.info(u'Clearing album art from items:')\n        for item in lib.items(query):\n            self._log.info(u'{0} - {1}', item.artist, item.title)\n            try:\n                mf = mediafile.MediaFile(syspath(item.path),\n                                         config['id3v23'].get(bool))\n            except mediafile.UnreadableFileError as exc:\n                self._log.error(u'Could not clear art from {0}: {1}',\n                                displayable_path(item.path), exc)\n                continue\n            del mf.art\n            mf.save()\n", "path": "beetsplug/embedart.py"}], "after_files": [{"content": "# This file is part of beets.\n# Copyright 2015, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Allows beets to embed album art into file metadata.\"\"\"\nimport os.path\nimport imghdr\nimport subprocess\nimport platform\nfrom tempfile import NamedTemporaryFile\n\nfrom beets import logging\nfrom beets.plugins import BeetsPlugin\nfrom beets import mediafile\nfrom beets import ui\nfrom beets.ui import decargs\nfrom beets.util import syspath, normpath, displayable_path\nfrom beets.util.artresizer import ArtResizer\nfrom beets import config\n\n\nclass EmbedCoverArtPlugin(BeetsPlugin):\n    \"\"\"Allows albumart to be embedded into the actual files.\n    \"\"\"\n    def __init__(self):\n        super(EmbedCoverArtPlugin, self).__init__()\n        self.config.add({\n            'maxwidth': 0,\n            'auto': True,\n            'compare_threshold': 0,\n            'ifempty': False,\n        })\n\n        if self.config['maxwidth'].get(int) and not ArtResizer.shared.local:\n            self.config['maxwidth'] = 0\n            self._log.warn(u\"ImageMagick or PIL not found; \"\n                           u\"'maxwidth' option ignored\")\n        if self.config['compare_threshold'].get(int) and not \\\n                ArtResizer.shared.can_compare:\n            self.config['compare_threshold'] = 0\n            self._log.warn(u\"ImageMagick 6.8.7 or higher not installed; \"\n                           u\"'compare_threshold' option ignored\")\n\n        self.register_listener('album_imported', self.album_imported)\n\n    def commands(self):\n        # Embed command.\n        embed_cmd = ui.Subcommand(\n            'embedart', help='embed image files into file metadata'\n        )\n        embed_cmd.parser.add_option(\n            '-f', '--file', metavar='PATH', help='the image file to embed'\n        )\n        maxwidth = self.config['maxwidth'].get(int)\n        compare_threshold = self.config['compare_threshold'].get(int)\n        ifempty = self.config['ifempty'].get(bool)\n\n        def embed_func(lib, opts, args):\n            if opts.file:\n                imagepath = normpath(opts.file)\n                if not os.path.isfile(syspath(imagepath)):\n                    raise ui.UserError(u'image file {0} not found'.format(\n                        displayable_path(imagepath)\n                    ))\n                for item in lib.items(decargs(args)):\n                    self.embed_item(item, imagepath, maxwidth, None,\n                                    compare_threshold, ifempty)\n            else:\n                for album in lib.albums(decargs(args)):\n                    self.embed_album(album, maxwidth)\n\n        embed_cmd.func = embed_func\n\n        # Extract command.\n        extract_cmd = ui.Subcommand('extractart',\n                                    help='extract an image from file metadata')\n        extract_cmd.parser.add_option('-o', dest='outpath',\n                                      help='image output file')\n\n        def extract_func(lib, opts, args):\n            outpath = normpath(opts.outpath or 'cover')\n            for item in lib.items(decargs(args)):\n                if self.extract(outpath, item):\n                    return\n        extract_cmd.func = extract_func\n\n        # Clear command.\n        clear_cmd = ui.Subcommand('clearart',\n                                  help='remove images from file metadata')\n\n        def clear_func(lib, opts, args):\n            self.clear(lib, decargs(args))\n        clear_cmd.func = clear_func\n\n        return [embed_cmd, extract_cmd, clear_cmd]\n\n    def album_imported(self, lib, album):\n        \"\"\"Automatically embed art into imported albums.\n        \"\"\"\n        if album.artpath and self.config['auto']:\n            max_width = self.config['maxwidth'].get(int)\n            self.embed_album(album, max_width, True)\n\n    def embed_item(self, item, imagepath, maxwidth=None, itempath=None,\n                   compare_threshold=0, ifempty=False, as_album=False):\n        \"\"\"Embed an image into the item's media file.\n        \"\"\"\n        if compare_threshold:\n            if not self.check_art_similarity(item, imagepath,\n                                             compare_threshold):\n                self._log.warn(u'Image not similar; skipping.')\n                return\n        if ifempty:\n            art = self.get_art(item)\n            if not art:\n                pass\n            else:\n                self._log.debug(u'media file contained art already {0}',\n                                displayable_path(imagepath))\n                return\n        if maxwidth and not as_album:\n            imagepath = self.resize_image(imagepath, maxwidth)\n\n        try:\n            self._log.debug(u'embedding {0}', displayable_path(imagepath))\n            item['images'] = [self._mediafile_image(imagepath, maxwidth)]\n        except IOError as exc:\n            self._log.error(u'could not read image file: {0}', exc)\n        else:\n            # We don't want to store the image in the database.\n            item.try_write(itempath)\n            del item['images']\n\n    def embed_album(self, album, maxwidth=None, quiet=False):\n        \"\"\"Embed album art into all of the album's items.\n        \"\"\"\n        imagepath = album.artpath\n        if not imagepath:\n            self._log.info(u'No album art present: {0} - {1}',\n                           album.albumartist, album.album)\n            return\n        if not os.path.isfile(syspath(imagepath)):\n            self._log.error(u'Album art not found at {0}',\n                            displayable_path(imagepath))\n            return\n        if maxwidth:\n            imagepath = self.resize_image(imagepath, maxwidth)\n\n        self._log.log(\n            logging.DEBUG if quiet else logging.INFO,\n            u'Embedding album art into {0.albumartist} - {0.album}.', album\n        )\n\n        for item in album.items():\n            thresh = self.config['compare_threshold'].get(int)\n            ifempty = self.config['ifempty'].get(bool)\n            self.embed_item(item, imagepath, maxwidth, None,\n                            thresh, ifempty, as_album=True)\n\n    def resize_image(self, imagepath, maxwidth):\n        \"\"\"Returns path to an image resized to maxwidth.\n        \"\"\"\n        self._log.info(u'Resizing album art to {0} pixels wide', maxwidth)\n        imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath))\n        return imagepath\n\n    def check_art_similarity(self, item, imagepath, compare_threshold):\n        \"\"\"A boolean indicating if an image is similar to embedded item art.\n        \"\"\"\n        with NamedTemporaryFile(delete=True) as f:\n            art = self.extract(f.name, item)\n\n            if art:\n                is_windows = platform.system() == \"Windows\"\n\n                # Converting images to grayscale tends to minimize the weight\n                # of colors in the diff score.\n                convert_proc = subprocess.Popen(\n                    ['convert', syspath(imagepath), syspath(art),\n                     '-colorspace', 'gray', 'MIFF:-'],\n                    stdout=subprocess.PIPE,\n                    close_fds=not is_windows,\n                )\n                compare_proc = subprocess.Popen(\n                    ['compare', '-metric', 'PHASH', '-', 'null:'],\n                    stdin=convert_proc.stdout,\n                    stdout=subprocess.PIPE,\n                    stderr=subprocess.PIPE,\n                    close_fds=not is_windows,\n                )\n                convert_proc.stdout.close()\n\n                stdout, stderr = compare_proc.communicate()\n                if compare_proc.returncode:\n                    if compare_proc.returncode != 1:\n                        self._log.debug(u'IM phashes compare failed for {0}, '\n                                        u'{1}', displayable_path(imagepath),\n                                        displayable_path(art))\n                        return\n                    out_str = stderr\n                else:\n                    out_str = stdout\n\n                try:\n                    phash_diff = float(out_str)\n                except ValueError:\n                    self._log.debug(u'IM output is not a number: {0!r}',\n                                    out_str)\n                    return\n\n                self._log.info(u'compare PHASH score is {0}', phash_diff)\n                if phash_diff > compare_threshold:\n                    return False\n\n        return True\n\n    def _mediafile_image(self, image_path, maxwidth=None):\n        \"\"\"Return a `mediafile.Image` object for the path.\n        \"\"\"\n\n        with open(syspath(image_path), 'rb') as f:\n            data = f.read()\n        return mediafile.Image(data, type=mediafile.ImageType.front)\n\n    def get_art(self, item):\n        # Extract the art.\n        try:\n            mf = mediafile.MediaFile(syspath(item.path))\n        except mediafile.UnreadableFileError as exc:\n            self._log.error(u'Could not extract art from {0}: {1}',\n                            displayable_path(item.path), exc)\n            return\n\n        return mf.art\n\n    # 'extractart' command.\n\n    def extract(self, outpath, item):\n        if not item:\n            self._log.error(u'No item matches query.')\n            return\n\n        art = self.get_art(item)\n\n        if not art:\n            self._log.error(u'No album art present in {0} - {1}.',\n                            item.artist, item.title)\n            return\n\n        # Add an extension to the filename.\n        ext = imghdr.what(None, h=art)\n        if not ext:\n            self._log.error(u'Unknown image type.')\n            return\n        outpath += '.' + ext\n\n        self._log.info(u'Extracting album art from: {0.artist} - {0.title} '\n                       u'to: {1}', item, displayable_path(outpath))\n        with open(syspath(outpath), 'wb') as f:\n            f.write(art)\n        return outpath\n\n    # 'clearart' command.\n    def clear(self, lib, query):\n        self._log.info(u'Clearing album art from items:')\n        for item in lib.items(query):\n            self._log.info(u'{0} - {1}', item.artist, item.title)\n            try:\n                mf = mediafile.MediaFile(syspath(item.path),\n                                         config['id3v23'].get(bool))\n            except mediafile.UnreadableFileError as exc:\n                self._log.error(u'Could not clear art from {0}: {1}',\n                                displayable_path(item.path), exc)\n                continue\n            del mf.art\n            mf.save()\n", "path": "beetsplug/embedart.py"}]}