ui: ConvertUI (file picker, opzioni, batch conversione)
This commit is contained in:
1 parent
9f41f9d302
commit
a774906814
1 file changed
+226
+226
@@ -399,6 +399,7 @@ const logEls = {
|
|||||||
video: () => $("#videoLog"),
|
video: () => $("#videoLog"),
|
||||||
spotify: () => $("#spotify-log"),
|
spotify: () => $("#spotify-log"),
|
||||||
youtube: () => $("#youtube-log"),
|
youtube: () => $("#youtube-log"),
|
||||||
|
convert: () => $("#convert-log"),
|
||||||
};
|
};
|
||||||
|
|
||||||
function classifyLog(msg) {
|
function classifyLog(msg) {
|
||||||
@@ -510,6 +511,9 @@ async function init() {
|
|||||||
// Spotify + YouTube search tabs
|
// Spotify + YouTube search tabs
|
||||||
await SpotifyUI.init();
|
await SpotifyUI.init();
|
||||||
await YoutubeUI.init();
|
await YoutubeUI.init();
|
||||||
|
|
||||||
|
// WAV -> MP3 converter tab
|
||||||
|
await ConvertUI.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshRecDevices() {
|
async function refreshRecDevices() {
|
||||||
@@ -1948,6 +1952,228 @@ const YoutubeUI = (() => {
|
|||||||
return { init };
|
return { init };
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// Converti (WAV -> MP3)
|
||||||
|
// =====================================================================
|
||||||
|
const ConvertUI = (() => {
|
||||||
|
const mstate = { files: [], selected: new Set(), running: false, outputDir: "" };
|
||||||
|
|
||||||
|
function _escape(s) {
|
||||||
|
const d = document.createElement("div");
|
||||||
|
d.textContent = s == null ? "" : String(s);
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTable() {
|
||||||
|
const tbody = $("#convert-tbody");
|
||||||
|
if (!tbody) return;
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
mstate.files.forEach((f, i) => {
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.dataset.idx = String(i);
|
||||||
|
const checked = mstate.selected.has(i) ? "checked" : "";
|
||||||
|
const baseName = f.split(/[\/\\]/).pop();
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td class="col-check"><input type="checkbox" data-idx="${i}" ${checked} /></td>
|
||||||
|
<td class="col-pos">${i + 1}</td>
|
||||||
|
<td title="${_escape(f)}">${_escape(baseName)}</td>
|
||||||
|
<td class="col-state" data-idx="${i}"></td>
|
||||||
|
`;
|
||||||
|
tr.querySelector("input[type=checkbox]").addEventListener("change", (e) => {
|
||||||
|
if (e.target.checked) mstate.selected.add(i);
|
||||||
|
else mstate.selected.delete(i);
|
||||||
|
updateSelectionCount();
|
||||||
|
});
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
$("#convert-table-wrap").hidden = mstate.files.length === 0;
|
||||||
|
updateSelectionCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectionCount() {
|
||||||
|
const total = mstate.files.length;
|
||||||
|
const sel = mstate.selected.size;
|
||||||
|
$("#convert-selected-count").textContent = `${sel}/${total} selezionati`;
|
||||||
|
$("#convert-run-btn").disabled = mstate.running || sel === 0;
|
||||||
|
const sa = $("#convert-select-all");
|
||||||
|
if (sa) {
|
||||||
|
sa.indeterminate = sel > 0 && sel < total;
|
||||||
|
sa.checked = sel === total && total > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendFiles(newFiles) {
|
||||||
|
// Dedup + append
|
||||||
|
const existing = new Set(mstate.files);
|
||||||
|
for (const f of newFiles) {
|
||||||
|
if (!existing.has(f)) {
|
||||||
|
mstate.files.push(f);
|
||||||
|
mstate.selected.add(mstate.files.length - 1);
|
||||||
|
existing.add(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(text, kind) {
|
||||||
|
const el = $("#convert-status");
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = text || "";
|
||||||
|
el.className = "beatport-status" + (kind ? " " + kind : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickFiles() {
|
||||||
|
const files = await window.pywebview.api.convert_pick_wav_files();
|
||||||
|
if (Array.isArray(files) && files.length) {
|
||||||
|
appendFiles(files);
|
||||||
|
setStatus("", "");
|
||||||
|
} else {
|
||||||
|
setStatus("Nessun file .wav selezionato", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickFolder() {
|
||||||
|
setStatus("Scansione cartella...", "loading");
|
||||||
|
const files = await window.pywebview.api.convert_pick_wav_folder(true);
|
||||||
|
if (Array.isArray(files) && files.length) {
|
||||||
|
appendFiles(files);
|
||||||
|
setStatus(`Trovati ${files.length} file .wav`, "ok");
|
||||||
|
} else {
|
||||||
|
setStatus("Nessun file .wav trovato nella cartella", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFiles() {
|
||||||
|
mstate.files = [];
|
||||||
|
mstate.selected.clear();
|
||||||
|
renderTable();
|
||||||
|
setStatus("", "");
|
||||||
|
$("#convert-log").innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function browseOutput() {
|
||||||
|
const p = await window.pywebview.api.browse_directory();
|
||||||
|
if (p && typeof p === "string") {
|
||||||
|
mstate.outputDir = p;
|
||||||
|
$("#convert-out-path").textContent = p;
|
||||||
|
$("#convert-out-custom").checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendConvertLog(msg) {
|
||||||
|
const el = $("#convert-log");
|
||||||
|
if (!el) return;
|
||||||
|
const cls = classifyLog(msg);
|
||||||
|
const line = document.createElement("div");
|
||||||
|
if (cls) line.className = cls;
|
||||||
|
line.textContent = msg;
|
||||||
|
el.appendChild(line);
|
||||||
|
el.scrollTop = el.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStateCell(idx, text, cls) {
|
||||||
|
const td = document.querySelector(`#convert-tbody td.col-state[data-idx="${idx}"]`);
|
||||||
|
if (!td) return;
|
||||||
|
td.textContent = text;
|
||||||
|
td.style.color = cls === "err" ? "var(--red)" : (cls === "ok" ? "var(--green-text)" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startConversion() {
|
||||||
|
const files = [...mstate.selected].sort((a, b) => a - b).map((i) => mstate.files[i]);
|
||||||
|
if (!files.length) return;
|
||||||
|
const bitrate = parseInt($("#convert-bitrate").value, 10);
|
||||||
|
const vbr = $("#convert-vbr").checked;
|
||||||
|
const useCustom = $("#convert-out-custom").checked;
|
||||||
|
const output_dir = useCustom ? (mstate.outputDir || "") : "";
|
||||||
|
if (useCustom && !output_dir) {
|
||||||
|
setStatus("Scegli una cartella di destinazione", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mstate.running = true;
|
||||||
|
$("#convert-run-btn").disabled = true;
|
||||||
|
$("#convert-stop-btn").hidden = false;
|
||||||
|
$("#convert-log").innerHTML = "";
|
||||||
|
// Reset state cells
|
||||||
|
$$("#convert-tbody td.col-state").forEach((td) => {
|
||||||
|
td.textContent = "";
|
||||||
|
td.style.color = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await window.pywebview.api.convert_start({ files, bitrate, vbr, output_dir });
|
||||||
|
} catch (e) {
|
||||||
|
appendConvertLog("[ERRORE] " + ((e && e.message) || e));
|
||||||
|
finishConversion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!res || !res.ok) {
|
||||||
|
const errMsg = (res && res.error) || "Impossibile avviare";
|
||||||
|
appendConvertLog("[ERRORE] " + errMsg);
|
||||||
|
if (!handleGateBlock(res || {})) toast(errMsg, "error");
|
||||||
|
finishConversion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopConversion() {
|
||||||
|
try { await window.pywebview.api.convert_stop(); } catch (e) { console.error(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishConversion() {
|
||||||
|
mstate.running = false;
|
||||||
|
$("#convert-run-btn").disabled = mstate.selected.size === 0;
|
||||||
|
$("#convert-stop-btn").hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
// Bind eventi
|
||||||
|
$("#convert-pick-files").addEventListener("click", pickFiles);
|
||||||
|
$("#convert-pick-folder").addEventListener("click", pickFolder);
|
||||||
|
$("#convert-clear").addEventListener("click", clearFiles);
|
||||||
|
$("#convert-out-browse").addEventListener("click", browseOutput);
|
||||||
|
$("#convert-out-custom").addEventListener("change", () => {
|
||||||
|
$("#convert-out-browse").disabled = false;
|
||||||
|
});
|
||||||
|
$("#convert-out-same").addEventListener("change", () => {
|
||||||
|
$("#convert-out-browse").disabled = true;
|
||||||
|
});
|
||||||
|
$("#convert-select-all").addEventListener("change", (e) => {
|
||||||
|
mstate.selected.clear();
|
||||||
|
if (e.target.checked) {
|
||||||
|
for (let i = 0; i < mstate.files.length; i++) mstate.selected.add(i);
|
||||||
|
}
|
||||||
|
renderTable();
|
||||||
|
});
|
||||||
|
$("#convert-run-btn").addEventListener("click", startConversion);
|
||||||
|
$("#convert-stop-btn").addEventListener("click", stopConversion);
|
||||||
|
|
||||||
|
// Bridge handlers per il canale "convert"
|
||||||
|
const origProgress = bridgeHandlers["convert:progress"];
|
||||||
|
bridgeHandlers["convert:progress"] = (p) => {
|
||||||
|
if (origProgress) origProgress(p);
|
||||||
|
if (!p) return;
|
||||||
|
if (p.status === "done") updateStateCell(p.idx, "✓ fatto", "ok");
|
||||||
|
else if (p.status === "skipped") updateStateCell(p.idx, "già esiste", "");
|
||||||
|
else if (p.status === "stopped") updateStateCell(p.idx, "interrotto", "");
|
||||||
|
else if (typeof p.status === "string" && p.status.startsWith("error")) updateStateCell(p.idx, "✗ errore", "err");
|
||||||
|
else if (p.status === "converting") updateStateCell(p.idx, `${p.pct || 0}%`, "");
|
||||||
|
};
|
||||||
|
const origDone = bridgeHandlers["convert:done"];
|
||||||
|
bridgeHandlers["convert:done"] = (p) => {
|
||||||
|
if (origDone) origDone(p);
|
||||||
|
if (mstate.running) {
|
||||||
|
appendConvertLog("[INFO] Conversione terminata.");
|
||||||
|
finishConversion();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Il canale "log" e' gia gestito da logEls.convert (aggiunto sopra), quindi
|
||||||
|
// non serve wrap qui: appendLog(view=convert, msg) targeta #convert-log.
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init };
|
||||||
|
})();
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Boot
|
// Boot
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in new issue
Block a user