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>
1834 lines
85 KiB
HTML
1834 lines
85 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="it">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>MusicTools</title>
|
||
<link rel="stylesheet" href="css/style.css" />
|
||
</head>
|
||
<body>
|
||
<div class="app">
|
||
|
||
<!-- ============== SIDEBAR ============== -->
|
||
<aside class="sidebar">
|
||
<div class="brand">
|
||
<div class="brand-logo">
|
||
<span>♫</span>
|
||
</div>
|
||
<div class="brand-name">MusicTools</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<nav class="nav">
|
||
<div class="nav-section-label">Scarica</div>
|
||
<button class="nav-item active" data-view="download" data-feature="audio">
|
||
<span class="nav-icon">⬇</span>
|
||
<span>Scarica</span>
|
||
</button>
|
||
<button class="nav-item" data-view="video" data-feature="video">
|
||
<span class="nav-icon">🎬</span>
|
||
<span>Video</span>
|
||
</button>
|
||
<button class="nav-item" data-view="spotify" data-feature="audio">
|
||
<span class="nav-icon">🟢</span>
|
||
<span>Spotify</span>
|
||
</button>
|
||
<button class="nav-item" data-view="youtube" data-feature="audio">
|
||
<span class="nav-icon">▶</span>
|
||
<span>YouTube</span>
|
||
</button>
|
||
<button class="nav-item" data-view="beatport" data-feature="audio">
|
||
<span class="nav-icon">🎧</span>
|
||
<span>Beatport</span>
|
||
</button>
|
||
<button class="nav-item" data-view="traxsource" data-feature="audio">
|
||
<span class="nav-icon">▲</span>
|
||
<span>Traxsource</span>
|
||
</button>
|
||
<button class="nav-item" data-view="charts" data-feature="audio">
|
||
<span class="nav-icon">📊</span>
|
||
<span>Charts</span>
|
||
</button>
|
||
|
||
<div class="nav-section-label">Organizza</div>
|
||
<button class="nav-item" data-view="dedup" data-feature="audio">
|
||
<span class="nav-icon">🗑</span>
|
||
<span>Dedup</span>
|
||
</button>
|
||
<button class="nav-item" data-view="catalog" data-feature="audio">
|
||
<span class="nav-icon">📅</span>
|
||
<span>Cataloga</span>
|
||
</button>
|
||
<button class="nav-item" data-view="flatten" data-feature="audio">
|
||
<span class="nav-icon">📤</span>
|
||
<span>Appiattisci</span>
|
||
</button>
|
||
<button class="nav-item" data-view="convert" data-feature="audio">
|
||
<span class="nav-icon">🔄</span>
|
||
<span>Converti</span>
|
||
</button>
|
||
<button class="nav-item" data-view="meta" data-feature="metadata">
|
||
<span class="nav-icon">🏷</span>
|
||
<span>Metadati</span>
|
||
</button>
|
||
<button class="nav-item" data-view="upgrade" data-feature="upgrade">
|
||
<span class="nav-icon">⚡</span>
|
||
<span>Upgrade</span>
|
||
</button>
|
||
|
||
<div class="nav-section-label">Altro</div>
|
||
<button class="nav-item" data-view="record" data-feature="record">
|
||
<span class="nav-icon">●</span>
|
||
<span>Registra</span>
|
||
</button>
|
||
|
||
<div class="nav-section-label">Sistema</div>
|
||
<button class="nav-item" data-view="settings">
|
||
<span class="nav-icon">⚙</span>
|
||
<span>Impostazioni</span>
|
||
</button>
|
||
<button class="nav-item" data-view="guide">
|
||
<span class="nav-icon">📖</span>
|
||
<span>Guida</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="quota-box" id="quotaBox" hidden>
|
||
<div class="quota-label">Oggi</div>
|
||
<div class="quota-value">
|
||
<span id="quotaUsed">0</span>
|
||
<span class="quota-sep">/</span>
|
||
<span id="quotaLimit">10</span>
|
||
</div>
|
||
<div class="quota-plan" id="quotaPlanName">Basic</div>
|
||
<button type="button" class="quota-upgrade" id="quotaUpgradeBtn" hidden>
|
||
Upgrade
|
||
</button>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<div class="footer-by">by LuZa</div>
|
||
<div class="footer-version" id="footerVersion">v1.0</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ============== MAIN ============== -->
|
||
<main class="main">
|
||
|
||
<!-- ===== VIEW: DOWNLOAD ===== -->
|
||
<section class="view active" id="view-download">
|
||
<header class="hero hero-green">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow">DOWNLOAD</div>
|
||
<h1 class="hero-title">Scarica la tua musica</h1>
|
||
<p class="hero-subtitle">Spotify · YouTube · SoundCloud · qualsiasi link supportato da yt-dlp</p>
|
||
</div>
|
||
<div class="hero-deco">♫</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Sorgente</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">
|
||
URL o titolo brano
|
||
<span class="badge" id="urlListBadge" hidden></span>
|
||
</label>
|
||
<div class="row">
|
||
<input type="text" id="urlInput" class="input pill" placeholder="Link Spotify/YouTube… oppure Artista - Titolo · Titolo brano" />
|
||
<button class="btn btn-ghost pill" id="loadListBtn">
|
||
<span>📂</span> Carica lista
|
||
</button>
|
||
</div>
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Puoi incollare un <strong>link</strong> (Spotify, YouTube, SoundCloud…) oppure scrivere direttamente
|
||
<strong>Artista - Titolo</strong> o solo il <strong>titolo del brano</strong>: l'app cerca il match migliore su YouTube e scarica in MP3.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Cartella output</label>
|
||
<div class="row">
|
||
<div class="path-display" id="dlPathDisplay">Nessuna cartella selezionata</div>
|
||
<button class="btn btn-ghost pill btn-sm" id="dlBrowseBtn">Sfoglia</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card actions">
|
||
<button class="btn btn-primary pill btn-lg" id="downloadBtn">
|
||
<span class="ico">▶</span> Avvia Download
|
||
</button>
|
||
<button class="btn btn-danger pill btn-lg" id="stopDownloadBtn" disabled>
|
||
<span class="ico">◼</span> Stop
|
||
</button>
|
||
<div class="counter" id="dlCounter"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="progress-head">
|
||
<span class="micro-label">Progresso</span>
|
||
<span class="percent green" id="dlPercent">0%</span>
|
||
</div>
|
||
<div class="progress">
|
||
<div class="progress-fill green" id="dlProgressFill"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="dlLog"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: VIDEO ===== -->
|
||
<section class="view" id="view-video">
|
||
<header class="hero hero-pink">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow pink">VIDEO</div>
|
||
<h1 class="hero-title">Scarica video</h1>
|
||
<p class="hero-subtitle">YouTube · TikTok · Instagram · Facebook · qualsiasi link supportato</p>
|
||
</div>
|
||
<div class="hero-deco">🎬</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Sorgente</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">URL</label>
|
||
<input type="text" id="videoUrlInput" class="input pill" placeholder="https://youtube.com/watch?v=… · https://tiktok.com/… · https://instagram.com/reel/…" />
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Cartella output</label>
|
||
<div class="row">
|
||
<div class="path-display" id="videoPathDisplay">Nessuna cartella selezionata</div>
|
||
<button class="btn btn-ghost pill btn-sm" id="videoBrowseBtn">Sfoglia</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field inline">
|
||
<label class="field-label">Modalità</label>
|
||
<div class="segmented" id="videoMode">
|
||
<button type="button" class="seg-btn active" data-mode="video">🎬 Video (MP4)</button>
|
||
<button type="button" class="seg-btn" data-mode="audio">🎵 Solo audio (MP3)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field inline" id="videoQualityField">
|
||
<label class="field-label">Qualità</label>
|
||
<select id="videoQuality" class="input pill input-sm">
|
||
<option value="best">Migliore disponibile</option>
|
||
<option value="1080p" selected>1080p</option>
|
||
<option value="720p">720p</option>
|
||
<option value="480p">480p</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
In modalità <strong>Solo audio</strong> il file viene salvato in MP3 al bitrate impostato in Impostazioni → Download.<br>
|
||
Per Instagram/Facebook privati serve un file <strong>cookies.txt</strong> valido (Impostazioni → Percorsi).
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card actions">
|
||
<button class="btn btn-pink pill btn-lg" id="videoDownloadBtn">
|
||
<span class="ico">▶</span> Avvia Download
|
||
</button>
|
||
<button class="btn btn-danger pill btn-lg" id="stopVideoBtn" disabled>
|
||
<span class="ico">◼</span> Stop
|
||
</button>
|
||
<div class="counter" id="videoCounter"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="progress-head">
|
||
<span class="micro-label">Progresso</span>
|
||
<span class="percent pink" id="videoPercent">0%</span>
|
||
</div>
|
||
<div class="progress">
|
||
<div class="progress-fill pink" id="videoProgressFill"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="videoLog"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: UPGRADE ===== -->
|
||
<section class="view" id="view-upgrade">
|
||
<header class="hero hero-blue">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow blue">UPGRADE QUALITÀ</div>
|
||
<h1 class="hero-title">Migliora i tuoi audio</h1>
|
||
<p class="hero-subtitle">Riscarica i file a bitrate più alto. Imposta la soglia minima accettabile.</p>
|
||
</div>
|
||
<div class="hero-deco">⚡</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Cartella audio</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<div class="row">
|
||
<div class="path-display" id="upPathDisplay">Nessuna cartella selezionata</div>
|
||
<button class="btn btn-ghost pill btn-sm" id="upBrowseBtn">📂 Sfoglia</button>
|
||
</div>
|
||
</div>
|
||
|
||
<label class="checkbox">
|
||
<input type="checkbox" id="upRecursive" />
|
||
<span class="check-box"></span>
|
||
<span>Includi sottocartelle</span>
|
||
</label>
|
||
|
||
<div class="info-row" id="upInfo"></div>
|
||
|
||
<div class="field inline">
|
||
<label class="field-label">Soglia HQ (kbps)</label>
|
||
<input type="number" id="upThreshold" class="input pill input-sm" value="310" />
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Cartella archivio (opzionale)</label>
|
||
<div class="row">
|
||
<div class="path-display empty" id="upArchivePathDisplay">Nessuna cartella</div>
|
||
<button class="btn btn-ghost pill btn-sm" id="upArchiveBrowseBtn">📂 Sfoglia</button>
|
||
<button class="btn btn-ghost pill btn-sm" id="upArchiveClearBtn" hidden>Rimuovi</button>
|
||
</div>
|
||
<label style="display:flex;gap:6px;align-items:center;margin-top:8px;font-size:13px;color:var(--text-2);">
|
||
<input type="checkbox" id="upArchiveAutoPick"/>
|
||
<span>Scelta automatica se ci sono più match (usa quello a bitrate più alto)</span>
|
||
</label>
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>Se selezionata, l'app cerca versioni HQ del brano nella tua libreria locale prima di scaricare da YouTube. In caso di match multipli scegli tu quale usare — oppure attiva "Scelta automatica" per non fermarti.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card actions">
|
||
<button class="btn btn-blue pill btn-lg" id="upgradeBtn">
|
||
<span class="ico">▶</span> Avvia Upgrade
|
||
</button>
|
||
<button class="btn btn-danger pill btn-lg" id="stopUpgradeBtn" disabled>
|
||
<span class="ico">◼</span> Stop
|
||
</button>
|
||
<div class="counter" id="upCounter"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="progress-head">
|
||
<span class="micro-label">Progresso</span>
|
||
<span class="percent blue" id="upPercent">0%</span>
|
||
</div>
|
||
<div class="progress">
|
||
<div class="progress-fill blue" id="upProgressFill"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="upLog"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: REGISTRA ===== -->
|
||
<section class="view" id="view-record">
|
||
<header class="hero hero-red">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow red">REGISTRAZIONE</div>
|
||
<h1 class="hero-title">Cattura audio</h1>
|
||
<p class="hero-subtitle">Registra in MP3 320 kbps da qualsiasi ingresso audio (mixer, scheda, microfono, BlackHole/loopback)</p>
|
||
</div>
|
||
<div class="hero-deco">●</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Dispositivo</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">Ingresso audio</label>
|
||
<div class="row">
|
||
<select id="recDevice" class="input pill">
|
||
<option value="">Caricamento…</option>
|
||
</select>
|
||
<button class="btn btn-ghost pill btn-sm" id="recRefreshBtn">↻ Aggiorna</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="hint" id="recHint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
<strong>Per registrare l'audio di sistema (Spotify/YouTube):</strong>
|
||
installa <code>brew install blackhole-2ch</code>, imposta l'uscita audio di macOS su <strong>BlackHole 2ch</strong>, poi seleziona qui sotto <strong>🔄 BlackHole 2ch (loopback)</strong>.
|
||
<br><br>
|
||
Per sentire l'audio mentre registri serve un <strong>Multi-Output Device</strong> in <em>Configurazione MIDI Audio</em> (BlackHole + altoparlanti) — guida completa nel
|
||
<a href="#" id="recOpenGuide">README su GitHub</a>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Destinazione</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">Cartella output</label>
|
||
<div class="row">
|
||
<div class="path-display" id="recPathDisplay">Nessuna cartella selezionata</div>
|
||
<button class="btn btn-ghost pill btn-sm" id="recBrowseBtn">Sfoglia</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Nome file (opzionale)</label>
|
||
<input type="text" id="recFilename" class="input pill" placeholder="Registrazione_AAAAMMGG_HHMMSS.mp3" />
|
||
</div>
|
||
|
||
<div class="field inline">
|
||
<label class="field-label">Bitrate</label>
|
||
<select id="recBitrate" class="input pill input-sm">
|
||
<option value="128k">128 kbps</option>
|
||
<option value="192k">192 kbps</option>
|
||
<option value="256k">256 kbps</option>
|
||
<option value="320k" selected>320 kbps</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card actions">
|
||
<button class="btn btn-red pill btn-lg" id="recStartBtn">
|
||
<span class="ico">●</span> REC
|
||
</button>
|
||
<button class="btn btn-danger pill btn-lg" id="recStopBtn" disabled>
|
||
<span class="ico">◼</span> Stop
|
||
</button>
|
||
<div class="rec-timer" id="recTimer">00:00:00</div>
|
||
<div class="rec-status" id="recStatus"></div>
|
||
</div>
|
||
|
||
<div class="card" id="recOutputCard" hidden>
|
||
<div class="micro-label">Ultima registrazione</div>
|
||
<div class="rec-result" id="recResult"></div>
|
||
</div>
|
||
|
||
<div class="card rec-error-card" id="recErrorCard" hidden>
|
||
<div class="micro-label red">Errore registrazione</div>
|
||
<div class="rec-error-msg" id="recErrorMsg"></div>
|
||
<div class="row" style="gap: 10px; margin-top: 12px;">
|
||
<button class="btn btn-ghost pill btn-sm" id="recShowLogBtn">Mostra log tecnici</button>
|
||
<button class="btn btn-ghost pill btn-sm" id="recCopyLogBtn" hidden>Copia log</button>
|
||
</div>
|
||
<pre class="rec-error-log" id="recErrorLog" hidden></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: METADATI ===== -->
|
||
<section class="view" id="view-meta">
|
||
<header class="hero hero-amber">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow amber">METADATI</div>
|
||
<h1 class="hero-title">Modifica tag audio</h1>
|
||
<p class="hero-subtitle">Titolo · Artista · Album · Anno · Copertina · BPM · Key · MP3/M4A/FLAC</p>
|
||
</div>
|
||
<div class="hero-deco">🏷</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">File</h2>
|
||
<div class="card">
|
||
<div class="row">
|
||
<button class="btn btn-amber pill" id="metaPickBtn">📂 Apri file audio</button>
|
||
<div class="path-display" id="metaPathDisplay">Nessun file selezionato</div>
|
||
<span id="metaAiBadge" class="ai-badge" hidden title="Brano generato con AI"></span>
|
||
</div>
|
||
<div class="meta-info" id="metaInfo" hidden></div>
|
||
</div>
|
||
|
||
<div id="metaForm" hidden>
|
||
<h2 class="section-label">Tag</h2>
|
||
<div class="card">
|
||
<div class="form-grid">
|
||
<div class="field full">
|
||
<label class="field-label">Titolo</label>
|
||
<input type="text" id="metaTitle" class="input pill">
|
||
</div>
|
||
<div class="field full">
|
||
<label class="field-label">Artista</label>
|
||
<input type="text" id="metaArtist" class="input pill">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Album</label>
|
||
<input type="text" id="metaAlbum" class="input pill">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Album Artist</label>
|
||
<input type="text" id="metaAlbumArtist" class="input pill">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Anno</label>
|
||
<input type="text" id="metaYear" class="input pill">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Traccia</label>
|
||
<input type="text" id="metaTrack" class="input pill" placeholder="1/12">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Genere</label>
|
||
<input type="text" id="metaGenre" class="input pill">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">BPM</label>
|
||
<input type="text" id="metaBpm" class="input pill">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Key</label>
|
||
<input type="text" id="metaKey" class="input pill">
|
||
</div>
|
||
<div class="field full">
|
||
<label class="field-label">Commento</label>
|
||
<textarea id="metaComment" class="input textarea" rows="3"></textarea>
|
||
</div>
|
||
<div class="field full">
|
||
<label class="field-label">URL sorgente (WOAS)</label>
|
||
<input type="text" id="metaSourceUrl" class="input pill" placeholder="https://...">
|
||
<div class="cover-hint">Link alla pagina d'origine del brano (es. Suno, YouTube, SoundCloud). Sopravvive alle copie del file.</div>
|
||
</div>
|
||
<div class="field full">
|
||
<label class="field-label">Testo / Prompt (USLT)</label>
|
||
<textarea id="metaLyrics" class="input textarea" rows="8" placeholder="Lyrics del brano o prompt AI utilizzato..."></textarea>
|
||
<div class="cover-hint">Testo o prompt strutturato. Suno lo compila automaticamente al momento della generazione.</div>
|
||
</div>
|
||
<details class="field full raw-frames-details">
|
||
<summary>Metadati avanzati (tutti i frame ID3 grezzi)</summary>
|
||
<table class="raw-frames-table" id="metaRawFrames">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:32px;"><input type="checkbox" id="metaRawSelectAll" title="Seleziona tutti"></th>
|
||
<th>Frame</th>
|
||
<th>Tipo</th>
|
||
<th>Contenuto</th>
|
||
<th style="text-align:right;">Bytes</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
<div class="row" style="margin-top:10px;justify-content:space-between;align-items:center;">
|
||
<span id="metaRawSelectedCount" style="font-size:12px;color:var(--text-3);">0 selezionati</span>
|
||
<button id="metaRawRemoveBtn" class="btn btn-danger pill btn-sm" disabled>
|
||
🗑 Rimuovi selezionati
|
||
</button>
|
||
</div>
|
||
<div class="cover-hint" style="margin-top:8px;">
|
||
I frame standard (TIT2 titolo, TPE1 artista, TALB album, APIC cover…) si gestiscono con i campi sopra.
|
||
Qui rimuovi frame custom come TXXX, GEOB (C2PA), USLT (lyrics) o COMM extra.
|
||
</div>
|
||
</details>
|
||
|
||
<div class="field full" id="metaWhereFromField" hidden>
|
||
<label class="field-label">
|
||
Origine (macOS · kMDItemWhereFroms)
|
||
</label>
|
||
<textarea id="metaWhereFrom" class="input textarea" rows="2" placeholder="https://... (uno per riga)"></textarea>
|
||
<div class="cover-hint">Extended attribute macOS — una URL per riga. Vuoto = rimuove l'attributo.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Copertina</h2>
|
||
<div class="card cover-card">
|
||
<div class="cover-preview" id="metaCoverPreview">
|
||
<span class="cover-empty">Nessuna copertina</span>
|
||
</div>
|
||
<div class="cover-actions">
|
||
<button class="btn btn-ghost pill" id="metaCoverBtn">📂 Cambia copertina</button>
|
||
<button class="btn btn-ghost pill" id="metaCoverRemoveBtn">✕ Rimuovi</button>
|
||
<div class="cover-hint" id="metaCoverHint"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card actions">
|
||
<button class="btn btn-amber pill btn-lg" id="metaSaveBtn">💾 Salva tag</button>
|
||
<button class="btn btn-ghost pill btn-lg" id="metaReloadBtn">↺ Ricarica</button>
|
||
<div class="counter" id="metaStatus"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: SETTINGS ===== -->
|
||
<section class="view" id="view-settings">
|
||
<header class="hero hero-purple">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow purple">IMPOSTAZIONI</div>
|
||
<h1 class="hero-title">Configurazione</h1>
|
||
<p class="hero-subtitle">Credenziali Spotify, qualità audio, percorsi e aggiornamenti.</p>
|
||
</div>
|
||
<div class="hero-deco">⚙</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Spotify API</h2>
|
||
<div class="card card-accent-purple">
|
||
<div class="row gap">
|
||
<button class="btn btn-purple pill" id="showGuideBtn">Come ottenere le credenziali?</button>
|
||
<button class="btn btn-ghost pill" id="openDashboardBtn">Apri Spotify Dashboard</button>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Client ID</label>
|
||
<input type="text" id="clientIdInput" class="input pill" placeholder="Inserisci Client ID" />
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Client Secret</label>
|
||
<div class="row">
|
||
<input type="password" id="clientSecretInput" class="input pill" placeholder="Inserisci Client Secret" />
|
||
<button class="btn btn-ghost pill btn-sm" id="toggleSecretBtn">Mostra</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Download</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">Bitrate</label>
|
||
<select id="bitrateSelect" class="input pill input-sm">
|
||
<option value="128K">128 kbps</option>
|
||
<option value="192K">192 kbps</option>
|
||
<option value="256K">256 kbps</option>
|
||
<option value="320K" selected>320 kbps</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Soglia HQ (kbps)</label>
|
||
<input type="number" id="hqThresholdInput" class="input pill input-sm" placeholder="310" />
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Percorsi</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">Percorso cookies.txt</label>
|
||
<div class="row">
|
||
<input type="text" id="cookiesInput" class="input pill" placeholder="cookies.txt" />
|
||
<button class="btn btn-ghost pill btn-sm" id="browseCookiesBtn">Sfoglia</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Cookies da browser (bypass 403 YouTube)</label>
|
||
<div class="row">
|
||
<select id="cookiesBrowserSelect" class="input pill" style="max-width:220px;">
|
||
<option value="">Nessuno</option>
|
||
<option value="chrome">Chrome</option>
|
||
<option value="safari">Safari</option>
|
||
<option value="firefox">Firefox</option>
|
||
<option value="edge">Edge</option>
|
||
<option value="brave">Brave</option>
|
||
</select>
|
||
</div>
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>Alcuni video YouTube (musica protetta, region-lock, età) danno HTTP 403 senza cookies autenticati. Selezionando un browser, l'app legge i cookies dal tuo profilo — richiede che tu sia loggato su YouTube in quel browser. Se hai anche cookies.txt sopra, ha priorità il file.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">Cartella output predefinita</label>
|
||
<div class="row">
|
||
<input type="text" id="outputInput" class="input pill" placeholder="MUSICA/" />
|
||
<button class="btn btn-ghost pill btn-sm" id="browseOutputBtn">Sfoglia</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">Cartella dei log</label>
|
||
<div class="row">
|
||
<div class="path-display" id="logPathDisplay" style="flex:1;">—</div>
|
||
<button class="btn btn-ghost pill btn-sm" id="openLogFolderBtn">📁 Apri cartella</button>
|
||
</div>
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
L'app scrive un log giornaliero (<code>app.log</code>) con rotazione automatica (7 giorni).
|
||
Utile per debug: incluide stdout, stderr, log delle tab e traceback.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Aspetto</h2>
|
||
<div class="card">
|
||
<div class="field">
|
||
<label class="field-label">Tema</label>
|
||
<select id="themeSelect" class="input pill input-sm">
|
||
<option value="dark">Dark</option>
|
||
<option value="light">Light</option>
|
||
<option value="system">System</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Licenza</h2>
|
||
<div class="card">
|
||
<div class="license-box" id="licenseStatusBox"></div>
|
||
<div class="license-actions">
|
||
<button class="btn btn-ghost pill" id="revalidateLicenseBtn">Verifica ora</button>
|
||
<button class="btn btn-danger pill" id="deactivateLicenseBtn">Disattiva su questo device</button>
|
||
</div>
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Disattivando liberi uno slot di attivazione, utile se sposti l'app su un altro Mac/PC.
|
||
Puoi riattivare in qualsiasi momento con la stessa email e chiave.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Aggiornamenti</h2>
|
||
<div class="card">
|
||
<div class="row spread">
|
||
<div class="version-line">
|
||
Versione corrente: <span class="strong" id="currentVersionLabel">v1.0</span>
|
||
</div>
|
||
<button class="btn btn-purple pill" id="checkUpdateBtn">Controlla aggiornamenti</button>
|
||
</div>
|
||
<div class="update-status" id="updateStatus"></div>
|
||
<button class="btn btn-success pill" id="downloadUpdateBtn" hidden>Scarica aggiornamento</button>
|
||
<div class="update-notes" id="updateNotes"></div>
|
||
</div>
|
||
|
||
<div class="save-row">
|
||
<button class="btn btn-primary pill btn-lg" id="saveBtn">Salva Impostazioni</button>
|
||
<div class="save-status" id="saveStatus"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: GUIDA ===== -->
|
||
<section class="view" id="view-guide">
|
||
<header class="hero hero-indigo">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow indigo">GUIDA</div>
|
||
<h1 class="hero-title">Manuale d'uso</h1>
|
||
<p class="hero-subtitle">Come usare MusicTools al meglio · setup · troubleshooting</p>
|
||
</div>
|
||
<div class="hero-deco">📖</div>
|
||
</header>
|
||
|
||
<div class="guide-layout">
|
||
<!-- TOC sticky -->
|
||
<nav class="guide-toc">
|
||
<div class="micro-label" style="margin-bottom: 8px;">Sommario</div>
|
||
<a href="#g-intro" class="toc-link">Introduzione</a>
|
||
<a href="#g-setup" class="toc-link">Setup iniziale</a>
|
||
<a href="#g-download" class="toc-link">Tab Scarica</a>
|
||
<a href="#g-video" class="toc-link">Tab Video</a>
|
||
<a href="#g-upgrade" class="toc-link">Tab Upgrade</a>
|
||
<a href="#g-meta" class="toc-link">Tab Metadati</a>
|
||
<a href="#g-record" class="toc-link">Tab Registra</a>
|
||
<a href="#g-settings" class="toc-link">Impostazioni</a>
|
||
<a href="#g-update" class="toc-link">Aggiornamenti</a>
|
||
<a href="#g-troubleshoot" class="toc-link">Troubleshooting</a>
|
||
</nav>
|
||
|
||
<!-- Content -->
|
||
<article class="guide-content">
|
||
|
||
<section id="g-intro" class="g-section">
|
||
<h2>Introduzione</h2>
|
||
<p><strong>MusicTools</strong> è 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.</p>
|
||
<p>L'interfaccia è organizzata in 6 sezioni accessibili dalla barra laterale: <span class="kbd">⬇ Scarica</span>, <span class="kbd">🎬 Video</span>, <span class="kbd">⚡ Upgrade</span>, <span class="kbd">🏷 Metadati</span>, <span class="kbd">● Registra</span>, <span class="kbd">⚙ Impostazioni</span>.</p>
|
||
</section>
|
||
|
||
<section id="g-setup" class="g-section">
|
||
<h2>Setup iniziale</h2>
|
||
<p>Prima di usare l'app, conviene fare 3 cose nelle <strong>Impostazioni</strong>:</p>
|
||
<ol>
|
||
<li><strong>Cartella output predefinita</strong> — dove vengono salvati tutti i download.</li>
|
||
<li><strong>Credenziali Spotify</strong> (solo se vuoi scaricare da link Spotify) — vedi il bottone "Come ottenere le credenziali?" nella tab Impostazioni.</li>
|
||
<li><strong>cookies.txt</strong> (opzionale) — necessario solo per scaricare contenuti privati Instagram/Facebook o aggirare rate-limit YouTube. Esportalo dal browser con l'estensione "Get cookies.txt LOCALLY".</li>
|
||
</ol>
|
||
<p>Tutti i campi vengono salvati automaticamente quando premi <strong>Salva Impostazioni</strong>.</p>
|
||
</section>
|
||
|
||
<section id="g-download" class="g-section">
|
||
<h2>Tab ⬇ Scarica</h2>
|
||
<p>Scarica musica in MP3 al bitrate impostato in Impostazioni (default 320 kbps).</p>
|
||
|
||
<h3>1 · Da Spotify</h3>
|
||
<p>Incolla un link a una <strong>playlist, album o brano</strong> (es. <code>open.spotify.com/playlist/...</code>). L'app risolve i metadati via API Spotify e scarica i brani da YouTube. <strong>Richiede</strong> le credenziali API impostate.</p>
|
||
|
||
<h3>2 · Da YouTube, SoundCloud e altri</h3>
|
||
<p>Incolla qualsiasi URL supportato da yt-dlp (singolo video o playlist). Funziona anche con Bandcamp, Mixcloud, ecc.</p>
|
||
|
||
<h3>3 · Lista da file .txt</h3>
|
||
<p>Clicca <strong>📂 Carica lista</strong>. Il file può contenere:</p>
|
||
<ul>
|
||
<li><strong>Una URL per riga</strong> — l'app le scarica una dopo l'altra.</li>
|
||
<li><strong>Tracklist con titoli</strong> (es. Beatport Top 100): formato <code>1. Artista – Titolo (variant) (durata)</code>. L'app riconosce automaticamente il formato e cerca ogni brano su YouTube.</li>
|
||
</ul>
|
||
<p>Nel caso tracklist, viene creata automaticamente una <strong>sottocartella</strong> col nome del file (es. <code>Beatport Top 100.txt</code> → <code>Beatport Top 100/</code>).</p>
|
||
|
||
<h3>4 · Skip duplicati</h3>
|
||
<p>Se riesegui la stessa playlist, i brani già scaricati vengono saltati in automatico. Il tracking funziona in due modi:</p>
|
||
<ul>
|
||
<li>File <code>.downloaded_tracks</code> dentro la cartella output.</li>
|
||
<li>Scan dei file presenti, con match dei nomi token-per-token (ignora parole come "Official Video", "Lyrics", "HD"...).</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section id="g-video" class="g-section">
|
||
<h2>Tab 🎬 Video</h2>
|
||
<p>Scarica video da <strong>YouTube, TikTok, Instagram, Facebook</strong> e oltre 1000 altri siti supportati da yt-dlp.</p>
|
||
|
||
<h3>Modalità</h3>
|
||
<ul>
|
||
<li><strong>🎬 Video (MP4)</strong> — output video+audio in MP4, qualità a scelta (best / 1080p / 720p / 480p).</li>
|
||
<li><strong>🎵 Solo audio (MP3)</strong> — estrae solo l'audio in MP3 al bitrate impostato in Impostazioni, con thumbnail embedded come copertina.</li>
|
||
</ul>
|
||
|
||
<h3>Contenuti privati</h3>
|
||
<p>Per scaricare reel Instagram privati, post Facebook privati o aggirare rate-limit YouTube serve un file <strong><code>cookies.txt</code></strong> valido (formato Netscape). Esportalo dal browser e impostalo in <strong>Impostazioni → Percorsi</strong>.</p>
|
||
|
||
<div class="g-box info">
|
||
<strong>Esempio:</strong> per scaricare la <em>Storia</em> di un utente Instagram seguito, fai login al browser, esporta i cookies, riavvia l'app.
|
||
</div>
|
||
</section>
|
||
|
||
<section id="g-upgrade" class="g-section">
|
||
<h2>Tab ⚡ Upgrade qualità</h2>
|
||
<p>Scansiona una cartella e <strong>riscarica i file sotto la soglia kbps</strong> impostata, sostituendoli con la versione a qualità maggiore.</p>
|
||
<ol>
|
||
<li>Seleziona la cartella con i file audio.</li>
|
||
<li>Imposta la <strong>Soglia HQ (kbps)</strong> — i file con bitrate inferiore verranno riprocessati. Default 310 kbps.</li>
|
||
<li>(Opzionale) Spunta <strong>Includi sottocartelle</strong> per la scansione ricorsiva.</li>
|
||
<li>Premi <strong>Avvia Upgrade</strong>.</li>
|
||
</ol>
|
||
<p>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 <code>.upgraded_tracks</code>.</p>
|
||
</section>
|
||
|
||
<section id="g-meta" class="g-section">
|
||
<h2>Tab 🏷 Metadati</h2>
|
||
<p>Editor completo dei tag audio. Apri un file con <strong>📂 Apri file audio</strong>.</p>
|
||
|
||
<h3>Formati supportati</h3>
|
||
<ul>
|
||
<li><strong>MP3</strong> — ID3v2.3</li>
|
||
<li><strong>M4A / AAC / MP4</strong> — Apple atoms</li>
|
||
<li><strong>FLAC</strong> — Vorbis comments</li>
|
||
<li><strong>WAV</strong> — ID3 embedded + fallback al chunk RIFF LIST/INFO (per file taggati da ffmpeg/Audacity)</li>
|
||
</ul>
|
||
|
||
<h3>Campi editabili</h3>
|
||
<p>Titolo, artista, album, album artist, anno, traccia (formato <code>1/12</code>), genere, BPM, key musicale, commento, copertina (JPG/PNG), e su macOS anche <strong>kMDItemWhereFroms</strong> (l'extended attribute che traccia l'origine del file scaricato).</p>
|
||
|
||
<h3>Copertina</h3>
|
||
<p>Anteprima 180×180 della copertina corrente. <strong>Cambia copertina</strong> ne carica una nuova; <strong>Rimuovi</strong> la marca per la rimozione al prossimo salvataggio.</p>
|
||
|
||
<h3>Annulla modifiche</h3>
|
||
<p>Premi <strong>↺ Ricarica</strong> per rileggere i tag dal file e scartare le modifiche non salvate.</p>
|
||
</section>
|
||
|
||
<section id="g-record" class="g-section">
|
||
<h2>Tab ● Registra</h2>
|
||
<p>Cattura audio in MP3 da qualsiasi ingresso del computer: <strong>mixer, scheda audio esterna, microfono, driver loopback</strong> (BlackHole/Soundflower/VB-Cable). Bitrate selezionabile 128–320 kbps.</p>
|
||
|
||
<h3>Setup standard (mixer/microfono)</h3>
|
||
<ol>
|
||
<li>Dropdown <strong>Dispositivo</strong>: seleziona la sorgente.</li>
|
||
<li>Imposta una cartella output e (opzionale) un nome file.</li>
|
||
<li>Premi <strong>● REC</strong> → registrazione, timer in tempo reale.</li>
|
||
<li><strong>◼ Stop</strong> → file MP3 finalizzato e linkato sotto.</li>
|
||
</ol>
|
||
|
||
<h3>Registrare l'audio di sistema su macOS (Spotify, YouTube...)</h3>
|
||
<p>macOS non espone nativamente l'uscita audio. Serve un driver loopback gratuito, il più diffuso è <strong>BlackHole</strong>.</p>
|
||
|
||
<h4>Installazione</h4>
|
||
<p>Apri Terminale e lancia:</p>
|
||
<pre><code>brew install blackhole-2ch</code></pre>
|
||
<p>Se subito dopo l'install BlackHole non compare nel dropdown, riavvia il servizio audio:</p>
|
||
<pre><code>sudo killall coreaudiod</code></pre>
|
||
<p>(rilancia da solo in pochi secondi, nessun rischio)</p>
|
||
|
||
<h4>Permesso microfono</h4>
|
||
<p>macOS richiede il permesso "Microfono" per leggere da BlackHole via AVFoundation. Vai su <strong>Preferenze di Sistema → Privacy e sicurezza → Microfono</strong> e abilita Terminal/MusicTools. Se la voce non c'è, comparirà un popup la prima volta che premi REC.</p>
|
||
|
||
<h4>Setup A — minimo (non senti l'audio durante)</h4>
|
||
<ol>
|
||
<li>Click icona altoparlante nella barra menu → <strong>Output → BlackHole 2ch</strong></li>
|
||
<li>Fai partire Spotify (non sentirai nulla — l'audio va a BlackHole)</li>
|
||
<li>Tab Registra → dispositivo <strong>🔄 BlackHole 2ch (loopback)</strong> → REC</li>
|
||
</ol>
|
||
|
||
<h4>Setup B — completo (registri E senti l'audio)</h4>
|
||
<ol>
|
||
<li>Apri <strong>Configurazione MIDI Audio</strong> (<code>Applicazioni → Utility</code>)</li>
|
||
<li>In basso a sinistra: <strong>+ → Crea dispositivo aggregato a uscita multipla</strong></li>
|
||
<li>Spunta sia <strong>BlackHole 2ch</strong> sia i tuoi <strong>Altoparlanti integrati</strong></li>
|
||
<li>Imposta <strong>BlackHole 2ch come Master Device</strong> (menu in alto)</li>
|
||
<li>Abilita <strong>Drift Correction</strong> sulla riga degli altoparlanti</li>
|
||
<li>(Opzionale) Rinomina in <code>Speakers + BlackHole</code></li>
|
||
<li>Preferenze di Sistema → Audio → Uscita → seleziona <code>Speakers + BlackHole</code></li>
|
||
<li>Nella tab Registra: scegli <strong>🔄 BlackHole 2ch (loopback)</strong>, non il dispositivo aggregato</li>
|
||
</ol>
|
||
<div class="g-box success">
|
||
Adesso ogni audio del sistema viene riprodotto sugli speaker (lo senti) e contemporaneamente catturato da BlackHole (viene registrato).
|
||
</div>
|
||
</section>
|
||
|
||
<section id="g-settings" class="g-section">
|
||
<h2>Impostazioni</h2>
|
||
|
||
<h3>Spotify API</h3>
|
||
<p>Le credenziali API Spotify sono <strong>gratuite</strong> e non richiedono Premium. Vai su <a href="#" data-ext="https://developer.spotify.com/dashboard">Spotify Developer Dashboard</a>, "Create app", compila i campi (Redirect URI <code>http://localhost:8888/callback</code>, seleziona "Web API"), e copia Client ID + Client Secret nei campi corrispondenti.</p>
|
||
|
||
<h3>Download</h3>
|
||
<ul>
|
||
<li><strong>Bitrate</strong> — qualità target per gli MP3 (128/192/256/320 kbps).</li>
|
||
<li><strong>Soglia HQ</strong> — soglia kbps per la tab Upgrade. I file sotto questa soglia vengono riscaricati.</li>
|
||
</ul>
|
||
|
||
<h3>Percorsi</h3>
|
||
<ul>
|
||
<li><strong>cookies.txt</strong> — per contenuti privati/protetti.</li>
|
||
<li><strong>Cartella output predefinita</strong> — viene proposta in tutte le tab.</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section id="g-update" class="g-section">
|
||
<h2>Aggiornamenti</h2>
|
||
<p>In <strong>Impostazioni → Aggiornamenti</strong> trovi il bottone <strong>Controlla aggiornamenti</strong>. L'app interroga la pagina release di GitHub e ti mostra se c'è una versione nuova.</p>
|
||
<p>Se è disponibile un aggiornamento, comparirà un secondo bottone <strong>Scarica aggiornamento</strong> che apre direttamente il browser sull'asset corretto (macOS o Windows) della release più recente.</p>
|
||
</section>
|
||
|
||
<section id="g-troubleshoot" class="g-section">
|
||
<h2>Troubleshooting</h2>
|
||
|
||
<table class="g-table">
|
||
<thead><tr><th>Sintomo</th><th>Causa probabile</th><th>Fix</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>BlackHole non compare nel dropdown della tab Registra</td>
|
||
<td>CoreAudio non l'ha ancora caricato dopo l'install</td>
|
||
<td><code>sudo killall coreaudiod</code> + premi <strong>↻ Aggiorna</strong></td>
|
||
</tr>
|
||
<tr>
|
||
<td>"Permesso microfono mancante" o errore ffmpeg -6</td>
|
||
<td>macOS nega l'accesso al device</td>
|
||
<td>Privacy e sicurezza → Microfono → abilita Terminal/MusicTools</td>
|
||
</tr>
|
||
<tr>
|
||
<td>File MP3 della registrazione è muto</td>
|
||
<td>L'output di sistema non passa da BlackHole</td>
|
||
<td>Imposta output su BlackHole o Multi-Output Device (Setup A/B)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Spotify dice "Autenticazione fallita"</td>
|
||
<td>Client ID/Secret mancanti o errati</td>
|
||
<td>Impostazioni → Spotify API → ripeti la procedura</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Instagram/Facebook non scarica</td>
|
||
<td>Contenuto privato senza cookies validi</td>
|
||
<td>Esporta cookies.txt dal browser dopo login</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Brano scaricato è sbagliato (versione cover invece dell'originale)</td>
|
||
<td>Ricerca YouTube ha preso il primo risultato</td>
|
||
<td>Modifica la query nel file di tracklist (più dettagli artista/titolo)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Tag non vengono letti su un WAV</td>
|
||
<td>File usa solo chunk INFO non ID3 (raro)</td>
|
||
<td>L'app ora gestisce entrambi — controlla di avere v1.2.1+</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stop button non risponde durante il download</td>
|
||
<td>yt-dlp sta finalizzando il file</td>
|
||
<td>Attendi qualche secondo, il processo si chiude dopo l'I/O</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>Reset configurazione</h3>
|
||
<p>Se vuoi azzerare tutte le impostazioni, cancella il file <code>config.json</code>. Posizione:</p>
|
||
<ul>
|
||
<li><strong>macOS</strong>: <code>~/Library/Application Support/MusicTools/config.json</code></li>
|
||
<li><strong>Windows</strong>: <code>%APPDATA%\MusicTools\config.json</code></li>
|
||
</ul>
|
||
<p>Verrà ricreato con valori di default al prossimo avvio.</p>
|
||
</section>
|
||
|
||
<section class="g-section">
|
||
<h2>Risorse</h2>
|
||
<ul>
|
||
<li><a href="#" data-ext="https://github.com/luzadev/musicdownload">Codice sorgente su GitHub</a></li>
|
||
<li><a href="#" data-ext="https://github.com/luzadev/musicdownload/releases">Tutte le release</a></li>
|
||
<li><a href="#" data-ext="https://github.com/yt-dlp/yt-dlp/wiki/FAQ">FAQ di yt-dlp</a></li>
|
||
<li><a href="#" data-ext="https://existential.audio/blackhole/">BlackHole — driver loopback macOS</a></li>
|
||
</ul>
|
||
<p style="color: var(--text-3); font-size: 12px; margin-top: 20px;">MusicTools — by <strong style="color: var(--green);">LuZa</strong></p>
|
||
</section>
|
||
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: BEATPORT ===== -->
|
||
<section class="view" id="view-beatport">
|
||
<header class="hero hero-indigo">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow indigo">BEATPORT CHARTS</div>
|
||
<h1 class="hero-title">Top 100 per genere</h1>
|
||
<p class="hero-subtitle">Sfoglia la Top 100 di Beatport, seleziona i brani e scaricali via YouTube</p>
|
||
</div>
|
||
<div class="hero-deco">🎧</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Genere</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<select id="beatport-genre" class="input pill"></select>
|
||
<button class="btn btn-primary pill" id="beatport-load-btn">
|
||
<span class="ico">↓</span> Carica Top 100
|
||
</button>
|
||
<span class="hint-inline">Shift+click per bypassare la cache</span>
|
||
</div>
|
||
<div id="beatport-status" class="beatport-status"></div>
|
||
<div id="beatport-output-info" class="beatport-output-info"></div>
|
||
</div>
|
||
|
||
<div class="card beatport-table-card">
|
||
<div class="beatport-table-wrap">
|
||
<table id="beatport-table" class="beatport-table" hidden>
|
||
<thead>
|
||
<tr>
|
||
<th class="col-check"><input type="checkbox" id="beatport-select-all" /></th>
|
||
<th class="col-cover"></th>
|
||
<th class="col-pos sortable" data-sort="position">#</th>
|
||
<th class="sortable" data-sort="artists">Artista</th>
|
||
<th class="sortable" data-sort="title">Titolo (Mix)</th>
|
||
<th class="col-dur sortable" data-sort="duration_sec">Durata</th>
|
||
<th class="col-date sortable" data-sort="release_date">Data</th>
|
||
<th class="col-state">Stato</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="beatport-toolbar" id="beatport-toolbar" hidden>
|
||
<span class="counter" id="beatport-selected-count">0/0 selezionati</span>
|
||
<button class="btn btn-primary pill" id="beatport-download-btn" disabled>
|
||
<span class="ico">▶</span> Scarica selezionati
|
||
</button>
|
||
<button class="btn btn-danger pill" id="beatport-stop-btn" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="beatport-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: TRAXSOURCE ===== -->
|
||
<section class="view" id="view-traxsource">
|
||
<header class="hero hero-purple">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow purple">TRAXSOURCE CHARTS</div>
|
||
<h1 class="hero-title">Top 100 Traxsource per genere</h1>
|
||
<p class="hero-subtitle">La classifica mensile ufficiale. Scarica in un click con tag ID3 completi (label, cover art, genere).</p>
|
||
</div>
|
||
<div class="hero-deco">▲</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Genere</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<select id="traxsource-genre" class="input pill"></select>
|
||
<button class="btn btn-primary pill" id="traxsource-load-btn">
|
||
<span class="ico">↓</span> Carica Top 100
|
||
</button>
|
||
<span class="hint-inline">Shift+click per bypassare la cache</span>
|
||
</div>
|
||
<div id="traxsource-status" class="beatport-status"></div>
|
||
<div id="traxsource-output-info" class="beatport-output-info"></div>
|
||
</div>
|
||
|
||
<div class="card beatport-table-card">
|
||
<div class="beatport-table-wrap">
|
||
<table id="traxsource-table" class="beatport-table" hidden>
|
||
<thead>
|
||
<tr>
|
||
<th class="col-check"><input type="checkbox" id="traxsource-select-all" /></th>
|
||
<th class="col-cover"></th>
|
||
<th class="col-pos sortable" data-sort="position">#</th>
|
||
<th class="sortable" data-sort="artists">Artista</th>
|
||
<th class="sortable" data-sort="title">Titolo (Mix)</th>
|
||
<th class="sortable" data-sort="label">Label</th>
|
||
<th class="col-state">Stato</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="beatport-toolbar" id="traxsource-toolbar" hidden>
|
||
<span class="counter" id="traxsource-selected-count">0/0 selezionati</span>
|
||
<button class="btn btn-primary pill" id="traxsource-download-btn" disabled>
|
||
<span class="ico">▶</span> Scarica selezionati
|
||
</button>
|
||
<button class="btn btn-danger pill" id="traxsource-stop-btn" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="traxsource-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: SPOTIFY SEARCH ===== -->
|
||
<section class="view" id="view-spotify">
|
||
<header class="hero hero-indigo">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow indigo">RICERCA SPOTIFY</div>
|
||
<h1 class="hero-title">Cerca brani su Spotify</h1>
|
||
<p class="hero-subtitle">Digita un artista, un titolo, o entrambi. Attiva "Solo artista" per scaricare tutta la discografia.</p>
|
||
</div>
|
||
<div class="hero-deco">🟢</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Ricerca</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<input type="text" id="spotify-query" class="input pill" placeholder="Es: Solomun, Hot Sauce, Kapuchon Hot Sauce…" style="flex:1; min-width:260px;" />
|
||
<label style="display:flex; gap:6px; align-items:center; color:var(--text-2); font-size:13px;">
|
||
<input type="checkbox" id="spotify-artist-mode" />
|
||
<span>Solo artista (discografia)</span>
|
||
</label>
|
||
<button class="btn btn-primary pill" id="spotify-search-btn">
|
||
<span class="ico">🔎</span> Cerca
|
||
</button>
|
||
</div>
|
||
<div id="spotify-status" class="beatport-status"></div>
|
||
<div id="spotify-output-info" class="beatport-output-info"></div>
|
||
</div>
|
||
|
||
<div class="card beatport-table-card" id="spotify-table-wrap" hidden>
|
||
<div class="beatport-table-wrap">
|
||
<table class="beatport-table" id="spotify-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="col-check"><input type="checkbox" id="spotify-select-all" checked /></th>
|
||
<th class="col-cover"></th>
|
||
<th class="col-pos sortable" data-sort="position">#</th>
|
||
<th class="sortable" data-sort="artists">Artista</th>
|
||
<th class="sortable" data-sort="name">Titolo</th>
|
||
<th class="sortable" data-sort="album">Album</th>
|
||
<th class="col-dur sortable" data-sort="duration_sec">Durata</th>
|
||
<th class="col-date sortable" data-sort="release_date">Data</th>
|
||
<th class="col-state"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="spotify-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="beatport-toolbar" id="spotify-toolbar">
|
||
<span class="counter" id="spotify-selected-count">0/0 selezionati</span>
|
||
<button class="btn btn-primary pill" id="spotify-download-btn" disabled>
|
||
<span class="ico">▶</span> Scarica selezionati
|
||
</button>
|
||
<button class="btn btn-danger pill" id="spotify-stop-btn" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="spotify-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: YOUTUBE SEARCH ===== -->
|
||
<section class="view" id="view-youtube">
|
||
<header class="hero hero-indigo">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow indigo">RICERCA YOUTUBE</div>
|
||
<h1 class="hero-title">Cerca video su YouTube</h1>
|
||
<p class="hero-subtitle">Perfetto per mix DJ, unreleased, bootleg e brani non presenti su Spotify.</p>
|
||
</div>
|
||
<div class="hero-deco">▶</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Ricerca</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<input type="text" id="youtube-query" class="input pill" placeholder="Es: Solomun Cocoricò 2024, Kapuchon Hot Sauce remix…" style="flex:1; min-width:260px;" />
|
||
<button class="btn btn-primary pill" id="youtube-search-btn">
|
||
<span class="ico">🔎</span> Cerca
|
||
</button>
|
||
</div>
|
||
<div id="youtube-status" class="beatport-status"></div>
|
||
<div id="youtube-output-info" class="beatport-output-info"></div>
|
||
</div>
|
||
|
||
<div class="card beatport-table-card" id="youtube-table-wrap" hidden>
|
||
<div class="beatport-table-wrap">
|
||
<table class="beatport-table" id="youtube-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="col-check"><input type="checkbox" id="youtube-select-all" checked /></th>
|
||
<th class="col-cover"></th>
|
||
<th class="col-pos sortable" data-sort="position">#</th>
|
||
<th class="sortable" data-sort="title">Titolo video</th>
|
||
<th class="sortable" data-sort="channel">Canale</th>
|
||
<th class="col-dur sortable" data-sort="duration_sec">Durata</th>
|
||
<th class="col-date sortable" data-sort="release_date">Data</th>
|
||
<th class="col-state"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="youtube-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="beatport-toolbar" id="youtube-toolbar">
|
||
<span class="counter" id="youtube-selected-count">0/0 selezionati</span>
|
||
<button class="btn btn-primary pill" id="youtube-download-btn" disabled>
|
||
<span class="ico">▶</span> Scarica selezionati
|
||
</button>
|
||
<button class="btn btn-danger pill" id="youtube-stop-btn" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="youtube-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: CONVERTI (WAV -> MP3) ===== -->
|
||
<section class="view" id="view-convert">
|
||
<header class="hero hero-purple">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow purple">WAV → MP3</div>
|
||
<h1 class="hero-title">Converti file WAV in MP3</h1>
|
||
<p class="hero-subtitle">Bitrate configurabile, CBR o VBR, output accanto al file o in cartella custom.</p>
|
||
</div>
|
||
<div class="hero-deco">🔄</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Sorgente</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<button id="convert-pick-files" class="btn btn-primary pill">📄 Scegli file .wav</button>
|
||
<button id="convert-pick-folder" class="btn btn-ghost pill">📁 Scegli cartella</button>
|
||
<button id="convert-clear" class="btn btn-ghost pill">Svuota</button>
|
||
</div>
|
||
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
Bitrate:
|
||
<select id="convert-bitrate" class="input pill input-sm">
|
||
<option value="128">128 kbps</option>
|
||
<option value="192">192 kbps</option>
|
||
<option value="256">256 kbps</option>
|
||
<option value="320" selected>320 kbps</option>
|
||
</select>
|
||
</label>
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="checkbox" id="convert-vbr" />
|
||
<span>VBR (qualità variabile)</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="beatport-header" style="margin-top:12px;gap:12px;">
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="radio" name="convert-out" id="convert-out-same" checked />
|
||
<span>Accanto al file originale</span>
|
||
</label>
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="radio" name="convert-out" id="convert-out-custom" />
|
||
<span>Cartella custom:</span>
|
||
</label>
|
||
<button id="convert-out-browse" class="btn btn-ghost pill btn-sm" disabled>Sfoglia…</button>
|
||
<span id="convert-out-path" class="beatport-output-info" style="margin:0;"></span>
|
||
</div>
|
||
|
||
<div id="convert-status" class="beatport-status"></div>
|
||
</div>
|
||
|
||
<div class="card beatport-table-card" id="convert-table-wrap" hidden>
|
||
<div class="beatport-table-wrap">
|
||
<table class="beatport-table" id="convert-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="col-check"><input type="checkbox" id="convert-select-all" checked /></th>
|
||
<th class="col-pos">#</th>
|
||
<th>File</th>
|
||
<th class="col-state">Stato</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="convert-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="beatport-toolbar" id="convert-toolbar">
|
||
<span class="counter" id="convert-selected-count">0/0 selezionati</span>
|
||
<button id="convert-run-btn" class="btn btn-primary pill" disabled>
|
||
<span class="ico">▶</span> Converti selezionati
|
||
</button>
|
||
<button id="convert-stop-btn" class="btn btn-danger pill" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="convert-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: DEDUP (audio duplicati via Chromaprint) ===== -->
|
||
<section class="view" id="view-dedup">
|
||
<header class="hero hero-red">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow red">DEDUP AUDIO</div>
|
||
<h1 class="hero-title">Trova e rimuovi duplicati audio</h1>
|
||
<p class="hero-subtitle">Audio fingerprinting via Chromaprint: riconosce brani identici indipendentemente dal bitrate, dal formato o dai tag.</p>
|
||
</div>
|
||
<div class="hero-deco">🗑</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Cartella</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<button id="dedup-pick-folder" class="btn btn-primary pill">📁 Scegli cartella</button>
|
||
<button id="dedup-start-btn" class="btn btn-primary pill" disabled>
|
||
<span class="ico">▶</span> Scansiona
|
||
</button>
|
||
<button id="dedup-stop-btn" class="btn btn-danger pill" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<span id="dedup-path-display" class="beatport-output-info" style="margin:0;">Nessuna cartella selezionata</span>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="checkbox" id="dedup-recursive" checked />
|
||
<span>Ricerca ricorsiva (include sottocartelle)</span>
|
||
</label>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<label style="display:flex;gap:8px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<span>Metodo:</span>
|
||
<select id="dedup-method" class="input" style="padding:4px 8px;font-size:13px;">
|
||
<option value="fingerprint">Audio fingerprint (preciso, lento)</option>
|
||
<option value="filename">Nome file (veloce, meno preciso)</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<div id="dedup-status" class="beatport-status"></div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Progresso</h2>
|
||
<div class="card">
|
||
<div class="progress">
|
||
<div class="progress-bar"><div id="dedupProgressFill" class="progress-fill pink"></div></div>
|
||
<div class="progress-meta">
|
||
<span id="dedupPercent" class="percent pink">0%</span>
|
||
<span id="dedupCounter">In attesa</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Risultati</h2>
|
||
<div class="card" id="dedup-summary-card" hidden>
|
||
<div class="dedup-summary" id="dedup-summary-line"></div>
|
||
</div>
|
||
<div id="dedup-groups-wrap" class="dedup-groups-scroll"></div>
|
||
|
||
<div class="dedup-footer-bar" id="dedup-footer-bar" hidden>
|
||
<span class="counter" id="dedup-selected-count">0 file da cancellare</span>
|
||
<button id="dedup-select-suggested-btn" class="btn btn-ghost pill">
|
||
✓ Seleziona tutti i consigliati
|
||
</button>
|
||
<button id="dedup-trash-btn" class="btn btn-danger pill" disabled>
|
||
<span class="ico">🗑</span> Sposta in cestino
|
||
</button>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="dedup-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: CATALOGA (AcoustID -> <Anno>/<Genere>/) ===== -->
|
||
<section class="view" id="view-catalog">
|
||
<header class="hero hero-purple">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow purple">CATALOGA</div>
|
||
<h1 class="hero-title">Organizza per anno e genere</h1>
|
||
<p class="hero-subtitle">Identifica ogni brano via audio fingerprint (Chromaprint + AcoustID) e sposta i file in <code><Anno>/<Genere>/</code>.</p>
|
||
</div>
|
||
<div class="hero-deco">📅</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Sorgente</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<button id="catalog-pick-source" class="btn btn-primary pill">📁 Cartella input</button>
|
||
<button id="catalog-start-btn" class="btn btn-primary pill" disabled>
|
||
<span class="ico">▶</span> Scansiona
|
||
</button>
|
||
<button id="catalog-stop-btn" class="btn btn-danger pill" hidden>
|
||
<span class="ico">◼</span> Interrompi
|
||
</button>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<span id="catalog-source-display" class="beatport-output-info" style="margin:0;">Nessuna cartella selezionata</span>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="checkbox" id="catalog-recursive" checked />
|
||
<span>Ricerca ricorsiva (include sottocartelle)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Destinazione</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<button id="catalog-pick-target" class="btn btn-ghost pill">📁 Cartella destinazione</button>
|
||
<button id="catalog-clear-target" class="btn btn-ghost pill btn-sm">Usa la stessa della sorgente</button>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:16px;">
|
||
<span id="catalog-target-display" class="beatport-output-info" style="margin:0;">Uguale alla sorgente (in-place)</span>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;gap:12px;">
|
||
<label style="display:flex;gap:8px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<span>Struttura cartelle:</span>
|
||
<select id="catalog-layout" class="input" style="padding:4px 8px;font-size:13px;">
|
||
<option value="year_genre"><Anno>/<Genere>/</option>
|
||
<option value="genre_year"><Genere>/<Anno>/</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<div class="hint" style="margin-top:10px;">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Se lasci vuota la destinazione i file vengono organizzati <strong>in-place</strong> nella cartella sorgente.
|
||
Le sottocartelle vengono create automaticamente in base allo schema scelto.
|
||
File senza match finiscono in <code>Unknown Year</code> / <code>Unknown Genre</code>.
|
||
</div>
|
||
</div>
|
||
<div id="catalog-status" class="beatport-status"></div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Progresso</h2>
|
||
<div class="card">
|
||
<div class="progress">
|
||
<div class="progress-bar"><div id="catalogProgressFill" class="progress-fill purple"></div></div>
|
||
<div class="progress-meta">
|
||
<span id="catalogPercent" class="percent purple">0%</span>
|
||
<span id="catalogCounter">In attesa</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Risultati</h2>
|
||
<div class="card" id="catalog-summary-card" hidden>
|
||
<div class="catalog-summary" id="catalog-summary-line"></div>
|
||
</div>
|
||
<div id="catalog-table-wrap" class="catalog-scroll" hidden>
|
||
<table class="catalog-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="catalog-col-check">
|
||
<input type="checkbox" id="catalog-select-all" title="Seleziona/deseleziona tutti"/>
|
||
</th>
|
||
<th class="catalog-col-file">File</th>
|
||
<th class="catalog-col-artist">Artista</th>
|
||
<th class="catalog-col-title">Titolo</th>
|
||
<th class="catalog-col-year">Anno</th>
|
||
<th class="catalog-col-genre">Genere</th>
|
||
<th class="catalog-col-status">Stato</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="catalog-table-body"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="catalog-footer-bar" id="catalog-footer-bar" hidden>
|
||
<span class="counter" id="catalog-selected-count">0 file selezionati</span>
|
||
<button id="catalog-move-btn" class="btn btn-danger pill" disabled>
|
||
<span class="ico">📦</span> Sposta selezionati
|
||
</button>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="catalog-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: FLATTEN ===== -->
|
||
<section class="view" id="view-flatten">
|
||
<header class="hero hero-blue">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow blue">APPIATTISCI</div>
|
||
<h1 class="hero-title">Sposta tutti i brani nella cartella padre</h1>
|
||
<p class="hero-subtitle">
|
||
Scansiona ricorsivamente ogni sotto-cartella e sposta i file audio nella root.
|
||
Utile come "undo" della catalogazione o per unire archivi frammentati.
|
||
</p>
|
||
</div>
|
||
<div class="hero-deco">📤</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Cartella</h2>
|
||
<div class="card">
|
||
<div class="beatport-header">
|
||
<button id="flatten-pick-folder" class="btn btn-primary pill">📁 Scegli cartella</button>
|
||
<button id="flatten-start-btn" class="btn btn-blue pill" disabled>▶ Sposta tutto nella root</button>
|
||
<button id="flatten-stop-btn" class="btn btn-danger pill" hidden>■ Interrompi</button>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;">
|
||
<span id="flatten-path-display" class="beatport-output-info" style="margin:0;">Nessuna cartella selezionata</span>
|
||
</div>
|
||
<div class="beatport-header" style="margin-top:12px;">
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="checkbox" id="flatten-remove-empty" checked />
|
||
<span>Elimina le sotto-cartelle rimaste vuote dopo lo spostamento</span>
|
||
</label>
|
||
</div>
|
||
<div class="hint" style="margin-top:10px;">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Ogni file con estensione audio (mp3, m4a, wav, flac...) nelle sotto-cartelle
|
||
verrà spostato nella cartella padre. In caso di nomi duplicati viene aggiunto
|
||
suffisso <code>_1</code>, <code>_2</code>... (nessun overwrite).
|
||
</div>
|
||
</div>
|
||
<div id="flatten-status" class="beatport-status"></div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Progresso</h2>
|
||
<div class="card">
|
||
<div class="progress">
|
||
<div class="progress-bar"><div id="flattenProgressFill" class="progress-fill blue"></div></div>
|
||
<div class="progress-meta">
|
||
<span id="flattenPercent" class="percent blue">0%</span>
|
||
<span id="flattenCounter">In attesa</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="flatten-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== VIEW: CHARTS ===== -->
|
||
<section class="view" id="view-charts">
|
||
<header class="hero hero-indigo">
|
||
<div class="hero-content">
|
||
<div class="hero-eyebrow indigo">CHARTS</div>
|
||
<h1 class="hero-title">Esplora le classifiche</h1>
|
||
<p class="hero-subtitle">Del momento (iTunes/Spotify), dance italiana (M2O) o storiche per genere (Last.fm) — aggiungi in un click alla coda Scarica.</p>
|
||
</div>
|
||
<div class="hero-deco">📊</div>
|
||
</header>
|
||
|
||
<h2 class="section-label">Sorgente</h2>
|
||
<div class="card">
|
||
<div class="charts-source-row">
|
||
<label class="charts-radio">
|
||
<input type="radio" name="charts-source" value="itunes" checked />
|
||
<span>🎵 Del momento (iTunes)</span>
|
||
</label>
|
||
<label class="charts-radio">
|
||
<input type="radio" name="charts-source" value="spotify" />
|
||
<span>🟢 Spotify Charts</span>
|
||
</label>
|
||
<label class="charts-radio">
|
||
<input type="radio" name="charts-source" value="m2o" />
|
||
<span>🔴 M2O Chart (dance italiana)</span>
|
||
</label>
|
||
<label class="charts-radio">
|
||
<input type="radio" name="charts-source" value="lastfm" />
|
||
<span>📻 Storiche per genere (Last.fm)</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="charts-filters" id="charts-filters-itunes">
|
||
<div class="field inline">
|
||
<label class="field-label">Paese</label>
|
||
<select id="charts-itunes-country" class="input pill input-sm">
|
||
<option value="it">🇮🇹 Italia</option>
|
||
<option value="us">🇺🇸 USA</option>
|
||
<option value="gb">🇬🇧 UK</option>
|
||
<option value="ww">🌐 Mondo</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-filters" id="charts-filters-spotify" hidden>
|
||
<div class="field inline">
|
||
<label class="field-label">Playlist chart</label>
|
||
<select id="charts-spotify-playlist" class="input pill input-sm">
|
||
<option value="top50_global">Top 50 Global</option>
|
||
<option value="top50_italy">Top 50 Italy</option>
|
||
<option value="top50_usa">Top 50 USA</option>
|
||
<option value="top50_uk">Top 50 UK</option>
|
||
<option value="viral50_global">Viral 50 Global</option>
|
||
<option value="viral50_italy">Viral 50 Italy</option>
|
||
</select>
|
||
</div>
|
||
<div class="hint" style="margin-top:8px;">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Le playlist Spotify richiedono le tue credenziali API (Impostazioni → Spotify).
|
||
Senza credenziali questa sorgente resta vuota.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-filters" id="charts-filters-m2o" hidden>
|
||
<div class="hint">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Chart dance italiana settimanale di M2O. Nessun filtro: premi "Aggiorna" per caricare la classifica corrente.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-filters" id="charts-filters-lastfm" hidden>
|
||
<div class="field inline">
|
||
<label class="field-label">Decade</label>
|
||
<select id="charts-lastfm-decade" class="input pill input-sm">
|
||
<option value="">Nessuna</option>
|
||
<option value="70s">Anni 70</option>
|
||
<option value="80s">Anni 80</option>
|
||
<option value="90s" selected>Anni 90</option>
|
||
<option value="2000s">Anni 2000</option>
|
||
<option value="2010s">Anni 2010</option>
|
||
</select>
|
||
</div>
|
||
<div class="field inline">
|
||
<label class="field-label">Genere</label>
|
||
<select id="charts-lastfm-genre" class="input pill input-sm">
|
||
<option value="">Nessuno</option>
|
||
<option value="pop">Pop</option>
|
||
<option value="rock">Rock</option>
|
||
<option value="dance" selected>Dance</option>
|
||
<option value="house">House</option>
|
||
<option value="hip hop">Hip Hop</option>
|
||
<option value="latin">Latin</option>
|
||
<option value="r&b">R&B</option>
|
||
<option value="italo disco">Italo Disco</option>
|
||
<option value="reggae">Reggae</option>
|
||
<option value="electronic">Electronic</option>
|
||
</select>
|
||
</div>
|
||
<div class="hint" style="margin-top:8px;">
|
||
<span class="hint-ico">ⓘ</span>
|
||
<div>
|
||
Last.fm richiede una API key: nell'app c'è una chiave demo, se non funziona
|
||
registrane una gratuita su <code>last.fm/api/account/create</code> e mettila nel campo
|
||
<code>lastfm_api_key</code> di <code>config.json</code>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="beatport-header" style="margin-top:14px;gap:12px;">
|
||
<button id="charts-fetch-btn" class="btn btn-primary pill">
|
||
<span class="ico">▶</span> Aggiorna classifica
|
||
</button>
|
||
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
|
||
<input type="checkbox" id="charts-force" />
|
||
<span>Ignora cache (force refresh)</span>
|
||
</label>
|
||
<span id="charts-status" class="beatport-status"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="charts-results-card" class="card" hidden>
|
||
<div class="charts-actions-row">
|
||
<span id="charts-summary-line" class="catalog-summary"></span>
|
||
<div class="charts-actions-btns">
|
||
<button id="charts-select-all-btn" class="btn btn-ghost pill btn-sm">Seleziona tutti</button>
|
||
<button id="charts-deselect-all-btn" class="btn btn-ghost pill btn-sm">Deseleziona tutti</button>
|
||
</div>
|
||
</div>
|
||
<div id="charts-table-wrap" class="charts-scroll">
|
||
<table class="charts-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="charts-col-check">
|
||
<input type="checkbox" id="charts-select-all" title="Seleziona/deseleziona tutti" />
|
||
</th>
|
||
<th class="charts-col-rank">#</th>
|
||
<th class="charts-col-artist">Artista</th>
|
||
<th class="charts-col-title">Titolo</th>
|
||
<th class="charts-col-album">Album</th>
|
||
<th class="charts-col-pop">Popolarità</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="charts-table-body"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-footer-bar" id="charts-footer-bar" hidden>
|
||
<span class="counter" id="charts-selected-count">0 brani selezionati</span>
|
||
<button id="charts-add-queue-btn" class="btn btn-primary pill" disabled>
|
||
<span class="ico">➕</span> Aggiungi alla coda Scarica
|
||
</button>
|
||
</div>
|
||
|
||
<h2 class="section-label">Log</h2>
|
||
<div class="card log-card">
|
||
<div class="log" id="charts-log"></div>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- ============== SCHERMATA ATTIVAZIONE LICENZA ============== -->
|
||
<div class="activate-screen" id="activateScreen" hidden>
|
||
<div class="activate-card">
|
||
<div class="activate-logo">♫</div>
|
||
<h1 class="activate-title">Benvenuto in MusicTools</h1>
|
||
<p class="activate-sub">
|
||
Inserisci la tua licenza per iniziare. Se non ne hai ancora una,
|
||
acquistala dal nostro sito ed entra in pochi secondi.
|
||
</p>
|
||
|
||
<div class="activate-form">
|
||
<div class="field">
|
||
<label class="field-label">Email dell'acquisto</label>
|
||
<input type="email" id="actEmail" class="input pill" placeholder="tu@esempio.com" autocomplete="email" />
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">Chiave di licenza</label>
|
||
<input type="text" id="actKey" class="input pill" placeholder="XXXX-XXXX-XXXX-XXXX" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="activate-error" id="actError" hidden></div>
|
||
|
||
<div class="activate-actions">
|
||
<button class="btn btn-primary pill btn-lg" id="actActivateBtn">
|
||
<span class="ico">🔓</span> Attiva
|
||
</button>
|
||
<button class="btn btn-ghost pill btn-lg" id="actBuyBtn">
|
||
Compra una licenza
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="activate-foot">
|
||
Hai problemi? Scrivici a <a href="mailto:info@djluza.com">info@djluza.com</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============== MODAL: Spotify Guide ============== -->
|
||
<div class="modal-backdrop" id="guideModal" hidden>
|
||
<div class="modal">
|
||
<div class="modal-head">
|
||
<h2>Come ottenere le credenziali Spotify</h2>
|
||
<button class="modal-close" id="guideCloseBtn">×</button>
|
||
</div>
|
||
<pre class="modal-body" id="guideBody"></pre>
|
||
<div class="modal-foot">
|
||
<button class="btn btn-purple pill" id="guideOpenDashBtn">Apri Spotify Dashboard</button>
|
||
<button class="btn btn-ghost pill" id="guideCloseBtn2">Chiudi</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============== MODAL: Upgrade richiesto ============== -->
|
||
<div class="modal-backdrop" id="upgradeModal" hidden>
|
||
<div class="modal">
|
||
<div class="modal-head">
|
||
<h2 id="upgradeModalTitle">Passa a un piano superiore</h2>
|
||
<button class="modal-close" id="upgradeCloseBtn">×</button>
|
||
</div>
|
||
<div class="modal-body" id="upgradeModalBody">
|
||
<!-- popolato dinamicamente -->
|
||
</div>
|
||
<div class="modal-foot">
|
||
<button class="btn btn-primary pill" id="upgradeOpenBtn">Vedi i piani</button>
|
||
<button class="btn btn-ghost pill" id="upgradeCloseBtn2">Chiudi</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============== MODAL: Match locale Upgrade ============== -->
|
||
<div class="modal-backdrop" id="upgradePickerModal" hidden>
|
||
<div class="modal">
|
||
<div class="modal-head">
|
||
<h2>Match locali trovati</h2>
|
||
<button class="modal-close" id="upgradePickerCloseBtn">×</button>
|
||
</div>
|
||
<div class="modal-body upgrade-picker-body">
|
||
<div class="modal-sub" id="upgradePickerFilename"></div>
|
||
<div id="upgradePickerList" class="upgrade-picker-list"></div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<button class="btn btn-primary pill" id="upgradePickerUseBtn" disabled>Usa selezionato</button>
|
||
<button class="btn btn-ghost pill" id="upgradePickerYoutubeBtn">Cerca su YouTube</button>
|
||
<button class="btn btn-ghost pill" id="upgradePickerSkipBtn">Salta</button>
|
||
<button class="btn btn-danger pill" id="upgradePickerCancelAllBtn">■ Annulla tutto</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============== MODAL: Cataloga — conferma spostamento ============== -->
|
||
<div class="modal-backdrop" id="catalogConfirmModal" hidden>
|
||
<div class="modal">
|
||
<div class="modal-head">
|
||
<h2>Conferma spostamento</h2>
|
||
<button class="modal-close" id="catalogConfirmCloseBtn">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="catalogConfirmMsg">Stai per spostare N file. Procedi?</p>
|
||
<p class="modal-hint">
|
||
L'operazione riorganizza i file in sottocartelle
|
||
<code><Anno>/<Genere>/</code>. E' reversibile
|
||
manualmente dal log ma puo' richiedere tempo su cartelle
|
||
grandi.
|
||
</p>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<button class="btn btn-ghost pill" id="catalogConfirmCancelBtn">Annulla</button>
|
||
<button class="btn btn-danger pill" id="catalogConfirmProceedBtn">Sposta</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============== TOAST ============== -->
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script src="js/app.js"></script>
|
||
</body>
|
||
</html>
|