slim-embedded-art: drop junk lyrics frames over 50 KB
The 49 files the art pass left behind were fat with USLT, not APIC - the worst held the complete text of Angela's Ashes (1.5 MB of Frank McCourt) in an Eels track, some tagger malfunction. AVPlayer pages through the whole tag before the first audio frame regardless of what fills it, so absurd lyrics cost the same silence as absurd art. No song has 50 KB of lyrics; anything past it is junk and is removed.
This commit is contained in:
@@ -39,6 +39,10 @@ from PIL import Image
|
||||
ROOT = os.path.expanduser("~/Music")
|
||||
EMBED_TARGET = 80_000
|
||||
PROCESS_OVER = 100_000
|
||||
# No song has 50 KB of lyrics. The frame that set this limit held the complete text of Frank
|
||||
# McCourt's "Angela's Ashes" - 1.5 MB of memoir in an Eels track's USLT, courtesy of some tagger
|
||||
# malfunction - and AVPlayer pages through ALL of it before the first audio frame, art or not.
|
||||
LYRICS_SANE = 50_000
|
||||
|
||||
|
||||
def audio_region_hash(path, offset=None):
|
||||
@@ -120,22 +124,33 @@ def process(path, apply):
|
||||
return None
|
||||
pics = pictures(tag)
|
||||
total = sum(len(p.data) for p in pics)
|
||||
if total <= PROCESS_OVER:
|
||||
junk_lyrics = [f for f in tag.getall("USLT") if len(f.text or "") > LYRICS_SANE]
|
||||
if total <= PROCESS_OVER and not junk_lyrics:
|
||||
return None
|
||||
largest = max(pics, key=lambda p: len(p.data))
|
||||
largest = max(pics, key=lambda p: len(p.data)) if pics else None
|
||||
if not apply:
|
||||
return f"would slim {total // 1024} KB of art"
|
||||
parts = []
|
||||
if total > PROCESS_OVER: parts.append(f"{total // 1024} KB of art")
|
||||
if junk_lyrics: parts.append(f"{sum(len(f.text) for f in junk_lyrics) // 1024} KB of junk lyrics")
|
||||
return "would slim " + " + ".join(parts)
|
||||
|
||||
before = audio_region_hash(path)
|
||||
tmp = path + ".slimtmp"
|
||||
shutil.copy2(path, tmp)
|
||||
try:
|
||||
rescued = rescue_cover(os.path.dirname(path), largest.data)
|
||||
jpeg, note = compress_cover(largest.data)
|
||||
note = "art untouched"
|
||||
t = ID3(tmp)
|
||||
t.delall("APIC")
|
||||
t.delall("PIC")
|
||||
t.add(APIC(encoding=0, mime="image/jpeg", type=3, desc="", data=jpeg))
|
||||
if largest is not None and total > PROCESS_OVER:
|
||||
rescued = rescue_cover(os.path.dirname(path), largest.data)
|
||||
jpeg, note = compress_cover(largest.data)
|
||||
t.delall("APIC")
|
||||
t.delall("PIC")
|
||||
t.add(APIC(encoding=0, mime="image/jpeg", type=3, desc="", data=jpeg))
|
||||
else:
|
||||
rescued = False
|
||||
jpeg = b""
|
||||
for f in junk_lyrics:
|
||||
t.delall(f.HashKey)
|
||||
v2 = 4 if tag.version >= (2, 4, 0) else 3
|
||||
# v1=0 REMOVES any trailing ID3v1 tag: it is a 128-byte legacy appendix fully redundant
|
||||
# with the v2 frames, and keeping it in sync is what tripped the audio guard before the
|
||||
@@ -145,6 +160,8 @@ def process(path, apply):
|
||||
raise RuntimeError("audio bytes changed - keeping original")
|
||||
os.replace(tmp, path)
|
||||
note_out = f"{total // 1024} KB -> {len(jpeg) // 1024} KB ({note})"
|
||||
if junk_lyrics:
|
||||
note_out += f" -{sum(len(f.text) for f in junk_lyrics) // 1024} KB junk lyrics"
|
||||
return note_out + (" +rescued cover.jpg" if rescued else "")
|
||||
except Exception as e:
|
||||
if os.path.exists(tmp):
|
||||
|
||||
Reference in New Issue
Block a user