Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions beetsplug/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~
Expand Down
15 changes: 15 additions & 0 deletions test/plugins/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading