From e0e96d10d7ecdd767d36c7abe97f4be1e9ba04e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 01:58:30 +0100 Subject: [PATCH] slim-embedded-art: hash the audio region from mutagen own tag parse A tagger that writes a non-standard size field puts the header claim and the real tag end in different places. mutagen preserves audio from where it parsed the tag to end; the guard hashed from where the header claimed, and refused good rewrites across a whole album. Both hashes now use the same authority. Failed files stay untouched and a re-run picks them up. --- scripts/slim-embedded-art.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/scripts/slim-embedded-art.py b/scripts/slim-embedded-art.py index 5bec3cf..1cee63e 100755 --- a/scripts/slim-embedded-art.py +++ b/scripts/slim-embedded-art.py @@ -41,16 +41,29 @@ EMBED_TARGET = 80_000 PROCESS_OVER = 100_000 -def audio_region_hash(path): - """SHA-256 of everything after the ID3v2 tag - the audio, byte-for-byte.""" +def audio_region_hash(path, offset=None): + """SHA-256 of everything after the ID3v2 tag - the audio, byte-for-byte. + + The offset comes from mutagen's OWN parse of the tag when available. Reading the header's + syncsafe size field ourselves disagreed with mutagen on files whose tagger wrote a + non-standard size (Shamrocks and Shenanigans, 2026-08-29): mutagen preserved the audio from + where IT parsed the tag to end, our hash read from where the header claimed, and the guard + refused a perfectly good rewrite. + """ + if offset is None: + try: + offset = ID3(path).size + except Exception: + offset = 0 + if not offset: + with open(path, "rb") as f: + head = f.read(10) + if head[:3] == b"ID3": + offset = 10 + ((head[6] << 21) | (head[7] << 14) | (head[8] << 7) | head[9]) + if head[5] & 0x10: # v2.4 footer + offset += 10 with open(path, "rb") as f: - head = f.read(10) - off = 0 - if head[:3] == b"ID3": - off = 10 + ((head[6] << 21) | (head[7] << 14) | (head[8] << 7) | head[9]) - if head[5] & 0x10: # v2.4 footer - off += 10 - f.seek(off) + f.seek(offset) h = hashlib.sha256() while chunk := f.read(1 << 20): h.update(chunk)