feat: metadati AI detection + prompt/lyrics + pannello frame grezzi (v1.13.0)

Metadati tab estesa per audit e cleanup file AI-generated:

- Nuovi campi read/write: source_url (WOAS ID3), lyrics (USLT), plus
  \xa9lyr per M4A
- Badge AI con riconoscimento: Suno, Udio, Riffusion, Mubert, AIVA,
  Boomy, SoundRAW, Loudly + presenza C2PA manifest (GEOB frame)
- Pannello "Metadati avanzati" collapsable: tabella di tutti i frame
  ID3 grezzi (chiave, tipo text/binary, preview, size)
- Rimozione selettiva frame custom via bridge remove_metadata_frames:
  ID3.__delitem__() + save() per MP3, MP4.tags per M4A. Frame standard
  protetti (TIT2/TPE1/APIC ecc.) marcati 🔒 nella UI, si svuotano dai
  campi form
- Rilevamento AI riusabile via core.metadata._detect_ai(comment,
  source_url, extra) — case-insensitive, returns (is_ai, source_name)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-09-24 11:13:58 +02:00
1 parent 8432b2a592
commit 9644ffec35
6 files changed
+399 -1

No files matched your search

+92
View File
@@ -1147,6 +1147,51 @@ function populateMetaForm(d) {
$("#metaBpm").value = d.bpm || "";
$("#metaKey").value = d.key || "";
$("#metaComment").value = d.comment || "";
$("#metaSourceUrl").value = d.source_url || "";
$("#metaLyrics").value = d.lyrics || "";
// Raw frames table
const rawBody = document.querySelector("#metaRawFrames tbody");
if (rawBody) {
// Frame gestiti dai campi standard: no checkbox (li rimuovi svuotando il campo)
const PROTECTED_MP3 = /^(TIT2|TPE1|TPE2|TALB|TDRC|TRCK|TCON|TBPM|TKEY|APIC)/;
const PROTECTED_MP4 = new Set(["\xa9nam","\xa9ART","aART","\xa9alb","\xa9day","trkn","\xa9gen","tmpo","covr"]);
rawBody.innerHTML = "";
(d.raw_frames || []).forEach((f) => {
const tr = document.createElement("tr");
const kindHtml = f.kind === "binary"
? '<span class="kind-binary">binary</span>'
: '<span style="color:var(--text-3);font-size:11px;">text</span>';
const isProtected = PROTECTED_MP3.test(f.key) || PROTECTED_MP4.has(f.key);
const cbHtml = isProtected
? '<span title="Frame standard — gestito dai campi sopra" style="color:var(--text-3);font-size:14px;">🔒</span>'
: `<input type="checkbox" class="meta-raw-check" data-key="${_escapeHtml(f.key)}">`;
tr.innerHTML = `
<td style="text-align:center;">${cbHtml}</td>
<td class="raw-key">${_escapeHtml(f.key)}</td>
<td>${kindHtml}</td>
<td class="raw-preview">${_escapeHtml(f.preview || "")}</td>
<td class="raw-size">${(f.size || 0).toLocaleString()}</td>
`;
rawBody.appendChild(tr);
});
// Reset stato selezione
const master = $("#metaRawSelectAll");
if (master) { master.checked = false; master.indeterminate = false; }
_updateRawFramesSelection();
}
// AI badge
const aiBadge = $("#metaAiBadge");
if (aiBadge) {
if (d.ai_generated) {
aiBadge.hidden = false;
aiBadge.textContent = d.ai_source ? `AI · ${d.ai_source}` : "AI Generated";
} else {
aiBadge.hidden = true;
aiBadge.textContent = "";
}
}
// WhereFroms (macOS only)
if (d.is_macos) {
@@ -1204,6 +1249,51 @@ $("#metaPickBtn").addEventListener("click", async () => {
if (path) await loadMetaFile(path);
});
function _updateRawFramesSelection() {
const boxes = $$("#metaRawFrames .meta-raw-check");
const checked = boxes.filter((b) => b.checked);
const cnt = $("#metaRawSelectedCount");
if (cnt) cnt.textContent = `${checked.length} selezionati`;
const btn = $("#metaRawRemoveBtn");
if (btn) btn.disabled = checked.length === 0;
const master = $("#metaRawSelectAll");
if (master) {
if (boxes.length === 0) { master.checked = false; master.indeterminate = false; }
else if (checked.length === 0) { master.checked = false; master.indeterminate = false; }
else if (checked.length === boxes.length) { master.checked = true; master.indeterminate = false; }
else { master.checked = false; master.indeterminate = true; }
}
}
document.addEventListener("change", (e) => {
const t = e.target;
if (t && t.classList && t.classList.contains("meta-raw-check")) {
_updateRawFramesSelection();
}
});
$("#metaRawSelectAll")?.addEventListener("change", (e) => {
$$("#metaRawFrames .meta-raw-check").forEach((b) => { b.checked = e.target.checked; });
_updateRawFramesSelection();
});
$("#metaRawRemoveBtn")?.addEventListener("click", async () => {
const keys = $$("#metaRawFrames .meta-raw-check")
.filter((b) => b.checked)
.map((b) => b.dataset.key);
if (keys.length === 0) return;
if (!window.confirm(`Rimuovere ${keys.length} frame dal file?\n\n${keys.join("\n")}\n\nOperazione permanente.`)) return;
const path = state.meta?.path;
if (!path) return;
const res = await window.pywebview.api.remove_metadata_frames(path, keys);
if (res && res.ok) {
toast(`Rimossi ${res.removed || 0} frame`, "ok");
await loadMetaFile(path); // ricarica per aggiornare tabella
} else {
toast("Errore: " + (res?.error || "sconosciuto"), "error");
}
});
$("#metaCoverBtn").addEventListener("click", async () => {
const path = await window.pywebview.api.pick_image_file();
if (!path) return;
@@ -1254,6 +1344,8 @@ $("#metaSaveBtn").addEventListener("click", async () => {
bpm: $("#metaBpm").value,
key: $("#metaKey").value,
comment: $("#metaComment").value,
source_url: $("#metaSourceUrl").value.trim(),
lyrics: $("#metaLyrics").value,
where_from: $("#metaWhereFrom").value
.split("\n")
.map(s => s.trim())