duplicate cleaner
This commit is contained in:
parent
aa09b0ba5b
commit
0b7b88e892
3 changed files with 385 additions and 5 deletions
|
|
@ -240,7 +240,10 @@
|
|||
<h3 class="section-title">Tools & Operations</h3>
|
||||
<div class="tools-grid">
|
||||
<div class="tool-card" id="db-updater-card">
|
||||
<h4 class="tool-card-title">Database Updater</h4>
|
||||
<div class="tool-card-header">
|
||||
<h4 class="tool-card-title">Database Updater</h4>
|
||||
<button class="tool-help-button" data-tool="db-updater" title="Learn more about this tool">?</button>
|
||||
</div>
|
||||
<p class="tool-card-info">Last Full Refresh: <span id="db-last-refresh">Never</span></p>
|
||||
<div class="tool-card-stats">
|
||||
<div class="stat-item">
|
||||
|
|
@ -277,8 +280,9 @@
|
|||
</div>
|
||||
|
||||
<div class="tool-card" id="metadata-updater-card">
|
||||
<div class="card-header">
|
||||
<h3>Metadata Updater</h3>
|
||||
<div class="tool-card-header">
|
||||
<h4 class="tool-card-title">Metadata Updater</h4>
|
||||
<button class="tool-help-button" data-tool="metadata-updater" title="Learn more about this tool">?</button>
|
||||
</div>
|
||||
<p class="metadata-updater-description tool-card-info">Updates artist photos, genres, and album art from Spotify.</p>
|
||||
<div class="tool-card-controls">
|
||||
|
|
@ -302,7 +306,10 @@
|
|||
</div>
|
||||
|
||||
<div class="tool-card" id="quality-scanner-card">
|
||||
<h4 class="tool-card-title">Quality Scanner</h4>
|
||||
<div class="tool-card-header">
|
||||
<h4 class="tool-card-title">Quality Scanner</h4>
|
||||
<button class="tool-help-button" data-tool="quality-scanner" title="Learn more about this tool">?</button>
|
||||
</div>
|
||||
<p class="tool-card-info">Scan library for tracks below quality preferences</p>
|
||||
<div class="tool-card-stats">
|
||||
<div class="stat-item">
|
||||
|
|
@ -339,7 +346,10 @@
|
|||
</div>
|
||||
|
||||
<div class="tool-card" id="duplicate-cleaner-card">
|
||||
<h4 class="tool-card-title">Duplicate Cleaner</h4>
|
||||
<div class="tool-card-header">
|
||||
<h4 class="tool-card-title">Duplicate Cleaner</h4>
|
||||
<button class="tool-help-button" data-tool="duplicate-cleaner" title="Learn more about this tool">?</button>
|
||||
</div>
|
||||
<p class="tool-card-info">Detect and remove duplicate tracks in Transfer folder</p>
|
||||
<div class="tool-card-stats">
|
||||
<div class="stat-item">
|
||||
|
|
@ -2212,6 +2222,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tool Help Modal -->
|
||||
<div class="tool-help-modal" id="tool-help-modal">
|
||||
<div class="tool-help-modal-content">
|
||||
<div class="tool-help-modal-header">
|
||||
<h3 id="tool-help-modal-title">Tool Information</h3>
|
||||
<button class="tool-help-modal-close">×</button>
|
||||
</div>
|
||||
<div class="tool-help-modal-body" id="tool-help-modal-body">
|
||||
<!-- Content will be dynamically inserted -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -9049,6 +9049,197 @@ function stopDuplicateCleanerPolling() {
|
|||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// == TOOL HELP MODAL ==
|
||||
// ============================================
|
||||
|
||||
const TOOL_HELP_CONTENT = {
|
||||
'db-updater': {
|
||||
title: 'Database Updater',
|
||||
content: `
|
||||
<h4>What does this tool do?</h4>
|
||||
<p>The Database Updater syncs your media server library (Plex, Jellyfin, or Navidrome) with SoulSync's internal database.</p>
|
||||
|
||||
<h4>Update Modes</h4>
|
||||
<ul>
|
||||
<li><strong>Incremental Update:</strong> Only scans for new artists, albums, and tracks that have been added since the last update. Fast and efficient for regular updates.</li>
|
||||
<li><strong>Full Refresh:</strong> Completely rebuilds the database from scratch. Use this if you've made significant changes to your library or if data seems out of sync.</li>
|
||||
</ul>
|
||||
|
||||
<h4>When to use it?</h4>
|
||||
<ul>
|
||||
<li>After adding new music to your media server</li>
|
||||
<li>When library statistics seem incorrect</li>
|
||||
<li>After changing media server settings</li>
|
||||
</ul>
|
||||
|
||||
<h4>Progress Persistence</h4>
|
||||
<p>The update runs in the background. You can close this page and return later - progress will be preserved and continue where it left off.</p>
|
||||
`
|
||||
},
|
||||
'metadata-updater': {
|
||||
title: 'Metadata Updater',
|
||||
content: `
|
||||
<h4>What does this tool do?</h4>
|
||||
<p>The Metadata Updater enhances your library by fetching artist photos, genres, and album artwork from Spotify.</p>
|
||||
|
||||
<h4>Refresh Interval Options</h4>
|
||||
<ul>
|
||||
<li><strong>6 months:</strong> Only updates metadata for artists not updated in the last 180 days</li>
|
||||
<li><strong>3 months:</strong> Updates metadata for artists not updated in the last 90 days</li>
|
||||
<li><strong>1 month:</strong> Updates metadata for artists not updated in the last 30 days</li>
|
||||
<li><strong>Force All:</strong> Updates all artists regardless of when they were last updated</li>
|
||||
</ul>
|
||||
|
||||
<h4>What gets updated?</h4>
|
||||
<ul>
|
||||
<li>Artist profile photos</li>
|
||||
<li>Music genres</li>
|
||||
<li>Album cover artwork</li>
|
||||
<li>Spotify popularity scores</li>
|
||||
</ul>
|
||||
|
||||
<h4>Note</h4>
|
||||
<p>This tool is only available for <strong>Plex</strong> media servers. It requires Spotify authentication to fetch metadata.</p>
|
||||
`
|
||||
},
|
||||
'quality-scanner': {
|
||||
title: 'Quality Scanner',
|
||||
content: `
|
||||
<h4>What does this tool do?</h4>
|
||||
<p>The Quality Scanner identifies tracks in your library that don't meet your preferred quality settings and automatically matches them to Spotify to add to your wishlist for re-downloading.</p>
|
||||
|
||||
<h4>Scan Scope</h4>
|
||||
<ul>
|
||||
<li><strong>Watchlist Artists Only:</strong> Only scans tracks from artists you're watching. Faster and more focused.</li>
|
||||
<li><strong>All Library Tracks:</strong> Scans your entire music library. Comprehensive but takes longer.</li>
|
||||
</ul>
|
||||
|
||||
<h4>How it works</h4>
|
||||
<ol>
|
||||
<li>Scans tracks and checks file format against your quality preferences</li>
|
||||
<li>Identifies tracks below your quality threshold (e.g., MP3 when you prefer FLAC)</li>
|
||||
<li>Uses fuzzy matching to find the track on Spotify (70% confidence minimum)</li>
|
||||
<li>Automatically adds matched tracks to your wishlist for re-download</li>
|
||||
</ol>
|
||||
|
||||
<h4>Quality Tiers</h4>
|
||||
<ul>
|
||||
<li><strong>Tier 1 (Best):</strong> FLAC, WAV, ALAC, AIFF - Lossless formats</li>
|
||||
<li><strong>Tier 2:</strong> OPUS, OGG - High quality lossy</li>
|
||||
<li><strong>Tier 3:</strong> M4A, AAC - Standard lossy</li>
|
||||
<li><strong>Tier 4:</strong> MP3, WMA - Lower quality lossy</li>
|
||||
</ul>
|
||||
|
||||
<h4>Stats Explained</h4>
|
||||
<ul>
|
||||
<li><strong>Processed:</strong> Total tracks scanned so far</li>
|
||||
<li><strong>Quality Met:</strong> Tracks that meet your quality standards</li>
|
||||
<li><strong>Low Quality:</strong> Tracks below your quality threshold</li>
|
||||
<li><strong>Matched:</strong> Low quality tracks successfully matched to Spotify and added to wishlist</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
'duplicate-cleaner': {
|
||||
title: 'Duplicate Cleaner',
|
||||
content: `
|
||||
<h4>What does this tool do?</h4>
|
||||
<p>The Duplicate Cleaner scans your Transfer folder for duplicate audio files and automatically removes lower-quality versions, keeping only the best copy.</p>
|
||||
|
||||
<h4>How it detects duplicates</h4>
|
||||
<p>Files are considered duplicates when:</p>
|
||||
<ul>
|
||||
<li>They are in the <strong>same folder</strong></li>
|
||||
<li>They have the <strong>exact same filename</strong> (ignoring file extension)</li>
|
||||
</ul>
|
||||
<p>Example: <code>Song.flac</code> and <code>Song.mp3</code> in the same folder = duplicates ✓</p>
|
||||
<p>Example: <code>Song.flac</code> and <code>Song (Remaster).flac</code> = NOT duplicates ✗</p>
|
||||
|
||||
<h4>Which file is kept?</h4>
|
||||
<p>Priority order (best to worst):</p>
|
||||
<ol>
|
||||
<li><strong>Format priority:</strong> FLAC/Lossless > OPUS/OGG > M4A/AAC > MP3/WMA</li>
|
||||
<li><strong>If same format:</strong> Larger file size is kept (usually indicates better bitrate)</li>
|
||||
</ol>
|
||||
|
||||
<h4>Where do deleted files go?</h4>
|
||||
<p>Removed files are moved to <code>Transfer/deleted/</code> folder (not permanently deleted). You can review and recover them if needed.</p>
|
||||
|
||||
<h4>Safety Features</h4>
|
||||
<ul>
|
||||
<li>Only processes audio files (FLAC, MP3, M4A, etc.)</li>
|
||||
<li>Only removes files with identical names in the same folder</li>
|
||||
<li>Files are moved, not deleted - fully recoverable</li>
|
||||
<li>Preserves original folder structure in the deleted folder</li>
|
||||
</ul>
|
||||
|
||||
<h4>Stats Explained</h4>
|
||||
<ul>
|
||||
<li><strong>Files Scanned:</strong> Total audio files checked</li>
|
||||
<li><strong>Duplicates Found:</strong> Number of duplicate files detected</li>
|
||||
<li><strong>Deleted:</strong> Files moved to deleted folder</li>
|
||||
<li><strong>Space Freed:</strong> Total disk space reclaimed</li>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
function initializeToolHelpButtons() {
|
||||
const helpButtons = document.querySelectorAll('.tool-help-button');
|
||||
const modal = document.getElementById('tool-help-modal');
|
||||
const closeButton = modal.querySelector('.tool-help-modal-close');
|
||||
|
||||
// Attach click handlers to all help buttons
|
||||
helpButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const toolId = button.getAttribute('data-tool');
|
||||
openToolHelpModal(toolId);
|
||||
});
|
||||
});
|
||||
|
||||
// Close modal when clicking close button
|
||||
closeButton.addEventListener('click', closeToolHelpModal);
|
||||
|
||||
// Close modal when clicking outside content
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
closeToolHelpModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on Escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && modal.classList.contains('active')) {
|
||||
closeToolHelpModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openToolHelpModal(toolId) {
|
||||
const modal = document.getElementById('tool-help-modal');
|
||||
const titleElement = document.getElementById('tool-help-modal-title');
|
||||
const bodyElement = document.getElementById('tool-help-modal-body');
|
||||
|
||||
const helpData = TOOL_HELP_CONTENT[toolId];
|
||||
if (!helpData) {
|
||||
console.warn(`No help content found for tool: ${toolId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
titleElement.textContent = helpData.title;
|
||||
bodyElement.innerHTML = helpData.content;
|
||||
|
||||
modal.classList.add('active');
|
||||
document.body.style.overflow = 'hidden'; // Prevent background scrolling
|
||||
}
|
||||
|
||||
function closeToolHelpModal() {
|
||||
const modal = document.getElementById('tool-help-modal');
|
||||
modal.classList.remove('active');
|
||||
document.body.style.overflow = ''; // Restore scrolling
|
||||
}
|
||||
|
||||
function stopWishlistCountPolling() {
|
||||
if (wishlistCountInterval) {
|
||||
clearInterval(wishlistCountInterval);
|
||||
|
|
@ -9160,6 +9351,9 @@ async function loadDashboardData() {
|
|||
duplicateCleanButton.addEventListener('click', handleDuplicateCleanButtonClick);
|
||||
}
|
||||
|
||||
// Attach event listeners for tool help buttons
|
||||
initializeToolHelpButtons();
|
||||
|
||||
// Attach event listener for the wishlist button
|
||||
const wishlistButton = document.getElementById('wishlist-button');
|
||||
if (wishlistButton) {
|
||||
|
|
|
|||
|
|
@ -15937,3 +15937,166 @@ body {
|
|||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ====================================
|
||||
Tool Help Button & Modal
|
||||
==================================== */
|
||||
|
||||
.tool-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tool-card-header .tool-card-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tool-help-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #666;
|
||||
background-color: transparent;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-help-button:hover {
|
||||
background-color: #1db954;
|
||||
border-color: #1db954;
|
||||
color: #fff;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tool-help-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
.tool-help-modal.active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tool-help-modal-content {
|
||||
background-color: #1a1a1a;
|
||||
border-radius: 12px;
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-help-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.tool-help-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tool-help-modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #999;
|
||||
font-size: 32px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.2s ease;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tool-help-modal-close:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tool-help-modal-body {
|
||||
padding: 24px;
|
||||
color: #ccc;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tool-help-modal-body h4 {
|
||||
color: #1db954;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tool-help-modal-body h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tool-help-modal-body ul {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.tool-help-modal-body li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.tool-help-modal-body p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.tool-help-modal-body code {
|
||||
background-color: #2a2a2a;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
color: #1db954;
|
||||
}
|
||||
|
||||
.tool-help-modal-body strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue