Metadati WAV: fallback al chunk RIFF LIST/INFO
Mutagen.wave legge solo i tag ID3 embedded nel WAV, ma molti file WAV usano il chunk RIFF LIST/INFO originale (es. ffmpeg, vari encoder semplici). Aggiunto _read_wav_info_chunk() che parsa manualmente il blocco RIFF e mappa i campi standard (INAM, IART, IPRD, ICRD, IGNR, ICMT, ITRK, IBPM, ...) sui campi della UI. In _read_wav() ora si legge prima il chunk INFO come fallback, poi l'ID3 sovrascrive i valori se presenti (ID3 ha priorita per i WAV moderni). In scrittura si continua a usare ID3 (standard piu ricco e supportato dai player moderni); i campi INFO esistenti restano nel file ma diventano ridondanti. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
246ff1b0e5
commit
61879c42d3
1 file changed
+90
-11
+90
-11
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import struct
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -90,26 +91,104 @@ def _read_mp4(path: str, result: dict) -> dict:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_WAV_INFO_MAP = {
|
||||||
|
"INAM": "title",
|
||||||
|
"IART": "artist",
|
||||||
|
"IPRD": "album",
|
||||||
|
"ICRD": "year",
|
||||||
|
"IYER": "year",
|
||||||
|
"IGNR": "genre",
|
||||||
|
"ICMT": "comment",
|
||||||
|
"ITRK": "track",
|
||||||
|
"IPRT": "track",
|
||||||
|
"TRCK": "track",
|
||||||
|
"IBPM": "bpm",
|
||||||
|
"TBPM": "bpm",
|
||||||
|
"TKEY": "key",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_wav_info_chunk(path: str) -> dict:
|
||||||
|
"""Legge il chunk RIFF LIST/INFO di un file WAV.
|
||||||
|
Ritorna un dict con i campi noti. Usato come fallback quando
|
||||||
|
il WAV non contiene ID3 embedded."""
|
||||||
|
out: dict = {}
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
header = f.read(12)
|
||||||
|
if len(header) < 12 or header[:4] != b"RIFF" or header[8:12] != b"WAVE":
|
||||||
|
return out
|
||||||
|
while True:
|
||||||
|
hdr = f.read(8)
|
||||||
|
if len(hdr) < 8:
|
||||||
|
break
|
||||||
|
chunk_id, chunk_size = struct.unpack("<4sI", hdr)
|
||||||
|
if chunk_id == b"LIST":
|
||||||
|
list_type = f.read(4)
|
||||||
|
list_end = f.tell() + (chunk_size - 4)
|
||||||
|
if list_type == b"INFO":
|
||||||
|
while f.tell() < list_end:
|
||||||
|
sub = f.read(8)
|
||||||
|
if len(sub) < 8:
|
||||||
|
break
|
||||||
|
sub_id, sub_size = struct.unpack("<4sI", sub)
|
||||||
|
data = f.read(sub_size).rstrip(b"\x00")
|
||||||
|
if sub_size % 2 == 1:
|
||||||
|
f.read(1) # padding word-aligned
|
||||||
|
key = _WAV_INFO_MAP.get(sub_id.decode("ascii", errors="ignore"))
|
||||||
|
if not key or not data:
|
||||||
|
continue
|
||||||
|
for enc in ("utf-8", "latin-1"):
|
||||||
|
try:
|
||||||
|
out[key] = data.decode(enc).strip()
|
||||||
|
break
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
f.seek(list_end)
|
||||||
|
else:
|
||||||
|
skip = chunk_size + (chunk_size % 2)
|
||||||
|
f.seek(skip, 1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _read_wav(path: str, result: dict) -> dict:
|
def _read_wav(path: str, result: dict) -> dict:
|
||||||
result["format"] = "WAV"
|
result["format"] = "WAV"
|
||||||
|
|
||||||
|
# Prima fallback: leggi il chunk INFO (formato RIFF originale)
|
||||||
|
info = _read_wav_info_chunk(path)
|
||||||
|
for k, v in info.items():
|
||||||
|
if v:
|
||||||
|
result[k] = v
|
||||||
|
|
||||||
|
# Poi leggi ID3 se presente; ha priorita sui valori INFO
|
||||||
w = WAVE(path)
|
w = WAVE(path)
|
||||||
tags = w.tags # ID3 object, puo essere None
|
tags = w.tags
|
||||||
if tags is None:
|
if tags is None:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
result["title"] = _text(tags.get("TIT2"))
|
def setif(key, frame_key):
|
||||||
result["artist"] = _text(tags.get("TPE1"))
|
v = _text(tags.get(frame_key))
|
||||||
result["album_artist"] = _text(tags.get("TPE2"))
|
if v:
|
||||||
result["album"] = _text(tags.get("TALB"))
|
result[key] = v
|
||||||
result["year"] = _text(tags.get("TDRC"))
|
|
||||||
result["track"] = _text(tags.get("TRCK"))
|
setif("title", "TIT2")
|
||||||
result["genre"] = _text(tags.get("TCON"))
|
setif("artist", "TPE1")
|
||||||
result["bpm"] = _text(tags.get("TBPM"))
|
setif("album_artist", "TPE2")
|
||||||
result["key"] = _text(tags.get("TKEY"))
|
setif("album", "TALB")
|
||||||
|
setif("year", "TDRC")
|
||||||
|
setif("track", "TRCK")
|
||||||
|
setif("genre", "TCON")
|
||||||
|
setif("bpm", "TBPM")
|
||||||
|
setif("key", "TKEY")
|
||||||
|
|
||||||
for k in tags.keys():
|
for k in tags.keys():
|
||||||
if k.startswith("COMM"):
|
if k.startswith("COMM"):
|
||||||
result["comment"] = _text(tags[k])
|
v = _text(tags[k])
|
||||||
|
if v:
|
||||||
|
result["comment"] = v
|
||||||
break
|
break
|
||||||
|
|
||||||
for k in tags.keys():
|
for k in tags.keys():
|
||||||
|
|||||||
Reference in new issue
Block a user