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 <noreply@anthropic.com>
This commit is contained in:
1 parent
3dcbdfa489
commit
246ff1b0e5
2 files changed
+84
-2
No files matched your search
+1
-1
@@ -248,7 +248,7 @@ class Api:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def pick_audio_file(self) -> str:
|
def pick_audio_file(self) -> str:
|
||||||
return self.browse_file([
|
return self.browse_file([
|
||||||
"Audio (*.mp3;*.m4a;*.mp4;*.aac;*.flac)",
|
"Audio (*.mp3;*.m4a;*.mp4;*.aac;*.flac;*.wav)",
|
||||||
"All files (*.*)",
|
"All files (*.*)",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
+83
-1
@@ -13,9 +13,10 @@ from mutagen.id3 import (
|
|||||||
)
|
)
|
||||||
from mutagen.mp4 import MP4, MP4Cover
|
from mutagen.mp4 import MP4, MP4Cover
|
||||||
from mutagen.flac import FLAC, Picture
|
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:
|
def _text(value) -> str:
|
||||||
@@ -89,6 +90,38 @@ def _read_mp4(path: str, result: dict) -> dict:
|
|||||||
return result
|
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:
|
def _read_flac(path: str, result: dict) -> dict:
|
||||||
result["format"] = "FLAC"
|
result["format"] = "FLAC"
|
||||||
f = FLAC(path)
|
f = FLAC(path)
|
||||||
@@ -147,6 +180,8 @@ def read_metadata(path: str) -> dict:
|
|||||||
return _read_mp4(str(p), result)
|
return _read_mp4(str(p), result)
|
||||||
if ext == ".flac":
|
if ext == ".flac":
|
||||||
return _read_flac(str(p), result)
|
return _read_flac(str(p), result)
|
||||||
|
if ext == ".wav":
|
||||||
|
return _read_wav(str(p), result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -286,6 +321,51 @@ def _write_flac(path: str, data: dict, cover_path: Optional[str], remove_cover:
|
|||||||
f.save()
|
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,
|
def write_metadata(path: str, data: dict, cover_path: Optional[str] = None,
|
||||||
remove_cover: bool = False) -> None:
|
remove_cover: bool = False) -> None:
|
||||||
"""Salva i metadati. Se remove_cover=True rimuove la copertina esistente
|
"""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)
|
_write_mp4(path, data, cover_path, remove_cover)
|
||||||
elif ext == ".flac":
|
elif ext == ".flac":
|
||||||
_write_flac(path, data, cover_path, remove_cover)
|
_write_flac(path, data, cover_path, remove_cover)
|
||||||
|
elif ext == ".wav":
|
||||||
|
_write_wav(path, data, cover_path, remove_cover)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Formato non supportato: {ext}")
|
raise ValueError(f"Formato non supportato: {ext}")
|
||||||
Reference in new issue
Block a user