Repository: beetbox/beets

Issue:
[embedart] ValueError: could not convert string to float:
Testing out beets for the first time before fixing my large music library.
Running it over some local files to see how the different plugins react, etc...

error output here: https://gist.github.com/k-f-/78e5ee77101da1d57257



Discussion:
beets config: https://gist.github.com/k-f-/6cb053f622a0bc52f9ce

Thanks for the report. Can you please send along a reproducible test case? I think this may be sensitive to the specific files you're importing, so we will need them to figure out what's going on.

Meanwhile, @Kraymer, any clues about why parsing the ImageMagick output might fail? Perhaps we should catch any unexpected output and log an error.

As you requested some files I was testing over that gave errors.
http://kfring.com/downloads/beetsfiles.zip

The command I was running:
beet -v import {FILES} 2>&1 | tee beets.log

Not exactly sure why, but disabling fetchart results in no errors.

Thanks for providing the samples. Unfortunately, I can't reproduce this; using your config, these files imported without incident. I'm guessing this could be due to different versions of ImageMagick.

I'm going to improve the error-handling around the errors that you're seeing. This should (a) let the process continue even when IM output can't be parsed, and (b) maybe give some more insight into the malformed output.

OK, error handling added. Can you try this again with the latest source and see if anything is different? If things still go wrong, can you provide the verbose output from an example?

The handling seems broken: if you get a ValueError you're logging it and continuing, but `phash_diff` won't be defined! Fix & test cases incoming.

Oops; that was incredibly dumb of me—thanks, @brunal. :sparkles:

@sampsyo, just wanted to let you guys know making this change in my config has stopped this error.  Not a fix, but it's a start.

```
embedart:
    compare_threshold:    0  # disabled. previously 80.
```

Also, I'd like to just say that `beets` is wonderful, wonderful, wonderful.  I've had a anally retentive music collection for over a decade and probably tried ~10 programs & various scripts/code with surprisingly good results.  However, beets is murdering all previous workflows.

:trophy:
thanks!

**edit**
embedart seems to be all good with the above change.  beets-check however still explodes pretty often.  I've opened an issue over there about it over at: geigerzaehler/beets-check#14

Yep, that disables the comparison feature where this issue arose. I'll close this now since we have better error handling.

And awesome! I'm glad beets is working out well for you.

I think what was causing it to explode:
`cover.jpg` already existed prior to import.
beets fetchart created `cover.1.jpg` or in some cases `cover.2.jpg` which I then manually deleted.

I'll take a look at the source code, but perhaps it was **user error** and the imagemagick stuff was correctly looking for the beets generated `.jpg`. because it wants to use what the library thinks is the art file.

I had hoped fetchart would not download if `cover.jpg` existed in the album dir.

Aha, that is a reasonable explanation.

You are right that `fetchart` should detect your local file before fetching anything new. Let us know if you find any evidence for why that went wrong.


Resolving patch:
diff --git a/beets/library.py b/beets/library.py
--- a/beets/library.py
+++ b/beets/library.py
@@ -500,12 +500,18 @@ def read(self, read_path=None):
 
         self.path = read_path
 
-    def write(self, path=None):
+    def write(self, path=None, tags=None):
         """Write the item's metadata to a media file.
 
         All fields in `_media_fields` are written to disk according to
         the values on this object.
 
+        `path` is the path of the mediafile to wirte the data to. It
+        defaults to the item's path.
+
+        `tags` is a dictionary of additional metadata the should be
+        written to the file.
+
         Can raise either a `ReadError` or a `WriteError`.
         """
         if path is None:
@@ -513,8 +519,10 @@ def write(self, path=None):
         else:
             path = normpath(path)
 
