aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-26 18:09:49 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-26 18:09:49 +0200
commite1f1b65cfac031096e4bae24ccf102ca0dbb86d9 (patch)
tree1a783c4ed5dcf23bc65d6bb8825906473670d3aa /packages/meshbay-node
parent80cb6c4dc5f22336387f2ac74ef2cb85cf2cf7d4 (diff)
parent6fb948045b9c563ce591da289cdac1df6bed360c (diff)
downloadmeshbay-e1f1b65cfac031096e4bae24ccf102ca0dbb86d9.tar.gz
Merge branch 'fix/music-cover-mixed-folder'
Diffstat (limited to 'packages/meshbay-node')
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/enrich_audio.py17
-rw-r--r--packages/meshbay-node/tests/test_enrich_audio.py38
2 files changed, 55 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/enrich_audio.py b/packages/meshbay-node/src/meshbay_node/indexer/enrich_audio.py
index a93df51..4fa08ef 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/enrich_audio.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/enrich_audio.py
@@ -141,6 +141,18 @@ def _extract_cover(mf) -> bytes | None:
_COVER_STEM_RANK = ("cover", "folder", "front", "albumart")
_COVER_EXTS = (".jpg", ".jpeg", ".png")
+# Windows Media Player's per-folder thumbnail cache: one `AlbumArt_{guid}_*`
+# pair per *release* it ever cached art for in that folder, keyed by a GUID
+# tied to that release — never to a track. Real folder, real GUIDs (module
+# docstring's revision note): a ~500-track flat "chart ranking" rip mixing
+# dozens of unrelated artists carried seven distinct guids here, each an art
+# leftover from one different original album. `Folder.jpg`/`AlbumArtSmall.jpg`
+# are WMP's own copy of just *one* arbitrary one of those for the folder icon
+# — fine when a folder really is one release (one guid, or none), meaningless
+# once two or more show up: there is no way to tell which track it belongs to,
+# so no candidate in the folder can be trusted as "the" cover for any of them.
+_WMP_ALBUMART_GUID_RE = re.compile(r"^albumart_\{([0-9a-f-]{36})\}_(large|small)$", re.IGNORECASE)
+
def _find_sibling_cover(folder: Path) -> Path | None:
try:
@@ -151,6 +163,11 @@ def _find_sibling_cover(folder: Path) -> Path | None:
if not candidates:
return None
+ guids = {m.group(1).lower() for p in candidates
+ if (m := _WMP_ALBUMART_GUID_RE.match(p.stem))}
+ if len(guids) >= 2:
+ return None
+
def rank(p: Path) -> int:
stem = p.stem.lower()
for i, name in enumerate(_COVER_STEM_RANK):
diff --git a/packages/meshbay-node/tests/test_enrich_audio.py b/packages/meshbay-node/tests/test_enrich_audio.py
index 1485a1f..d69a097 100644
--- a/packages/meshbay-node/tests/test_enrich_audio.py
+++ b/packages/meshbay-node/tests/test_enrich_audio.py
@@ -12,6 +12,7 @@ from meshbay_node.indexer.enrich_audio import (
_artist_album_from_ancestors,
_clean_tag,
_extract_cover,
+ _find_sibling_cover,
_musepack_tags_from_object,
_split_top_level_folder,
)
@@ -292,6 +293,43 @@ async def test_enricher_uses_a_sibling_cover_file_when_no_embedded_art(tmp_path,
assert stored == b"\xff\xd8\xff\xe0fake-jpeg-bytes"
+def test_find_sibling_cover_refuses_a_folder_that_mixes_several_releases(tmp_path):
+ """
+ A real case: dozens of individually-ripped tracks by unrelated artists
+ sitting flat in one folder (an informal "chart ranking" compilation,
+ never a real per-album folder). The ripper left one `AlbumArt_{guid}_*`
+ thumbnail pair *per original release* it pulled a track from, plus its
+ own arbitrary `Folder.jpg` copy of just one of them. Two or more distinct
+ guids among the candidates is direct proof this folder is not a single
+ release, so no image here can be safely called "the" cover — picking
+ `Folder.jpg` anyway (the pre-fix behavior) silently attached one
+ unrelated release's art to every other track in the folder.
+ """
+ folder = tmp_path / "_SomeChartFolder"
+ folder.mkdir()
+ (folder / "AlbumArt_{2C5E5D27-1075-43B9-92EE-351846034C67}_Large.jpg").write_bytes(b"one")
+ (folder / "AlbumArt_{2C5E5D27-1075-43B9-92EE-351846034C67}_Small.jpg").write_bytes(b"one")
+ (folder / "AlbumArt_{427CA566-E7E9-4ECD-9ED1-1DDADB9D07E8}_Large.jpg").write_bytes(b"two")
+ (folder / "AlbumArt_{427CA566-E7E9-4ECD-9ED1-1DDADB9D07E8}_Small.jpg").write_bytes(b"two")
+ (folder / "AlbumArtSmall.jpg").write_bytes(b"whichever")
+ (folder / "Folder.jpg").write_bytes(b"whichever")
+
+ assert _find_sibling_cover(folder) is None
+
+
+def test_find_sibling_cover_still_picks_folder_jpg_for_a_single_release(tmp_path):
+ """Regression guard: a real single-release folder (one guid, or none at
+ all, same shape as the existing sibling-cover test above) must keep
+ picking its Folder.jpg exactly as before this fix."""
+ folder = tmp_path / "Some Artist" / "Some Album"
+ folder.mkdir(parents=True)
+ (folder / "AlbumArt_{2C5E5D27-1075-43B9-92EE-351846034C67}_Large.jpg").write_bytes(b"one")
+ (folder / "AlbumArt_{2C5E5D27-1075-43B9-92EE-351846034C67}_Small.jpg").write_bytes(b"one")
+ (folder / "Folder.jpg").write_bytes(b"one")
+
+ assert _find_sibling_cover(folder) == folder / "Folder.jpg"
+
+
async def _run(enricher, entry, path, root_path=None):
done = asyncio.get_event_loop().create_future()