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 from __future__ import (division, absolute_import, print_function,
17                         unicode_literals)
18 
19 import os.path
20 import imghdr
21 import subprocess
22 import platform
23 from tempfile import NamedTemporaryFile
24 
25 from beets.plugins import BeetsPlugin
26 from beets import mediafile
27 from beets import ui
28 from beets.ui import decargs
29 from beets.util import syspath, normpath, displayable_path
30 from beets.util.artresizer import ArtResizer
31 from beets import config
32 
33 
34 class EmbedCoverArtPlugin(BeetsPlugin):
35     """Allows albumart to be embedded into the actual files.
36     """
37     def __init__(self):
38         super(EmbedCoverArtPlugin, self).__init__()
39         self.config.add({
40             'maxwidth': 0,
41             'auto': True,
42             'compare_threshold': 0,
43             'ifempty': False,
44         })
45 
46         if self.config['maxwidth'].get(int) and not ArtResizer.shared.local:
47             self.config['maxwidth'] = 0
48             self._log.warning(u"ImageMagick or PIL not found; "
49                               u"'maxwidth' option ignored")
50         if self.config['compare_threshold'].get(int) and not \
51                 ArtResizer.shared.can_compare:
52             self.config['compare_threshold'] = 0
53             self._log.warning(u"ImageMagick 6.8.7 or higher not installed; "
54                               u"'compare_threshold' option ignored")
55 
56         self.register_listener('album_imported', self.album_imported)
57 
58     def commands(self):
59         # Embed command.
60         embed_cmd = ui.Subcommand(
61             'embedart', help='embed image files into file metadata'
62         )
63         embed_cmd.parser.add_option(
64             '-f', '--file', metavar='PATH', help='the image file to embed'
65         )
66         maxwidth = self.config['maxwidth'].get(int)
67         compare_threshold = self.config['compare_threshold'].get(int)
68         ifempty = self.config['ifempty'].get(bool)
69 
70         def embed_func(lib, opts, args):
71             if opts.file:
72                 imagepath = normpath(opts.file)
73                 if not os.path.isfile(syspath(imagepath)):
74                     raise ui.UserError(u'image file {0} not found'.format(
75                         displayable_path(imagepath)
76                     ))
77                 for item in lib.items(decargs(args)):
78                     self.embed_item(item, imagepath, maxwidth, None,
79                                     compare_threshold, ifempty)
80             else:
81                 for album in lib.albums(decargs(args)):
82                     self.embed_album(album, maxwidth)
83 
84         embed_cmd.func = embed_func
85 
86         # Extract command.
87         extract_cmd = ui.Subcommand('extractart',
88                                     help='extract an image from file metadata')
89         extract_cmd.parser.add_option('-o', dest='outpath',
90                                       help='image output file')
91 
92         def extract_func(lib, opts, args):
93             outpath = normpath(opts.outpath or config['art_filename'].get())
94             for item in lib.items(decargs(args)):
95                 if self.extract(outpath, item):
96                     return
97         extract_cmd.func = extract_func
98 
99         # Clear command.
100         clear_cmd = ui.Subcommand('clearart',
101                                   help='remove images from file metadata')
102 
103         def clear_func(lib, opts, args):
104             self.clear(lib, decargs(args))
105         clear_cmd.func = clear_func
106 
107         return [embed_cmd, extract_cmd, clear_cmd]
108 
109     def album_imported(self, lib, album):
110         """Automatically embed art into imported albums.
111         """
112         if album.artpath and self.config['auto']:
113             max_width = self.config['maxwidth'].get(int)
114             self.embed_album(album, max_width, True)
115 
116     def embed_item(self, item, imagepath, maxwidth=None, itempath=None,
117                    compare_threshold=0, ifempty=False, as_album=False):
118         """Embed an image into the item's media file.
119         """
120         if compare_threshold:
121             if not self.check_art_similarity(item, imagepath,
122                                              compare_threshold):
123                 self._log.info(u'Image not similar; skipping.')
124                 return
125         if ifempty and self.get_art(item):
126                 self._log.info(u'media file already contained art')
127                 return
128         if maxwidth and not as_album:
129             imagepath = self.resize_image(imagepath, maxwidth)
130 
131         try:
132             self._log.debug(u'embedding {0}', displayable_path(imagepath))
133             item['images'] = [self._mediafile_image(imagepath, maxwidth)]
134         except IOError as exc:
135             self._log.warning(u'could not read image file: {0}', exc)
136         else:
137             # We don't want to store the image in the database.
138             item.try_write(itempath)
139             del item['images']
140 
141     def embed_album(self, album, maxwidth=None, quiet=False):
142         """Embed album art into all of the album's items.
143         """
144         imagepath = album.artpath
145         if not imagepath:
146             self._log.info(u'No album art present for {0}', album)
147             return
148         if not os.path.isfile(syspath(imagepath)):
149             self._log.info(u'Album art not found at {0} for {1}',
150                            displayable_path(imagepath), album)
151             return
152         if maxwidth:
153             imagepath = self.resize_image(imagepath, maxwidth)
154 
155         self._log.info(u'Embedding album art into {0}', album)
156 
157         for item in album.items():
158             thresh = self.config['compare_threshold'].get(int)
159             ifempty = self.config['ifempty'].get(bool)
160             self.embed_item(item, imagepath, maxwidth, None,
161                             thresh, ifempty, as_album=True)
162 
163     def resize_image(self, imagepath, maxwidth):
164         """Returns path to an image resized to maxwidth.
165         """
166         self._log.debug(u'Resizing album art to {0} pixels wide', maxwidth)
167         imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath))
168         return imagepath
169 
170     def check_art_similarity(self, item, imagepath, compare_threshold):
171         """A boolean indicating if an image is similar to embedded item art.
172         """
173         with NamedTemporaryFile(delete=True) as f:
174             art = self.extract(f.name, item)
175 
176             if art:
177                 is_windows = platform.system() == "Windows"
178 
179                 # Converting images to grayscale tends to minimize the weight
180                 # of colors in the diff score.
181                 convert_proc = subprocess.Popen(
182                     ['convert', syspath(imagepath), syspath(art),
183                      '-colorspace', 'gray', 'MIFF:-'],
184                     stdout=subprocess.PIPE,
185                     close_fds=not is_windows,
186                 )
187                 compare_proc = subprocess.Popen(
188                     ['compare', '-metric', 'PHASH', '-', 'null:'],
189                     stdin=convert_proc.stdout,
190                     stdout=subprocess.PIPE,
191                     stderr=subprocess.PIPE,
192                     close_fds=not is_windows,
193                 )
194                 convert_proc.stdout.close()
195 
196                 stdout, stderr = compare_proc.communicate()
197                 if compare_proc.returncode:
198                     if compare_proc.returncode != 1:
199                         self._log.debug(u'IM phashes compare failed for {0}, '
200                                         u'{1}', displayable_path(imagepath),
201                                         displayable_path(art))
202                         return
203                     out_str = stderr
204                 else:
205                     out_str = stdout
206 
207                 try:
208                     phash_diff = float(out_str)
209                 except ValueError:
210                     self._log.debug(u'IM output is not a number: {0!r}',
211                                     out_str)
212                     return
213 
214                 self._log.debug(u'compare PHASH score is {0}', phash_diff)
215                 return phash_diff <= compare_threshold
216 
217         return True
218 
219     def _mediafile_image(self, image_path, maxwidth=None):
220         """Return a `mediafile.Image` object for the path.
221         """
222 
223         with open(syspath(image_path), 'rb') as f:
224             data = f.read()
225         return mediafile.Image(data, type=mediafile.ImageType.front)
226 
227     def get_art(self, item):
228         # Extract the art.
229         try:
230             mf = mediafile.MediaFile(syspath(item.path))
231         except mediafile.UnreadableFileError as exc:
232             self._log.warning(u'Could not extract art from {0}: {1}',
233                               displayable_path(item.path), exc)
234             return
235 
236         return mf.art
237 
238     # 'extractart' command.
239 
240     def extract(self, outpath, item):
241         art = self.get_art(item)
242 
243         if not art:
244             self._log.info(u'No album art present in {0}, skipping.', item)
245             return
246 
247         # Add an extension to the filename.
248         ext = imghdr.what(None, h=art)
249         if not ext:
250             self._log.warning(u'Unknown image type in {0}.',
251                               displayable_path(item.path))
252             return
253         outpath += '.' + ext
254 
255         self._log.info(u'Extracting album art from: {0} to: {1}',
256                        item, displayable_path(outpath))
257         with open(syspath(outpath), 'wb') as f:
258             f.write(art)
259         return outpath
260 
261     # 'clearart' command.
262     def clear(self, lib, query):
263         id3v23 = config['id3v23'].get(bool)
264 
265         items = lib.items(query)
266         self._log.info(u'Clearing album art from {0} items', len(items))
267         for item in items:
268             self._log.debug(u'Clearing art for {0}', item)
269             try:
270                 mf = mediafile.MediaFile(syspath(item.path), id3v23)
271             except mediafile.UnreadableFileError as exc:
272                 self._log.warning(u'Could not read file {0}: {1}',
273                                   displayable_path(item.path), exc)
274             else:
275                 del mf.art
276                 mf.save()
277
```
--- 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
@@ -88,12 +88,30 @@
                                     help='extract an image from file metadata')
         extract_cmd.parser.add_option('-o', dest='outpath',
                                       help='image output file')
+        extract_cmd.parser.add_option('-n', dest='filename',
+                                      help='image filename to create for all '
+                                           'matched albums')
+        extract_cmd.parser.add_option('-a', dest='associate',
+                                      action='store_true',
+                                      help='associate the extracted images '
+                                           'with the album')
 
         def extract_func(lib, opts, args):
-            outpath = normpath(opts.outpath or config['art_filename'].get())
-            for item in lib.items(decargs(args)):
-                if self.extract(outpath, item):
+            if opts.outpath:
+                self.extract_first(normpath(opts.outpath),
+                                   lib.items(decargs(args)))
+            else:
+                filename = opts.filename or config['art_filename'].get()
+                if os.path.dirname(filename) != '':
+                    self._log.error(u"Only specify a name rather than a path "
+                                    u"for -n")
                     return
+                for album in lib.albums(decargs(args)):
+                    artpath = normpath(os.path.join(album.path, filename))
+                    artpath = self.extract_first(artpath, album.items())
+                    if artpath and opts.associate:
+                        album.set_art(artpath)
+                        album.store()
         extract_cmd.func = extract_func
 
         # Clear command.
@@ -236,7 +254,6 @@
         return mf.art
 
     # 'extractart' command.
-
     def extract(self, outpath, item):
         art = self.get_art(item)
 
@@ -258,6 +275,12 @@
             f.write(art)
         return outpath
 
+    def extract_first(self, outpath, items):
+        for item in items:
+            real_path = self.extract(outpath, item)
+            if real_path:
+                return real_path
+
     # 'clearart' command.
     def clear(self, lib, query):
         id3v23 = config['id3v23'].get(bool)

{"golden_diff": "diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py\n--- a/beetsplug/embedart.py\n+++ b/beetsplug/embedart.py\n@@ -88,12 +88,30 @@\n                                     help='extract an image from file metadata')\n         extract_cmd.parser.add_option('-o', dest='outpath',\n                                       help='image output file')\n+        extract_cmd.parser.add_option('-n', dest='filename',\n+                                      help='image filename to create for all '\n+                                           'matched albums')\n+        extract_cmd.parser.add_option('-a', dest='associate',\n+                                      action='store_true',\n+                                      help='associate the extracted images '\n+                                           'with the album')\n \n         def extract_func(lib, opts, args):\n-            outpath = normpath(opts.outpath or config['art_filename'].get())\n-            for item in lib.items(decargs(args)):\n-                if self.extract(outpath, item):\n+            if opts.outpath:\n+                self.extract_first(normpath(opts.outpath),\n+                                   lib.items(decargs(args)))\n+            else:\n+                filename = opts.filename or config['art_filename'].get()\n+                if os.path.dirname(filename) != '':\n+                    self._log.error(u\"Only specify a name rather than a path \"\n+                                    u\"for -n\")\n                     return\n+                for album in lib.albums(decargs(args)):\n+                    artpath = normpath(os.path.join(album.path, filename))\n+                    artpath = self.extract_first(artpath, album.items())\n+                    if artpath and opts.associate:\n+                        album.set_art(artpath)\n+                        album.store()\n         extract_cmd.func = extract_func\n \n         # Clear command.\n@@ -236,7 +254,6 @@\n         return mf.art\n \n     # 'extractart' command.\n-\n     def extract(self, outpath, item):\n         art = self.get_art(item)\n \n@@ -258,6 +275,12 @@\n             f.write(art)\n         return outpath\n \n+    def extract_first(self, outpath, items):\n+        for item in items:\n+            real_path = self.extract(outpath, item)\n+            if real_path:\n+                return real_path\n+\n     # 'clearart' command.\n     def clear(self, lib, query):\n         id3v23 = config['id3v23'].get(bool)\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.\"\"\"\nfrom __future__ import (division, absolute_import, print_function,\n                        unicode_literals)\n\nimport os.path\nimport imghdr\nimport subprocess\nimport platform\nfrom tempfile import NamedTemporaryFile\n\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.warning(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.warning(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            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.info(u'Image not similar; skipping.')\n                return\n        if ifempty and self.get_art(item):\n                self._log.info(u'media file already contained art')\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.warning(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 for {0}', album)\n            return\n        if not os.path.isfile(syspath(imagepath)):\n            self._log.info(u'Album art not found at {0} for {1}',\n                           displayable_path(imagepath), album)\n            return\n        if maxwidth:\n            imagepath = self.resize_image(imagepath, maxwidth)\n\n        self._log.info(u'Embedding album art into {0}', album)\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.debug(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.debug(u'compare PHASH score is {0}', phash_diff)\n                return phash_diff <= compare_threshold\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.warning(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        art = self.get_art(item)\n\n        if not art:\n            self._log.info(u'No album art present in {0}, skipping.', item)\n            return\n\n        # Add an extension to the filename.\n        ext = imghdr.what(None, h=art)\n        if not ext:\n            self._log.warning(u'Unknown image type in {0}.',\n                              displayable_path(item.path))\n            return\n        outpath += '.' + ext\n\n        self._log.info(u'Extracting album art from: {0} to: {1}',\n                       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        id3v23 = config['id3v23'].get(bool)\n\n        items = lib.items(query)\n        self._log.info(u'Clearing album art from {0} items', len(items))\n        for item in items:\n            self._log.debug(u'Clearing art for {0}', item)\n            try:\n                mf = mediafile.MediaFile(syspath(item.path), id3v23)\n            except mediafile.UnreadableFileError as exc:\n                self._log.warning(u'Could not read file {0}: {1}',\n                                  displayable_path(item.path), exc)\n            else:\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.\"\"\"\nfrom __future__ import (division, absolute_import, print_function,\n                        unicode_literals)\n\nimport os.path\nimport imghdr\nimport subprocess\nimport platform\nfrom tempfile import NamedTemporaryFile\n\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.warning(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.warning(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        extract_cmd.parser.add_option('-n', dest='filename',\n                                      help='image filename to create for all '\n                                           'matched albums')\n        extract_cmd.parser.add_option('-a', dest='associate',\n                                      action='store_true',\n                                      help='associate the extracted images '\n                                           'with the album')\n\n        def extract_func(lib, opts, args):\n            if opts.outpath:\n                self.extract_first(normpath(opts.outpath),\n                                   lib.items(decargs(args)))\n            else:\n                filename = opts.filename or config['art_filename'].get()\n                if os.path.dirname(filename) != '':\n                    self._log.error(u\"Only specify a name rather than a path \"\n                                    u\"for -n\")\n                    return\n                for album in lib.albums(decargs(args)):\n                    artpath = normpath(os.path.join(album.path, filename))\n                    artpath = self.extract_first(artpath, album.items())\n                    if artpath and opts.associate:\n                        album.set_art(artpath)\n                        album.store()\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.info(u'Image not similar; skipping.')\n                return\n        if ifempty and self.get_art(item):\n                self._log.info(u'media file already contained art')\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.warning(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 for {0}', album)\n            return\n        if not os.path.isfile(syspath(imagepath)):\n            self._log.info(u'Album art not found at {0} for {1}',\n                           displayable_path(imagepath), album)\n            return\n        if maxwidth:\n            imagepath = self.resize_image(imagepath, maxwidth)\n\n        self._log.info(u'Embedding album art into {0}', album)\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.debug(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.debug(u'compare PHASH score is {0}', phash_diff)\n                return phash_diff <= compare_threshold\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.warning(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    def extract(self, outpath, item):\n        art = self.get_art(item)\n\n        if not art:\n            self._log.info(u'No album art present in {0}, skipping.', item)\n            return\n\n        # Add an extension to the filename.\n        ext = imghdr.what(None, h=art)\n        if not ext:\n            self._log.warning(u'Unknown image type in {0}.',\n                              displayable_path(item.path))\n            return\n        outpath += '.' + ext\n\n        self._log.info(u'Extracting album art from: {0} to: {1}',\n                       item, displayable_path(outpath))\n        with open(syspath(outpath), 'wb') as f:\n            f.write(art)\n        return outpath\n\n    def extract_first(self, outpath, items):\n        for item in items:\n            real_path = self.extract(outpath, item)\n            if real_path:\n                return real_path\n\n    # 'clearart' command.\n    def clear(self, lib, query):\n        id3v23 = config['id3v23'].get(bool)\n\n        items = lib.items(query)\n        self._log.info(u'Clearing album art from {0} items', len(items))\n        for item in items:\n            self._log.debug(u'Clearing art for {0}', item)\n            try:\n                mf = mediafile.MediaFile(syspath(item.path), id3v23)\n            except mediafile.UnreadableFileError as exc:\n                self._log.warning(u'Could not read file {0}: {1}',\n                                  displayable_path(item.path), exc)\n            else:\n                del mf.art\n                mf.save()\n", "path": "beetsplug/embedart.py"}]}