From 246ff1b0e5e78d29563c21f615e9dde6a6ce5117 Mon Sep 17 00:00:00 2001 From: luzadev Date: Mon, 8 Jun 2026 10:29:36 +0200 Subject: [PATCH] Metadati: aggiunto supporto WAV (ID3 chunk embedded) WAV puo' contenere un blocco ID3v2 embedded oltre al PCM raw. Mutagen.wave.WAVE espone gli stessi frame TIT2/TPE1/APIC etc. del MP3. - core/metadata.py: SUPPORTED_EXTS include '.wav', read/write via WAVE.tags (con add_tags() se mancante) - bridge.py: file picker include *.wav nel filter audio Round-trip verificato: titolo/artista/album/anno/track/bpm/key/ commento + copertina preservati. Co-Authored-By: Claude Opus 4.7 --- api/bridge.py | 2 +- core/metadata.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/api/bridge.py b/api/bridge.py index 1c08a24..20d16a1 100644 --- a/api/bridge.py +++ b/api/bridge.py @@ -248,7 +248,7 @@ class Api: # ------------------------------------------------------------------ def pick_audio_file(self) -> str: return self.browse_file([ - "Audio (*.mp3;*.m4a;*.mp4;*.aac;*.flac)", + "Audio (*.mp3;*.m4a;*.mp4;*.aac;*.flac;*.wav)", "All files (*.*)", ]) diff --git a/core/metadata.py b/core/metadata.py index 22b7a82..418ee7e 100644 --- a/core/metadata.py +++ b/core/metadata.py @@ -13,9 +13,10 @@ from mutagen.id3 import ( ) from mutagen.mp4 import MP4, MP4Cover from mutagen.flac import FLAC, Picture +from mutagen.wave import WAVE -SUPPORTED_EXTS = (".mp3", ".m4a", ".mp4", ".aac", ".flac") +SUPPORTED_EXTS = (".mp3", ".m4a", ".mp4", ".aac", ".flac", ".wav") def _text(value) -> str: @@ -89,6 +90,38 @@ def _read_mp4(path: str, result: dict) -> dict: return result +def _read_wav(path: str, result: dict) -> dict: + result["format"] = "WAV" + w = WAVE(path) + tags = w.tags # ID3 object, puo essere None + if tags is None: + return result + + result["title"] = _text(tags.get("TIT2")) + result["artist"] = _text(tags.get("TPE1")) + result["album_artist"] = _text(tags.get("TPE2")) + result["album"] = _text(tags.get("TALB")) + result["year"] = _text(tags.get("TDRC")) + result["track"] = _text(tags.get("TRCK")) + result["genre"] = _text(tags.get("TCON")) + result["bpm"] = _text(tags.get("TBPM")) + result["key"] = _text(tags.get("TKEY")) + + for k in tags.keys(): + if k.startswith("COMM"): + result["comment"] = _text(tags[k]) + break + + for k in tags.keys(): + if k.startswith("APIC"): + apic = tags[k] + result["cover_base64"] = base64.b64encode(apic.data).decode("ascii") + result["cover_mime"] = apic.mime or "image/jpeg" + break + + return result + + def _read_flac(path: str, result: dict) -> dict: result["format"] = "FLAC" f = FLAC(path) @@ -147,6 +180,8 @@ def read_metadata(path: str) -> dict: return _read_mp4(str(p), result) if ext == ".flac": return _read_flac(str(p), result) + if ext == ".wav": + return _read_wav(str(p), result) return result @@ -286,6 +321,51 @@ def _write_flac(path: str, data: dict, cover_path: Optional[str], remove_cover: f.save() +def _write_wav(path: str, data: dict, cover_path: Optional[str], remove_cover: bool) -> None: + w = WAVE(path) + if w.tags is None: + w.add_tags() + tags = w.tags + + def set_or_del(key, frame_cls, value): + if value: + tags[key] = frame_cls(encoding=3, text=value) + elif key in tags: + del tags[key] + + set_or_del("TIT2", TIT2, data.get("title", "")) + set_or_del("TPE1", TPE1, data.get("artist", "")) + set_or_del("TPE2", TPE2, data.get("album_artist", "")) + set_or_del("TALB", TALB, data.get("album", "")) + set_or_del("TDRC", TDRC, data.get("year", "")) + set_or_del("TRCK", TRCK, data.get("track", "")) + set_or_del("TCON", TCON, data.get("genre", "")) + set_or_del("TBPM", TBPM, data.get("bpm", "")) + set_or_del("TKEY", TKEY, data.get("key", "")) + + for k in list(tags.keys()): + if k.startswith("COMM"): + del tags[k] + comment = data.get("comment", "") + if comment: + tags.add(COMM(encoding=3, lang="ita", desc="", text=comment)) + + if remove_cover or cover_path: + for k in list(tags.keys()): + if k.startswith("APIC"): + del tags[k] + if cover_path: + cp = Path(cover_path) + if cp.exists(): + ext_c = cp.suffix.lower() + mime = "image/png" if ext_c == ".png" else "image/jpeg" + with open(cp, "rb") as f: + cover_data = f.read() + tags.add(APIC(encoding=3, mime=mime, type=3, desc="Cover", data=cover_data)) + + w.save() + + def write_metadata(path: str, data: dict, cover_path: Optional[str] = None, remove_cover: bool = False) -> None: """Salva i metadati. Se remove_cover=True rimuove la copertina esistente @@ -297,5 +377,7 @@ def write_metadata(path: str, data: dict, cover_path: Optional[str] = None, _write_mp4(path, data, cover_path, remove_cover) elif ext == ".flac": _write_flac(path, data, cover_path, remove_cover) + elif ext == ".wav": + _write_wav(path, data, cover_path, remove_cover) else: raise ValueError(f"Formato non supportato: {ext}")