diff --git a/scripts/slim-embedded-art.py b/scripts/slim-embedded-art.py index 1f43ea7..bafc363 100755 --- a/scripts/slim-embedded-art.py +++ b/scripts/slim-embedded-art.py @@ -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):