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.
This commit is contained in:
2026-08-29 01:58:30 +01:00
parent fe1405f273
commit e0e96d10d7
+22 -9
View File
@@ -41,16 +41,29 @@ EMBED_TARGET = 80_000
PROCESS_OVER = 100_000 PROCESS_OVER = 100_000
def audio_region_hash(path): def audio_region_hash(path, offset=None):
"""SHA-256 of everything after the ID3v2 tag - the audio, byte-for-byte.""" """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: with open(path, "rb") as f:
head = f.read(10) f.seek(offset)
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)
h = hashlib.sha256() h = hashlib.sha256()
while chunk := f.read(1 << 20): while chunk := f.read(1 << 20):
h.update(chunk) h.update(chunk)