From e56a975b773caa5efaee6a93509ef342c370b4e8 Mon Sep 17 00:00:00 2001 From: luzadev Date: Mon, 8 Jun 2026 11:30:48 +0200 Subject: [PATCH] Nuova tab Guida: manuale d'uso completo dentro l'app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aggiunta una sezione 'Guida' (icona đź“–, hero indaco) con manuale d'uso interno, organizzato in 10 capitoli con sommario sticky a sinistra: - Introduzione, Setup iniziale - Una sezione per ogni tab (Scarica, Video, Upgrade, Metadati, Registra, Impostazioni) - Aggiornamenti - Troubleshooting con tabella di 8 sintomi/cause/fix - Risorse esterne (GitHub, yt-dlp FAQ, BlackHole) UX: - Layout grid 200px (TOC) + content fluido. Su schermi <900px il TOC diventa una row in alto - TOC sticky con highlight del link attivo via IntersectionObserver - Link esterni (data-ext) aperti nel browser di sistema via open_external_url - Hint nella tab Registra ora apre la sezione 'Tab Registra' della guida invece del README su GitHub - Box info/success/warn riusabili, stile verde,
  monospace, tabella troubleshooting

CSS:
- Nuova palette indigo + hero-indigo
- Stili .guide-layout, .guide-toc, .g-section, .g-box, .g-table,
  .kbd, .toc-link.active

Co-Authored-By: Claude Opus 4.7 
---
 webui/css/style.css | 218 +++++++++++++++++++++++++++++++++++
 webui/index.html    | 275 ++++++++++++++++++++++++++++++++++++++++++++
 webui/js/app.js     |  50 +++++++-
 3 files changed, 540 insertions(+), 3 deletions(-)

diff --git a/webui/css/style.css b/webui/css/style.css
index 0979113..c050418 100644
--- a/webui/css/style.css
+++ b/webui/css/style.css
@@ -38,6 +38,10 @@
   --rec-red-hover: #F87171;
   --rec-red-dim: #7F1D1D;
 
+  --indigo: #6366F1;
+  --indigo-hover: #818CF8;
+  --indigo-dim: #312E81;
+
   --red: #E22134;
   --red-hover: #FF4055;
 
@@ -249,6 +253,10 @@ button, input, select, textarea { font-family: inherit; font-size: inherit; }
   background: linear-gradient(135deg, #DC2626 0%, #7F1D1D 60%, #3F0A0A 100%);
 }
 