-        tags = dict(self)
-        plugins.send('write', item=self, path=path, tags=tags)
+        item_tags = dict(self)
+        if tags is not None:
+            item_tags.update(tags)
+        plugins.send('write', item=self, path=path, tags=item_tags)
 
         try:
             mediafile = MediaFile(syspath(path),
@@ -522,7 +530,7 @@ def write(self, path=None):
         except (OSError, IOError, UnreadableFileError) as exc:
             raise ReadError(self.path, exc)
 
-        mediafile.update(tags)
+        mediafile.update(item_tags)
         try:
             mediafile.save()
         except (OSError, IOError, MutagenError) as exc:
@@ -533,14 +541,14 @@ def write(self, path=None):
             self.mtime = self.current_mtime()
         plugins.send('after_write', item=self, path=path)
 
-    def try_write(self, path=None):
+    def try_write(self, path=None, tags=None):
         """Calls `write()` but catches and logs `FileOperationError`
         exceptions.
 
         Returns `False` an exception was caught and `True` otherwise.
         """
         try:
-            self.write(path)
+            self.write(path, tags)
             return True
         except FileOperationError as exc:
             log.error("{0}", exc)
diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py
--- a/beetsplug/embedart.py
+++ b/beetsplug/embedart.py
@@ -148,13 +148,11 @@ def embed_item(self, item, imagepath, maxwidth=None, itempath=None,
 
         try:
             self._log.debug(u'embedding {0}', displayable_path(imagepath))
-            item['images'] = [self._mediafile_image(imagepath, maxwidth)]
+            image = self._mediafile_image(imagepath, maxwidth)
         except IOError as 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)
-            del item['images']
+            return
+        item.try_write(path=itempath, tags={'images': [image]})
 
     def embed_album(self, album, maxwidth=None, quiet=False):
         """Embed album art into all of the album's items.


Test patch:
diff --git a/test/test_library.py b/test/test_library.py
--- a/test/test_library.py
+++ b/test/test_library.py
@@ -35,6 +35,7 @@
 from beets import plugins
 from beets import config
 from beets.mediafile import MediaFile
+from test.helper import TestHelper
 
 # Shortcut to path normalization.
 np = util.normpath
@@ -1109,37 +1110,49 @@ def test_unicode_path(self):
         self.i.write()
 
 
-class WriteTest(_common.LibTestCase):
+class WriteTest(unittest.TestCase, TestHelper):
+    def setUp(self):
+        self.setup_beets()
+
+    def tearDown(self):
+        self.teardown_beets()
+
     def test_write_nonexistant(self):
-        self.i.path = '/path/does/not/exist'
-        self.assertRaises(beets.library.ReadError, self.i.write)
+        item = self.create_item()
+        item.path = '/path/does/not/exist'
+        with self.assertRaises(beets.library.ReadError):
+            item.write()
 
     def test_no_write_permission(self):
-        path = os.path.join(self.temp_dir, 'file.mp3')
-        shutil.copy(os.path.join(_common.RSRC, 'empty.mp3'), path)
+        item = self.add_item_fixture()
+        path = item.path
         os.chmod(path, stat.S_IRUSR)
 
         try:
-            self.i.path = path
-            self.assertRaises(beets.library.WriteError, self.i.write)
+            self.assertRaises(beets.library.WriteError, item.write)
 
         finally:
             # Restore write permissions so the file can be cleaned up.
             os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
 
     def test_write_with_custom_path(self):
-        custom_path = os.path.join(self.temp_dir, 'file.mp3')
-        self.i.path = os.path.join(self.temp_dir, 'item_file.mp3')
-        shutil.copy(os.path.join(_common.RSRC, 'empty.mp3'), custom_path)
-        shutil.copy(os.path.join(_common.RSRC, 'empty.mp3'), self.i.path)
+        item = self.add_item_fixture()
+        custom_path = os.path.join(self.temp_dir, 'custom.mp3')
+        shutil.copy(item.path, custom_path)
 
-        self.i['artist'] = 'new artist'
+        item['artist'] = 'new artist'
         self.assertNotEqual(MediaFile(custom_path).artist, 'new artist')
-        self.assertNotEqual(MediaFile(self.i.path).artist, 'new artist')
+        self.assertNotEqual(MediaFile(item.path).artist, 'new artist')
 
-        self.i.write(custom_path)
+        item.write(custom_path)
         self.assertEqual(MediaFile(custom_path).artist, 'new artist')
-        self.assertNotEqual(MediaFile(self.i.path).artist, 'new artist')
+        self.assertNotEqual(MediaFile(item.path).artist, 'new artist')
+
+    def test_write_custom_tags(self):
+        item = self.add_item_fixture(artist='old artist')
+        item.write(tags={'artist': 'new artist'})
+        self.assertNotEqual(item.artist, 'new artist')
+        self.assertEqual(MediaFile(item.path).artist, 'new artist')
 
 
 class ItemReadTest(unittest.TestCase):