Carica lista: auto-detect URL list vs tracklist

Adesso 'Carica lista' nella tab Scarica riconosce automaticamente
il formato del file .txt:

- Se la prima riga e un URL (http/https) -> URL list (come prima)
- Altrimenti -> tracklist parsata 'Artista - Titolo (variant) (durata)'
  e scaricata cercando ogni brano su YouTube (riusa download_playlist)

Parser robusto: gestisce numerazione iniziale (1. / 1) / 1: / 1-),
qualsiasi tipo di dash (-, en-dash, em-dash, ...) e durata finale
'(M:SS)'. Testato sulla Beatport Top 100 Melodic House & Techno
(100/100 tracce parsate).

api/bridge.py: nuovo start_tracks_download, load_url_list ritorna
'kind: urls|tracks'.
webui/js/app.js: badge cambia tra 'X URL' e 'X tracce', avvio
chiama start_tracks_download quando appropriato.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
luzadevandClaude Opus 4.7 committed 2026-06-05 19:37:17 +02:00
1 parent e5158d382f
commit 4b1524e5f7
3 files changed
+248 -21

No files matched your search

+34 -19
View File
@@ -7,7 +7,7 @@ const $$ = (sel) => Array.from(document.querySelectorAll(sel));
const state = {
config: {},
urlList: [],
loaded: null, // { kind: "urls"|"tracks", urls?, tracks?, count }
dlOutputDir: "",
upDir: "",
videoOutputDir: "",
@@ -55,7 +55,7 @@ const bridgeHandlers = {
"download:done": () => {
state.downloading = false;
state.urlList = [];
state.loaded = null;
$("#urlListBadge").hidden = true;
$("#urlListBadge").textContent = "";
$("#downloadBtn").disabled = false;
@@ -221,27 +221,23 @@ $("#loadListBtn").addEventListener("click", async () => {
return;
}
if (!res.count) {
toast("Il file non contiene URL validi", "error");
toast("Il file non contiene voci valide", "error");
return;
}
state.urlList = res.urls;
state.loaded = res;
$("#urlInput").value = path;
const badge = $("#urlListBadge");
badge.textContent = `· ${res.count} URL caricati`;
if (res.kind === "tracks") {
badge.textContent = `· ${res.count} tracce`;
appendLog("download", `[INFO] Caricata tracklist: ${res.count} brani`);
} else {
badge.textContent = `· ${res.count} URL`;
appendLog("download", `[INFO] Caricati ${res.count} URL dal file`);
}
badge.hidden = false;
appendLog("download", `[INFO] Caricati ${res.count} URL dal file`);
});
$("#downloadBtn").addEventListener("click", async () => {
let urls = state.urlList;
if (!urls.length) {
const single = $("#urlInput").value.trim();
if (!single) {
toast("Inserisci un URL o carica una lista", "error");
return;
}
urls = [single];
}
if (!state.dlOutputDir) {
toast("Seleziona una cartella di destinazione", "error");
return;
@@ -254,10 +250,29 @@ $("#downloadBtn").addEventListener("click", async () => {
$("#dlProgressFill").style.width = "0%";
$("#dlPercent").textContent = "0%";
const res = await window.pywebview.api.start_download({
urls,
output_dir: state.dlOutputDir,
});
let res;
if (state.loaded && state.loaded.kind === "tracks") {
res = await window.pywebview.api.start_tracks_download({
tracks: state.loaded.tracks,
output_dir: state.dlOutputDir,
});
} else {
let urls = state.loaded && state.loaded.kind === "urls" ? state.loaded.urls : [];
if (!urls.length) {
const single = $("#urlInput").value.trim();
if (!single) {
toast("Inserisci un URL o carica una lista", "error");
bridgeHandlers["download:done"]();
return;
}
urls = [single];
}
res = await window.pywebview.api.start_download({
urls,
output_dir: state.dlOutputDir,
});
}
if (!res.ok) {
toast(res.error || "Errore", "error");
bridgeHandlers["download:done"]();