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:
@@ -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:
|
with open(path, "rb") as f:
|
||||||
head = f.read(10)
|
head = f.read(10)
|
||||||
off = 0
|
|
||||||
if head[:3] == b"ID3":
|
if head[:3] == b"ID3":
|
||||||
off = 10 + ((head[6] << 21) | (head[7] << 14) | (head[8] << 7) | head[9])
|
offset = 10 + ((head[6] << 21) | (head[7] << 14) | (head[8] << 7) | head[9])
|
||||||
if head[5] & 0x10: # v2.4 footer
|
if head[5] & 0x10: # v2.4 footer
|
||||||
off += 10
|
offset += 10
|
||||||
f.seek(off)
|
with open(path, "rb") as f:
|
||||||
|
f.seek(offset)
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
while chunk := f.read(1 << 20):
|
while chunk := f.read(1 << 20):
|
||||||
h.update(chunk)
|
h.update(chunk)
|
||||||
|
|||||||
Reference in New Issue
Block a user