From 4017906d75339c74b00eba49a6ba4bdd5ef4df2b Mon Sep 17 00:00:00 2001 From: Sebastian Cao Date: Wed, 17 Jun 2026 23:35:42 +0800 Subject: [PATCH] convert: skip missing album art instead of crashing --- beetsplug/convert.py | 8 ++++++++ docs/changelog.rst | 4 ++++ test/plugins/test_convert.py | 15 +++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index bcefb0d3ed..d6d044d4f7 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -532,6 +532,14 @@ def copy_album_art(self, album: Album) -> None: if not album or not album.artpath: return + # The stored art path may point to a missing file (e.g. the cover + # lives in the album's root directory rather than a per-disc one). + if not os.path.isfile(util.syspath(album.artpath)): + self._log.info( + "Skipping {.art_filepath} (source file not found)", album + ) + return + album_item = album.items().get() # Album shouldn't be empty. if not album_item: diff --git a/docs/changelog.rst b/docs/changelog.rst index 23812a7b7d..188e32ff3f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -85,6 +85,10 @@ Bug fixes the database has been deleted from disk. Missing items are now skipped with a warning and the command continues. :bug:`6720` - :doc:`plugins/fish`: Fix error on plugin initialization. +- :doc:`plugins/convert`: ``convert -a`` with ``copy_album_art`` enabled no + longer crashes when the stored album art path points to a missing file (for + example a multi-disc album whose cover lives in the album root rather than a + per-disc directory); the missing art is skipped instead. :bug:`4692` For plugin developers ~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/plugins/test_convert.py b/test/plugins/test_convert.py index 31444c5fda..51b2006a66 100644 --- a/test/plugins/test_convert.py +++ b/test/plugins/test_convert.py @@ -181,6 +181,21 @@ def test_embed_album_art(self): mediafile = MediaFile(self.converted_mp3) assert mediafile.images[0].data == image_data + def test_copy_album_art_missing_source(self, caplog): + # A missing/stale art source should be skipped instead of crashing + # the conversion (see #4692). + self.config["convert"]["copy_album_art"] = True + self.album.artpath = os.path.join(_common.RSRC, b"nonexistent.jpg") + self.album.store() + + with caplog.at_level("INFO", logger="beets.convert"): + self.run_command("convert", "-a", "--yes") + + assert any( + "source file not found" in message for message in caplog.messages + ) + assert self.file_endswith(self.converted_mp3, "mp3") + def test_skip_existing(self): converted = self.converted_mp3 self.touch(converted, content="XXX")