+.hero-indigo {
+  background: linear-gradient(135deg, #4F46E5 0%, #312E81 60%, #1E1B4B 100%);
+}
+
 .hero-content { position: relative; z-index: 2; max-width: 70%; }
 
 .hero-eyebrow {
@@ -264,6 +272,7 @@ button, input, select, textarea { font-family: inherit; font-size: inherit; }
 .hero-eyebrow.pink { color: var(--pink-hover); }
 .hero-eyebrow.amber { color: var(--amber-hover); }
 .hero-eyebrow.red { color: var(--rec-red-hover); }
+.hero-eyebrow.indigo { color: var(--indigo-hover); }
 
 .hero-title {
   font-size: 36px;
@@ -1038,3 +1047,212 @@ input[type="number"]::-webkit-inner-spin-button {
   font-weight: 700;
   margin-right: 10px;
 }
+
+/* ===============================
+   GUIDE
+   =============================== */
+.guide-layout {
+  display: grid;
+  grid-template-columns: 200px 1fr;
+  gap: 32px;
+  align-items: start;
+}
+
+.guide-toc {
+  position: sticky;
+  top: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  padding: 16px 10px;
+  background: var(--bg-card);
+  border-radius: var(--r-md);
+  max-height: calc(100vh - 80px);
+  overflow-y: auto;
+}
+
+.toc-link {
+  color: var(--text-2);
+  text-decoration: none;
+  padding: 8px 12px;
+  border-radius: var(--r-sm);
+  font-size: 13px;
+  font-weight: 600;
+  transition: background-color 0.15s, color 0.15s;
+}
+
+.toc-link:hover {
+  background: var(--bg-input);
+  color: var(--text);
+}
+
+.toc-link.active {
+  background: var(--indigo-dim);
+  color: var(--indigo-hover);
+}
+
+.guide-content {
+  min-width: 0;  /* evita overflow horizontal */
+  color: var(--text-2);
+  font-size: 14px;
+  line-height: 1.65;
+}
+
+.g-section {
+  background: var(--bg-card);
+  border-radius: var(--r-md);
+  padding: 24px 28px;
+  margin-bottom: 18px;
+  scroll-margin-top: 12px;
+}
+
+.g-section h2 {
+  font-size: 22px;
+  font-weight: 800;
+  color: var(--text);
+  margin-bottom: 10px;
+  letter-spacing: -0.3px;
+}
+
+.g-section h3 {
+  font-size: 15px;
+  font-weight: 800;
+  color: var(--text);
+  margin-top: 22px;
+  margin-bottom: 8px;
+  letter-spacing: 0.2px;
+}
+
+.g-section h4 {
+  font-size: 13px;
+  font-weight: 700;
+  color: var(--indigo-hover);
+  margin-top: 16px;
+  margin-bottom: 6px;
+  text-transform: uppercase;
+  letter-spacing: 0.6px;
+}
+
+.g-section p {
+  margin-bottom: 10px;
+}
+
+.g-section ul,
+.g-section ol {
+  padding-left: 22px;
+  margin-bottom: 12px;
+}
+
+.g-section li {
+  margin-bottom: 6px;
+}
+
+.g-section strong { color: var(--text); }
+
+.g-section code {
+  background: var(--bg-input);
+  padding: 2px 7px;
+  border-radius: 4px;
+  font-family: "SF Mono", Menlo, Consolas, monospace;
+  font-size: 12.5px;
+  color: var(--green-hover);
+}
+
+.g-section pre {
+  background: var(--bg-card-2);
+  border: 1px solid var(--border);
+  border-radius: var(--r-sm);
+  padding: 12px 16px;
+  margin: 10px 0 14px;
+  overflow-x: auto;
+}
+
+.g-section pre code {
+  background: transparent;
+  padding: 0;
+  color: var(--text);
+  font-size: 13px;
+}
+
+.g-section a {
+  color: var(--indigo-hover);
+  text-decoration: none;
+  font-weight: 600;
+  border-bottom: 1px solid transparent;
+}
+
+.g-section a:hover {
+  border-bottom-color: var(--indigo-hover);
+}
+
+.kbd {
+  background: var(--bg-input);
+  border: 1px solid var(--bg-input-hover);
+  color: var(--text);
+  padding: 1px 8px;
+  border-radius: 6px;
+  font-size: 12px;
+  font-weight: 700;
+  white-space: nowrap;
+}
+
+.g-box {
+  padding: 14px 18px;
+  border-radius: var(--r-sm);
+  margin: 14px 0;
+  font-size: 13.5px;
+  line-height: 1.55;
+}
+
+.g-box.info {
+  background: rgba(99, 102, 241, 0.10);
+  border-left: 3px solid var(--indigo);
+}
+
+.g-box.success {
+  background: rgba(29, 185, 84, 0.10);
+  border-left: 3px solid var(--green);
+}
+
+.g-box.warn {
+  background: rgba(253, 203, 110, 0.10);
+  border-left: 3px solid #FDCB6E;
+}
+
+.g-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 12px 0;
+  font-size: 13px;
+}
+
+.g-table th,
+.g-table td {
+  padding: 10px 14px;
+  text-align: left;
+  border-bottom: 1px solid var(--divider);
+  vertical-align: top;
+}
+
+.g-table th {
+  color: var(--text);
+  font-weight: 700;
+  font-size: 11.5px;
+  letter-spacing: 0.5px;
+  text-transform: uppercase;
+  background: var(--bg-card-2);
+}
+
+.g-table tr:last-child td {
+  border-bottom: none;
+}
+
+@media (max-width: 900px) {
+  .guide-layout { grid-template-columns: 1fr; }
+  .guide-toc {
+    position: static;
+    flex-direction: row;
+    flex-wrap: wrap;
+    max-height: none;
+  }
+}
diff --git a/webui/index.html b/webui/index.html
index 136ebdf..6ce2cb8 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -45,6 +45,10 @@
           âš™
           Impostazioni
         
+        
       
 
       
       
 
+      
+      
+
+
+
GUIDA
+

Manuale d'uso

+

Come usare MusicTools al meglio · setup · troubleshooting

+
+
đź“–
+
+ +
+ + + + +
+ +
+

Introduzione

+

MusicTools è una app desktop tutto-in-uno per chi lavora con la musica: scarica brani e video da decine di piattaforme, migliora la qualità dei file esistenti, modifica i tag/metadati e registra l'audio da qualsiasi ingresso del computer.

+

L'interfaccia è organizzata in 6 sezioni accessibili dalla barra laterale: ⬇ Scarica, 🎬 Video, ⚡ Upgrade, 🏷 Metadati, ● Registra, ⚙ Impostazioni.

+
+ +
+

Setup iniziale

+

Prima di usare l'app, conviene fare 3 cose nelle Impostazioni:

+
    +
  1. Cartella output predefinita — dove vengono salvati tutti i download.
  2. +
  3. Credenziali Spotify (solo se vuoi scaricare da link Spotify) — vedi il bottone "Come ottenere le credenziali?" nella tab Impostazioni.
  4. +
  5. cookies.txt (opzionale) — necessario solo per scaricare contenuti privati Instagram/Facebook o aggirare rate-limit YouTube. Esportalo dal browser con l'estensione "Get cookies.txt LOCALLY".
  6. +
+

Tutti i campi vengono salvati automaticamente quando premi Salva Impostazioni.

+
+ +
+

Tab ⬇ Scarica

+

Scarica musica in MP3 al bitrate impostato in Impostazioni (default 320 kbps).

+ +

1 · Da Spotify

+

Incolla un link a una playlist, album o brano (es. open.spotify.com/playlist/...). L'app risolve i metadati via API Spotify e scarica i brani da YouTube. Richiede le credenziali API impostate.

+ +

2 · Da YouTube, SoundCloud e altri

+

Incolla qualsiasi URL supportato da yt-dlp (singolo video o playlist). Funziona anche con Bandcamp, Mixcloud, ecc.

+ +

3 · Lista da file .txt

+

Clicca 📂 Carica lista. Il file può contenere:

+
    +
  • Una URL per riga — l'app le scarica una dopo l'altra.
  • +
  • Tracklist con titoli (es. Beatport Top 100): formato 1. Artista – Titolo (variant) (durata). L'app riconosce automaticamente il formato e cerca ogni brano su YouTube.
  • +
+

Nel caso tracklist, viene creata automaticamente una sottocartella col nome del file (es. Beatport Top 100.txt → Beatport Top 100/).

+ +

4 · Skip duplicati

+

Se riesegui la stessa playlist, i brani giĂ  scaricati vengono saltati in automatico. Il tracking funziona in due modi:

+
    +
  • File .downloaded_tracks dentro la cartella output.
  • +
  • Scan dei file presenti, con match dei nomi token-per-token (ignora parole come "Official Video", "Lyrics", "HD"...).
  • +
+
+ +
+

Tab 🎬 Video

+

Scarica video da YouTube, TikTok, Instagram, Facebook e oltre 1000 altri siti supportati da yt-dlp.

+ +

ModalitĂ 

+
    +
  • 🎬 Video (MP4) — output video+audio in MP4, qualitĂ  a scelta (best / 1080p / 720p / 480p).
  • +
  • 🎵 Solo audio (MP3) — estrae solo l'audio in MP3 al bitrate impostato in Impostazioni, con thumbnail embedded come copertina.
  • +
+ +

Contenuti privati

+

Per scaricare reel Instagram privati, post Facebook privati o aggirare rate-limit YouTube serve un file cookies.txt valido (formato Netscape). Esportalo dal browser e impostalo in Impostazioni → Percorsi.

+ +
+ Esempio: per scaricare la Storia di un utente Instagram seguito, fai login al browser, esporta i cookies, riavvia l'app. +
+
+ +
+

Tab ⚡ Upgrade qualità

+

Scansiona una cartella e riscarica i file sotto la soglia kbps impostata, sostituendoli con la versione a qualitĂ  maggiore.

+
    +
  1. Seleziona la cartella con i file audio.
  2. +
  3. Imposta la Soglia HQ (kbps) — i file con bitrate inferiore verranno riprocessati. Default 310 kbps.
  4. +
  5. (Opzionale) Spunta Includi sottocartelle per la scansione ricorsiva.
  6. +
  7. Premi Avvia Upgrade.
  8. +
+

Per ogni file l'app cerca la versione migliore su YouTube e la sostituisce mantenendo nome originale. I file giĂ  processati vengono saltati grazie al tracking .upgraded_tracks.

+
+ +
+

Tab 🏷 Metadati

+

Editor completo dei tag audio. Apri un file con đź“‚ Apri file audio.

+ +

Formati supportati

+
    +
  • MP3 — ID3v2.3
  • +
  • M4A / AAC / MP4 — Apple atoms
  • +
  • FLAC — Vorbis comments
  • +
  • WAV — ID3 embedded + fallback al chunk RIFF LIST/INFO (per file taggati da ffmpeg/Audacity)
  • +
+ +

Campi editabili

+

Titolo, artista, album, album artist, anno, traccia (formato 1/12), genere, BPM, key musicale, commento, copertina (JPG/PNG), e su macOS anche kMDItemWhereFroms (l'extended attribute che traccia l'origine del file scaricato).

+ +

Copertina

+

Anteprima 180Ă—180 della copertina corrente. Cambia copertina ne carica una nuova; Rimuovi la marca per la rimozione al prossimo salvataggio.

+ +

Annulla modifiche

+

Premi ↺ Ricarica per rileggere i tag dal file e scartare le modifiche non salvate.

+
+ +
+

Tab â—Ź Registra

+

Cattura audio in MP3 da qualsiasi ingresso del computer: mixer, scheda audio esterna, microfono, driver loopback (BlackHole/Soundflower/VB-Cable). Bitrate selezionabile 128–320 kbps.

+ +

Setup standard (mixer/microfono)

+
    +
  1. Dropdown Dispositivo: seleziona la sorgente.
  2. +
  3. Imposta una cartella output e (opzionale) un nome file.
  4. +
  5. Premi ● REC → registrazione, timer in tempo reale.
  6. +
  7. ◼ Stop → file MP3 finalizzato e linkato sotto.
  8. +
+ +

Registrare l'audio di sistema su macOS (Spotify, YouTube...)

+

macOS non espone nativamente l'uscita audio. Serve un driver loopback gratuito, il più diffuso è BlackHole.

+ +

Installazione

+

Apri Terminale e lancia:

+
brew install blackhole-2ch
+

Se subito dopo l'install BlackHole non compare nel dropdown, riavvia il servizio audio:

+
sudo killall coreaudiod
+

(rilancia da solo in pochi secondi, nessun rischio)

+ +

Permesso microfono

+

macOS richiede il permesso "Microfono" per leggere da BlackHole via AVFoundation. Vai su Preferenze di Sistema → Privacy e sicurezza → Microfono e abilita Terminal/MusicTools. Se la voce non c'è, comparirà un popup la prima volta che premi REC.

+ +

Setup A — minimo (non senti l'audio durante)

+
    +
  1. Click icona altoparlante nella barra menu → Output → BlackHole 2ch
  2. +
  3. Fai partire Spotify (non sentirai nulla — l'audio va a BlackHole)
  4. +
  5. Tab Registra → dispositivo 🔄 BlackHole 2ch (loopback) → REC
  6. +
+ +

Setup B — completo (registri E senti l'audio)

+
    +
  1. Apri Configurazione MIDI Audio (Applicazioni → Utility)
  2. +
  3. In basso a sinistra: + → Crea dispositivo aggregato a uscita multipla
  4. +
  5. Spunta sia BlackHole 2ch sia i tuoi Altoparlanti integrati
  6. +
  7. Imposta BlackHole 2ch come Master Device (menu in alto)
  8. +
  9. Abilita Drift Correction sulla riga degli altoparlanti
  10. +
  11. (Opzionale) Rinomina in Speakers + BlackHole
  12. +
  13. Preferenze di Sistema → Audio → Uscita → seleziona Speakers + BlackHole
  14. +
  15. Nella tab Registra: scegli 🔄 BlackHole 2ch (loopback), non il dispositivo aggregato
  16. +
+
+ Adesso ogni audio del sistema viene riprodotto sugli speaker (lo senti) e contemporaneamente catturato da BlackHole (viene registrato). +
+
+ +
+

Impostazioni

+ +

Spotify API

+

Le credenziali API Spotify sono gratuite e non richiedono Premium. Vai su Spotify Developer Dashboard, "Create app", compila i campi (Redirect URI http://localhost:8888/callback, seleziona "Web API"), e copia Client ID + Client Secret nei campi corrispondenti.

+ +

Download

+
    +
  • Bitrate — qualitĂ  target per gli MP3 (128/192/256/320 kbps).
  • +
  • Soglia HQ — soglia kbps per la tab Upgrade. I file sotto questa soglia vengono riscaricati.
  • +
+ +

Percorsi

+
    +
  • cookies.txt — per contenuti privati/protetti.
  • +
  • Cartella output predefinita — viene proposta in tutte le tab.
  • +
+
+ +
+

Aggiornamenti

+

In Impostazioni → Aggiornamenti trovi il bottone Controlla aggiornamenti. L'app interroga la pagina release di GitHub e ti mostra se c'è una versione nuova.

+

Se è disponibile un aggiornamento, comparirà un secondo bottone Scarica aggiornamento che apre direttamente il browser sull'asset corretto (macOS o Windows) della release più recente.

+
+ +
+

Troubleshooting

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SintomoCausa probabileFix
BlackHole non compare nel dropdown della tab RegistraCoreAudio non l'ha ancora caricato dopo l'installsudo killall coreaudiod + premi ↻ Aggiorna
"Permesso microfono mancante" o errore ffmpeg -6macOS nega l'accesso al devicePrivacy e sicurezza → Microfono → abilita Terminal/MusicTools
File MP3 della registrazione è mutoL'output di sistema non passa da BlackHoleImposta output su BlackHole o Multi-Output Device (Setup A/B)
Spotify dice "Autenticazione fallita"Client ID/Secret mancanti o erratiImpostazioni → Spotify API → ripeti la procedura
Instagram/Facebook non scaricaContenuto privato senza cookies validiEsporta cookies.txt dal browser dopo login
Brano scaricato è sbagliato (versione cover invece dell'originale)Ricerca YouTube ha preso il primo risultatoModifica la query nel file di tracklist (più dettagli artista/titolo)
Tag non vengono letti su un WAVFile usa solo chunk INFO non ID3 (raro)L'app ora gestisce entrambi — controlla di avere v1.2.1+
Stop button non risponde durante il downloadyt-dlp sta finalizzando il fileAttendi qualche secondo, il processo si chiude dopo l'I/O
+ +

Reset configurazione

+

Se vuoi azzerare tutte le impostazioni, cancella il file config.json. Posizione:

+
    +
  • macOS: ~/Library/Application Support/MusicTools/config.json
  • +
  • Windows: %APPDATA%\MusicTools\config.json
  • +
+

VerrĂ  ricreato con valori di default al prossimo avvio.

+
+ +
+

Risorse

+ +

MusicTools — by LuZa

+
+ +
+
+
+ diff --git a/webui/js/app.js b/webui/js/app.js index 03cf5a5..d4b6cc1 100644 --- a/webui/js/app.js +++ b/webui/js/app.js @@ -488,11 +488,55 @@ $("#recRefreshBtn").addEventListener("click", refreshRecDevices); $("#recOpenGuide").addEventListener("click", (e) => { e.preventDefault(); - window.pywebview.api.open_external_url( - "https://github.com/luzadev/musicdownload#guida-rapida-registrazione-audio-di-sistema-macos" - ); + showView("guide"); + setTimeout(() => { + const el = document.getElementById("g-record"); + if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); + }, 80); }); +// ============================================================ +// GUIDE — link esterni + TOC active highlight +// ============================================================ +document.querySelectorAll("#view-guide a[data-ext]").forEach((a) => { + a.addEventListener("click", (e) => { + e.preventDefault(); + window.pywebview.api.open_external_url(a.dataset.ext); + }); +}); + +document.querySelectorAll(".toc-link").forEach((a) => { + a.addEventListener("click", (e) => { + e.preventDefault(); + const id = a.getAttribute("href").slice(1); + const el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); + }); +}); + +// Aggiorna TOC active in base allo scroll della view +function setupGuideObserver() { + const sections = document.querySelectorAll(".g-section[id]"); + const tocLinks = document.querySelectorAll(".toc-link"); + if (!sections.length || !tocLinks.length) return; + + const main = document.querySelector(".main"); + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) return; + const id = entry.target.id; + tocLinks.forEach((l) => { + l.classList.toggle("active", l.getAttribute("href") === "#" + id); + }); + }); + }, + { root: main, rootMargin: "-20% 0px -65% 0px", threshold: 0 } + ); + sections.forEach((s) => observer.observe(s)); +} +setupGuideObserver(); + $("#recBrowseBtn").addEventListener("click", async () => { const path = await window.pywebview.api.browse_directory(); if (path) {