Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import

All user-facing labels, docs, help text, tooltips, error messages, and debug
info output updated. Backend config keys, variable names, actual path values,
and Docker volume mounts are completely unchanged — zero functional impact.
This commit is contained in:
Broque Thomas 2026-04-18 17:35:20 -07:00
parent cef1162c5e
commit efe8280e23
8 changed files with 127 additions and 127 deletions

View file

@ -280,9 +280,9 @@ Open SoulSync at `http://localhost:8008` and go to Settings.
**Download Source**: Choose your preferred source (Soulseek, Deezer, Tidal, Qobuz, HiFi, YouTube, or Hybrid)
**Paths**:
- **Download Path**: Container path to slskd's download folder (e.g., `/app/downloads`)
- **Transfer Path**: Where organized music goes (e.g., `/app/Transfer`)
- **Staging Path**: Optional import folder (e.g., `/app/Staging`)
- **Input Folder**: Container path to slskd's download folder (e.g., `/app/downloads`)
- **Output Folder**: Where organized music goes (e.g., `/app/Transfer`)
- **Import Folder**: Optional folder for importing existing music (e.g., `/app/Staging`)
**Media Server** (optional): Use your machine's actual IP (not `localhost` — that means inside the container)
@ -293,9 +293,9 @@ Open SoulSync at `http://localhost:8008` and go to Settings.
| Config | `/app/config` | Your config folder |
| Logs | `/app/logs` | Your logs folder |
| Database | `/app/data` | Named volume (recommended) |
| Downloads | `/app/downloads` | Same folder slskd downloads to |
| Transfer | `/app/Transfer` | Where organized music goes |
| Staging | `/app/Staging` | Optional import folder |
| Input | `/app/downloads` | Same folder slskd downloads to |
| Output | `/app/Transfer` | Where organized music goes |
| Import | `/app/Staging` | Optional folder for importing music |
**Important:** Use a named volume for the database (`soulsync_database:/app/data`). Direct host path mounts to `/app/data` can overwrite Python module files.

View file

@ -39,13 +39,13 @@ services:
# Use named volume for database persistence (separate from host database)
# NOTE: Changed from /app/database to /app/data to avoid overwriting Python package
- soulsync_database:/app/data
# IMPORTANT: Mount host paths that your download/transfer folders live on.
# IMPORTANT: Mount host paths that your input/output folders live on.
# Replace these examples with your actual paths:
# Linux/Mac: - /path/to/your/downloads:/host/downloads:rw
# WSL: - /mnt/d/Music:/host/music:rw
# You can add as many mounts as needed for your setup.
# - /path/to/downloads:/host/downloads:rw
# - /path/to/transfer:/host/transfer:rw
# - /path/to/music-library:/host/music:rw
# Optional: Mount your music library for Plex/Jellyfin/Navidrome access
# - /path/to/your/music:/music:ro
extra_hosts:

View file

@ -1727,7 +1727,7 @@ def _register_automation_handlers():
log_line=f'Empty directories: removed {dirs_removed}', log_type='success' if dirs_removed else 'info')
# --- 4. Sweep empty staging directories ---
_update_automation_progress(automation_id, phase='Sweeping staging folder...', progress=60)
_update_automation_progress(automation_id, phase='Sweeping import folder...', progress=60)
staging_path = _get_staging_path()
s_removed = 0
if os.path.isdir(staging_path):
@ -4454,9 +4454,9 @@ def run_service_test(service, test_config):
count += 1
if count > 100:
break # Don't count everything, just confirm files exist
return True, f"SoulSync standalone ready! Transfer folder: {transfer_path} ({count}+ audio files)"
return True, f"SoulSync standalone ready! Output folder: {transfer_path} ({count}+ audio files)"
else:
return False, f"Transfer folder not found: {transfer_path}"
return False, f"Output folder not found: {transfer_path}"
elif service == "soulseek":
if soulseek_client is None:
return False, "Download orchestrator failed to initialize. Check server logs for startup errors."
@ -6852,7 +6852,7 @@ def get_automation_blocks():
{"type": "clean_completed_downloads", "label": "Clean Completed Downloads", "icon": "check-square",
"description": "Clear completed downloads and empty directories", "available": True},
{"type": "full_cleanup", "label": "Full Cleanup", "icon": "trash",
"description": "Clear quarantine, download queue, staging folder, and search history in one sweep", "available": True},
"description": "Clear quarantine, download queue, import folder, and search history in one sweep", "available": True},
{"type": "deep_scan_library", "label": "Deep Scan Library", "icon": "search",
"description": "Full library comparison without losing enrichment data", "available": True},
{"type": "run_script", "label": "Run Script", "icon": "terminal",
@ -22516,7 +22516,7 @@ def get_version_info():
},
{
"title": "Auto-Import",
"description": "Background staging folder watcher that automatically identifies and imports music into your library",
"description": "Background import folder watcher that automatically identifies and imports music into your library",
"features": [
"• Recursive scan — any folder depth (Artist/Album/tracks, Album/tracks, loose files)",
"• Single file support — loose audio files identified via tags, filename, or AcoustID",
@ -22526,7 +22526,7 @@ def get_version_info():
"• Expandable track match details with per-track confidence scores",
"• Race condition fix prevents duplicate processing during multi-track albums",
],
"usage_note": "Enable on the Import page Auto tab. Set your staging folder in Settings."
"usage_note": "Enable on the Import page Auto tab. Set your import folder in Settings."
},
{
"title": "Wishlist Nebula",
@ -22982,7 +22982,7 @@ _OLD_V22_NOTES = """
"title": "Fix Library Maintenance Path Fixes Failing Silently",
"description": "Path mismatch fixes now use fresh config and report errors to the UI",
"features": [
"Transfer folder path is re-read from config before each fix attempt",
"Output folder path is re-read from config before each fix attempt",
"• Fix failure reasons are now shown in the toast notification",
"• Bulk fix failures are logged individually with finding ID and error details"
]
@ -23428,9 +23428,9 @@ _OLD_V2_NOTES = r"""
},
{
"title": "Staging Folder Pre-Download Check",
"description": "Check your staging folder for existing files before downloading",
"description": "Check your import folder for existing files before downloading",
"features": [
"• Before searching Soulseek/YouTube, checks the staging folder for a matching file",
"• Before searching Soulseek/YouTube, checks the import folder for a matching file",
"• Tag-based matching (Mutagen) with filename parsing fallback",
"• On match, copies the file to transfer and runs normal post-processing",
"• Staging scan cached per batch — only scans once for the entire download"
@ -24499,9 +24499,9 @@ def _resume_workers_after_scan():
def _run_soulsync_deep_scan():
"""Deep scan for SoulSync standalone mode.
1. Scans the Transfer folder for all audio files
1. Scans the output folder for all audio files
2. Compares against soulsync DB records (by file_path)
3. Untracked files moved to Staging for auto-import processing
3. Untracked files moved to import folder for auto-import processing
4. Stale DB records (file gone) removed from DB
"""
try:
@ -24510,7 +24510,7 @@ def _run_soulsync_deep_scan():
staging_path = docker_resolve_path(config_manager.get('import.staging_path', './Staging'))
if not os.path.isdir(transfer_path):
_db_update_error_callback(f"Transfer folder not found: {transfer_path}")
_db_update_error_callback(f"Output folder not found: {transfer_path}")
return
print(f"[SoulSync Deep Scan] Starting — Transfer: {transfer_path}")
@ -26851,8 +26851,8 @@ def _run_duplicate_cleaner():
if not transfer_folder or not os.path.exists(transfer_folder):
with duplicate_cleaner_lock:
duplicate_cleaner_state["status"] = "error"
duplicate_cleaner_state["phase"] = "Transfer folder not configured or does not exist"
duplicate_cleaner_state["error_message"] = "Please configure Transfer folder in settings"
duplicate_cleaner_state["phase"] = "Output folder not configured or does not exist"
duplicate_cleaner_state["error_message"] = "Please configure output folder in settings"
print(f"[Duplicate Cleaner] Transfer folder not found: {transfer_folder}")
return

View file

@ -4422,7 +4422,7 @@
</div>
</div>
<div class="form-group">
<label>Library Folder (Transfer Path):</label>
<label>Library Folder (Output Path):</label>
<input type="text" id="soulsync-transfer-path" disabled placeholder="Set in Paths section below">
<small>Files in this folder are your library. Configure the path in the Paths section.</small>
</div>
@ -4449,7 +4449,7 @@
</div>
<div class="form-group">
<label>Slskd Download Dir:</label>
<label>Input Folder (Download Dir):</label>
<div class="path-input-group">
<input type="text" id="download-path" placeholder="./downloads" readonly>
<button class="browse-button locked" onclick="togglePathLock('download', this)">Unlock</button>
@ -4457,7 +4457,7 @@
</div>
<div class="form-group">
<label>Matched Transfer Dir (Plex Music Dir?):</label>
<label>Output Folder (Music Library):</label>
<div class="path-input-group">
<input type="text" id="transfer-path" placeholder="./Transfer" readonly>
<button class="browse-button locked" onclick="togglePathLock('transfer', this)">Unlock</button>
@ -4465,7 +4465,7 @@
</div>
<div class="form-group">
<label>Import Staging Dir:</label>
<label>Import Folder:</label>
<div class="path-input-group">
<input type="text" id="staging-path" placeholder="./Staging" readonly>
<button class="browse-button locked" onclick="togglePathLock('staging', this)">Unlock</button>
@ -6060,13 +6060,13 @@
<div class="import-page-header">
<div class="import-page-title-row">
<h1 class="import-page-title"><img src="/static/import.png" class="page-header-icon" alt=""><span>Import Music</span></h1>
<button class="import-page-refresh-btn" onclick="importPageRefreshStaging()" title="Re-scan staging folder">
<button class="import-page-refresh-btn" onclick="importPageRefreshStaging()" title="Re-scan import folder">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M13.65 2.35A8 8 0 1 0 16 8h-2a6 6 0 1 1-1.76-4.24L10 6h6V0l-2.35 2.35z"/></svg>
Refresh
</button>
</div>
<div class="import-page-staging-bar" id="import-staging-bar">
<span class="import-staging-path" id="import-page-staging-path">Staging folder: loading...</span>
<span class="import-staging-path" id="import-page-staging-path">Import folder: loading...</span>
<span class="import-staging-stats" id="import-page-staging-stats"></span>
</div>
</div>
@ -6097,7 +6097,7 @@
<span>Auto-Import</span>
</label>
<span class="auto-import-status" id="auto-import-status-text">Disabled</span>
<button class="auto-import-scan-now-btn" id="auto-import-scan-now" onclick="_autoImportScanNow()" title="Scan staging folder now" style="display:none">
<button class="auto-import-scan-now-btn" id="auto-import-scan-now" onclick="_autoImportScanNow()" title="Scan import folder now" style="display:none">
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M13.65 2.35A8 8 0 1 0 16 8h-2a6 6 0 1 1-1.76-4.24L10 6h6V0l-2.35 2.35z"/></svg>
Scan Now
</button>
@ -6136,8 +6136,8 @@
</div>
<div class="auto-import-results" id="auto-import-results">
<div class="auto-import-empty">
<p>Enable auto-import to watch your staging folder for new music.</p>
<p style="opacity:0.5;font-size:12px;">Drop album folders or single tracks into your staging directory and SoulSync will identify, match, and import them automatically.</p>
<p>Enable auto-import to watch your import folder for new music.</p>
<p style="opacity:0.5;font-size:12px;">Drop album folders or single tracks into your import folder and SoulSync will identify, match, and import them automatically.</p>
</div>
</div>
</div>
@ -6147,7 +6147,7 @@
<!-- Search state -->
<div id="import-page-album-search-section">
<div class="import-page-suggestions" id="import-page-suggestions">
<div class="import-page-section-label">Suggested from your staging folder</div>
<div class="import-page-section-label">Suggested from your import folder</div>
<div class="import-page-album-grid" id="import-page-suggestions-grid"></div>
</div>
<div class="import-page-search-bar">
@ -6199,7 +6199,7 @@
</div>
</div>
<div class="import-page-singles-list" id="import-page-singles-list">
<div class="import-page-empty-state">Navigate to this page to scan your staging folder for audio files.</div>
<div class="import-page-empty-state">Navigate to this page to scan your import folder for audio files.</div>
</div>
</div>
@ -6520,7 +6520,7 @@
<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>
<p class="tool-card-info">Detect and remove duplicate tracks in output folder</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Files Scanned:</span>

View file

@ -63,7 +63,7 @@ const DOCS_SECTIONS = [
<li><strong>Download Source</strong> &mdash; Connect at least one download source: Soulseek (slskd), YouTube, Tidal, Qobuz, HiFi, or Deezer. Soulseek offers the best quality selection; the others work as alternatives or fallbacks in Hybrid mode.</li>
<li><strong>Media Server</strong> &mdash; Connect Plex, Jellyfin, or Navidrome so SoulSync knows where your library lives and can trigger scans.</li>
<li><strong>Spotify (Recommended)</strong> &mdash; Connect Spotify for the richest metadata. Create an app at <strong>developer.spotify.com</strong>, enter your Client ID and Secret, then click Authenticate.</li>
<li><strong>Download Path</strong> &mdash; Set your download and transfer paths in the Download Settings section. The transfer path should point to your media server's monitored folder.</li>
<li><strong>Input Path</strong> &mdash; Set your input and output paths in the Download Settings section. The output path should point to your media server's monitored folder.</li>
</ol>
${docsImg('gs-first-setup.jpg', 'Settings page first-time setup')}
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div>You can start using SoulSync with just one download source. Spotify and other services add metadata enrichment but aren't strictly required &mdash; iTunes/Apple Music and Deezer are always available as free fallbacks.</div></div>
@ -105,7 +105,7 @@ const DOCS_SECTIONS = [
<li><strong>Artists</strong> &mdash; Search artists, manage your watchlist, scan for new releases</li>
<li><strong>Automations</strong> &mdash; Create scheduled tasks and event-driven workflows</li>
<li><strong>Library</strong> &mdash; Browse and manage your music collection with standard or enhanced views</li>
<li><strong>Import</strong> &mdash; Import music files from a staging folder with album/track matching</li>
<li><strong>Import</strong> &mdash; Import music files from an import folder with album/track matching</li>
<li><strong>Settings</strong> &mdash; Configure services, download preferences, quality profiles, and more</li>
</ul>
${docsImg('gs-interface.jpg', 'SoulSync interface layout')}
@ -126,9 +126,9 @@ const DOCS_SECTIONS = [
<table class="docs-table">
<thead><tr><th>Folder</th><th>Default (Docker)</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><strong>Download Path</strong></td><td><code>/app/downloads</code></td><td>Where slskd/YouTube/Tidal/Qobuz initially saves downloaded files. This is a <strong>temporary staging area</strong> &mdash; files should not stay here permanently.</td></tr>
<tr><td><strong>Transfer Path</strong></td><td><code>/app/Transfer</code></td><td>Where post-processed files are moved after tagging and renaming. This <strong>must</strong> be the folder your media server (Plex/Jellyfin/Navidrome) monitors.</td></tr>
<tr><td><strong>Staging Path</strong></td><td><code>/app/Staging</code></td><td>For the Import feature only. Drop audio files here to import them into your library via the Import page.</td></tr>
<tr><td><strong>Input Path</strong></td><td><code>/app/downloads</code></td><td>Where slskd/YouTube/Tidal/Qobuz initially saves downloaded files. This is a <strong>temporary holding area</strong> &mdash; files should not stay here permanently.</td></tr>
<tr><td><strong>Output Path</strong></td><td><code>/app/Transfer</code></td><td>Where post-processed files are moved after tagging and renaming. This <strong>must</strong> be the folder your media server (Plex/Jellyfin/Navidrome) monitors.</td></tr>
<tr><td><strong>Import Path</strong></td><td><code>/app/Staging</code></td><td>For the Import feature only. Drop audio files here to import them into your library via the Import page.</td></tr>
</tbody>
</table>
${docsImg('gs-folders.jpg', 'Download settings folder configuration')}
@ -137,13 +137,13 @@ const DOCS_SECTIONS = [
<div class="docs-callout info"><span class="docs-callout-icon">&#x2139;&#xFE0F;</span><div>
<strong>The complete download-to-library pipeline:</strong><br><br>
<strong>1.</strong> You search for music in SoulSync and click download<br>
<strong>2.</strong> SoulSync tells slskd to download the file &rarr; slskd saves it to its download folder<br>
<strong>3.</strong> SoulSync detects the completed download in the <strong>Download Path</strong><br>
<strong>2.</strong> SoulSync tells slskd to download the file &rarr; slskd saves it to its input folder<br>
<strong>3.</strong> SoulSync detects the completed download in the <strong>Input Path</strong><br>
<strong>4.</strong> Post-processing runs: AcoustID verification &rarr; metadata tagging &rarr; cover art embedding &rarr; lyrics fetch<br>
<strong>5.</strong> File is renamed and organized (e.g., <code>Artist/Album/01 - Title.flac</code>)<br>
<strong>6.</strong> File is moved from Download Path &rarr; <strong>Transfer Path</strong><br>
<strong>6.</strong> File is moved from Input Path &rarr; <strong>Output Path</strong><br>
<strong>7.</strong> Media server scan is triggered &rarr; file appears in your library<br><br>
<strong>If any step fails, the pipeline stops.</strong> The most common failure point is Step 3 &mdash; SoulSync can't find the file because the Download Path doesn't match where slskd actually saved it.
<strong>If any step fails, the pipeline stops.</strong> The most common failure point is Step 3 &mdash; SoulSync can't find the file because the Input Path doesn't match where slskd actually saved it.
</div></div>
<h4>Docker Setup: The Full Picture</h4>
@ -163,31 +163,31 @@ const DOCS_SECTIONS = [
<code>/app/downloads/</code> &larr; same files as <code style="color: var(--accent-primary);">/mnt/data/slskd-downloads/</code><br>
<code>/app/Transfer/</code> &larr; same files as <code style="color: #50e050;">/mnt/media/music/</code><br><br>
<strong>SoulSync Settings (what you enter in the app)</strong><br>
Download Path: <code>/app/downloads</code><br>
Transfer Path: <code>/app/Transfer</code>
Input Path: <code>/app/downloads</code><br>
Output Path: <code>/app/Transfer</code>
</div></div>
<h4>The #1 Mistake: Not Configuring App Settings</h4>
<p class="docs-text">Many users set up their docker-compose volumes correctly but <strong>never open SoulSync Settings to configure the paths</strong>. The app defaults may not match your volume mounts. You must go to <strong>Settings &rarr; Download Settings</strong> and verify that:</p>
<ul class="docs-list">
<li><strong>Download Path</strong> matches where slskd puts completed files <em>inside the container</em> (usually <code>/app/downloads</code>)</li>
<li><strong>Transfer Path</strong> matches where you mounted your media library <em>inside the container</em> (usually <code>/app/Transfer</code>)</li>
<li><strong>Input Path</strong> matches where slskd puts completed files <em>inside the container</em> (usually <code>/app/downloads</code>)</li>
<li><strong>Output Path</strong> matches where you mounted your media library <em>inside the container</em> (usually <code>/app/Transfer</code>)</li>
</ul>
<div class="docs-callout warning"><span class="docs-callout-icon">&#x26A0;&#xFE0F;</span><div>
<strong>"I set up my docker-compose but nothing transfers"</strong> &mdash; this almost always means the app settings weren't configured. Docker-compose makes the folders accessible. The app settings tell SoulSync where to look. <strong>Both are required.</strong>
</div></div>
<h4>The #2 Mistake: Download Path Doesn't Match slskd</h4>
<p class="docs-text">The <strong>Download Path</strong> in SoulSync must point to the <strong>exact same physical folder</strong> where slskd saves its completed downloads. If they don't match, SoulSync can't find the files and post-processing fails silently.</p>
<h4>The #2 Mistake: Input Path Doesn't Match slskd</h4>
<p class="docs-text">The <strong>Input Path</strong> in SoulSync must point to the <strong>exact same physical folder</strong> where slskd saves its completed downloads. If they don't match, SoulSync can't find the files and post-processing fails silently.</p>
<div class="docs-callout info"><span class="docs-callout-icon">&#x2139;&#xFE0F;</span><div>
<strong>Both SoulSync and slskd must see the same download folder.</strong><br><br>
<strong>Both SoulSync and slskd must see the same input folder.</strong><br><br>
<strong>slskd container:</strong><br>
&bull; slskd downloads to <code>/downloads/complete</code> inside its own container<br>
&bull; slskd docker-compose: <code>- /mnt/data/slskd-downloads:/downloads/complete</code><br><br>
<strong>SoulSync container:</strong><br>
&bull; SoulSync docker-compose: <code>- /mnt/data/slskd-downloads:/app/downloads</code> (same host folder!)<br>
&bull; SoulSync Setting: Download Path = <code>/app/downloads</code><br><br>
&bull; SoulSync Setting: Input Path = <code>/app/downloads</code><br><br>
<strong>The key:</strong> both containers mount the <strong>same host folder</strong> (<code>/mnt/data/slskd-downloads</code>). The container-internal paths can be different &mdash; that's fine. What matters is they point to the same physical directory on your server.
</div></div>
@ -205,18 +205,18 @@ const DOCS_SECTIONS = [
</tbody>
</table>
<h4>Transfer Path = Media Server's Music Folder</h4>
<p class="docs-text">Your Transfer Path must ultimately point to the same physical directory your media server monitors. This is how new music appears in Plex/Jellyfin/Navidrome.</p>
<h4>Output Path = Media Server's Music Folder</h4>
<p class="docs-text">Your Output Path must ultimately point to the same physical directory your media server monitors. This is how new music appears in Plex/Jellyfin/Navidrome.</p>
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div>
<strong>Example with Plex:</strong><br><br>
&bull; Plex monitors <code>/mnt/media/music</code> on the host<br>
&bull; SoulSync docker-compose: <code>- /mnt/media/music:/app/Transfer:rw</code><br>
&bull; SoulSync Settings: Transfer Path = <code>/app/Transfer</code><br><br>
&bull; SoulSync Settings: Output Path = <code>/app/Transfer</code><br><br>
<strong>Result:</strong> SoulSync writes to <code>/app/Transfer</code> inside the container &rarr; appears at <code>/mnt/media/music</code> on the host &rarr; Plex sees it and adds it to your library.
</div></div>
<h4>Complete Docker Compose Example (slskd + SoulSync)</h4>
<p class="docs-text">Here's a working example showing both slskd and SoulSync configured to share the same download folder:</p>
<p class="docs-text">Here's a working example showing both slskd and SoulSync configured to share the same input folder:</p>
<div class="docs-callout info"><span class="docs-callout-icon">&#x1F4CB;</span><div>
<code><strong># docker-compose.yml</strong></code><br>
<code>services:</code><br>
@ -239,17 +239,17 @@ const DOCS_SECTIONS = [
<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- /docker/soulsync/staging:/app/Staging</code><br>
<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- soulsync_database:/app/data</code><br><br>
<code><strong># Then in SoulSync Settings:</strong></code><br>
<code># Download Path: /app/downloads</code><br>
<code># Transfer Path: /app/Transfer</code>
<code># Input Path: /app/downloads</code><br>
<code># Output Path: /app/Transfer</code>
</div></div>
${docsImg('gs-docker.jpg', 'Docker compose configuration')}
<h4>Setup Checklist</h4>
<p class="docs-text">Go through every item. If you miss any single one, the pipeline will break:</p>
<ol class="docs-steps">
<li><strong>slskd download folder is mounted in SoulSync's container</strong> &mdash; Both containers must mount the <strong>same host directory</strong>. The host paths (left side of <code>:</code>) must be identical.</li>
<li><strong>Media server's music folder is mounted as Transfer</strong> &mdash; Mount the folder your Plex/Jellyfin/Navidrome monitors as <code>/app/Transfer</code> with <code>:rw</code> permissions.</li>
<li><strong>SoulSync Settings are configured</strong> &mdash; Open <strong>Settings &rarr; Download Settings</strong>. Set Download Path to <code>/app/downloads</code> and Transfer Path to <code>/app/Transfer</code> (or whatever container paths you used on the right side of <code>:</code>).</li>
<li><strong>slskd input folder is mounted in SoulSync's container</strong> &mdash; Both containers must mount the <strong>same host directory</strong>. The host paths (left side of <code>:</code>) must be identical.</li>
<li><strong>Media server's music folder is mounted as Output</strong> &mdash; Mount the folder your Plex/Jellyfin/Navidrome monitors as <code>/app/Transfer</code> with <code>:rw</code> permissions.</li>
<li><strong>SoulSync Settings are configured</strong> &mdash; Open <strong>Settings &rarr; Download Settings</strong>. Set Input Path to <code>/app/downloads</code> and Output Path to <code>/app/Transfer</code> (or whatever container paths you used on the right side of <code>:</code>).</li>
<li><strong>slskd URL and API key are set</strong> &mdash; In <strong>Settings &rarr; Soulseek</strong>, enter your slskd URL (e.g., <code>http://slskd:5030</code> or <code>http://host.docker.internal:5030</code>) and API key.</li>
<li><strong>PUID/PGID match your host user</strong> &mdash; Run <code>id</code> on your host. Set those values in docker-compose environment variables. Both slskd and SoulSync should use the same PUID/PGID.</li>
<li><strong>Test with one track</strong> &mdash; Download a single track. Watch the logs. If it downloads but doesn't transfer, the paths are wrong.</li>
@ -259,7 +259,7 @@ const DOCS_SECTIONS = [
<p class="docs-text">If paths are correct but files still won't transfer, it's usually a permissions issue. SoulSync needs <strong>read + write</strong> access to all three folders.</p>
<ul class="docs-list">
<li>Set <code>PUID</code> and <code>PGID</code> in your docker-compose to match the user that owns your music folders (run <code>id</code> on your host to find your UID/GID &mdash; usually 1000/1000)</li>
<li>Ensure the Transfer folder is writable: <code>chmod -R 755 /mnt/media/music</code> (use your actual host path)</li>
<li>Ensure the output folder is writable: <code>chmod -R 755 /mnt/media/music</code> (use your actual host path)</li>
<li>If using multiple containers (slskd + SoulSync), both must use the <strong>same PUID/PGID</strong> so file permissions are compatible</li>
<li>NFS/CIFS/network mounts may need additional permissions &mdash; test with a local folder first to isolate the issue</li>
</ul>
@ -270,7 +270,7 @@ const DOCS_SECTIONS = [
<li><strong>Verify downloads are visible:</strong> <code>docker exec soulsync-webui ls -la /app/downloads</code> &mdash; you should see slskd's downloaded files here. If empty or "No such file or directory", your volume mount is wrong.</li>
<li><strong>Verify Transfer is writable:</strong> <code>docker exec soulsync-webui touch /app/Transfer/test.txt && echo "OK"</code> &mdash; then check that <code>test.txt</code> appears in your media server's music folder on the host. Clean up after: <code>rm /mnt/media/music/test.txt</code></li>
<li><strong>Verify permissions:</strong> <code>docker exec soulsync-webui id</code> &mdash; the uid and gid should match your PUID/PGID values.</li>
<li><strong>Verify app settings:</strong> Open SoulSync Settings &rarr; Download Settings. Confirm the Download Path and Transfer Path show container paths (like <code>/app/downloads</code>), not host paths.</li>
<li><strong>Verify app settings:</strong> Open SoulSync Settings &rarr; Download Settings. Confirm the Input Path and Output Path show container paths (like <code>/app/downloads</code>), not host paths.</li>
<li><strong>Test a single download:</strong> Search for a track, download it, and watch the logs. Enable DEBUG logging in Settings for full detail. Check <code>logs/app.log</code> for any path errors.</li>
</ol>
@ -278,13 +278,13 @@ const DOCS_SECTIONS = [
<table class="docs-table">
<thead><tr><th>Symptom</th><th>Likely Cause</th><th>Fix</th></tr></thead>
<tbody>
<tr><td>Files download but never transfer</td><td>App settings not configured &mdash; docker-compose volumes are set but SoulSync Settings still have defaults or wrong paths</td><td>Open <strong>Settings &rarr; Download Settings</strong> and set Download Path + Transfer Path to your <strong>container-side</strong> mount paths.</td></tr>
<tr><td>Post-processing log is empty</td><td>SoulSync can't find the downloaded file at the expected path &mdash; the Download Path in Settings doesn't match where slskd actually saves files inside the container</td><td>Run <code>docker exec soulsync-webui ls /app/downloads</code> to see what's actually there. The Download Path in Settings must match this path exactly.</td></tr>
<tr><td>Same tracks downloading multiple times</td><td>Post-processing fails so SoulSync thinks the track was never downloaded successfully. On resume, it tries again.</td><td>Fix the folder paths first. Once post-processing works, files move to Transfer and SoulSync knows they exist.</td></tr>
<tr><td>Files not renamed properly</td><td>Post-processing isn't running (path mismatch) or file organization is disabled in Settings</td><td>Verify File Organization is enabled in <strong>Settings &rarr; Processing & Organization</strong>. Fix Download Path first.</td></tr>
<tr><td>Permission denied in logs</td><td>Container user can't write to the Transfer folder on the host</td><td>Set PUID/PGID to match the host user that owns the music folder. Run <code>chmod -R 755</code> on the Transfer host folder.</td></tr>
<tr><td>Media server doesn't see new files</td><td>Transfer Path doesn't map to the folder your media server monitors</td><td>Ensure the <strong>host path</strong> in your SoulSync volume mount (<code>/mnt/media/music:/app/Transfer</code>) is the same folder Plex/Jellyfin/Navidrome watches.</td></tr>
<tr><td>slskd downloads work fine on their own but not through SoulSync</td><td>slskd's download folder and SoulSync's Download Path point to different physical locations</td><td>Both containers must mount the <strong>same host directory</strong>. Check the left side of <code>:</code> in both docker-compose volume entries &mdash; they must match.</td></tr>
<tr><td>Files download but never transfer</td><td>App settings not configured &mdash; docker-compose volumes are set but SoulSync Settings still have defaults or wrong paths</td><td>Open <strong>Settings &rarr; Download Settings</strong> and set Input Path + Output Path to your <strong>container-side</strong> mount paths.</td></tr>
<tr><td>Post-processing log is empty</td><td>SoulSync can't find the downloaded file at the expected path &mdash; the Input Path in Settings doesn't match where slskd actually saves files inside the container</td><td>Run <code>docker exec soulsync-webui ls /app/downloads</code> to see what's actually there. The Input Path in Settings must match this path exactly.</td></tr>
<tr><td>Same tracks downloading multiple times</td><td>Post-processing fails so SoulSync thinks the track was never downloaded successfully. On resume, it tries again.</td><td>Fix the folder paths first. Once post-processing works, files move to the output folder and SoulSync knows they exist.</td></tr>
<tr><td>Files not renamed properly</td><td>Post-processing isn't running (path mismatch) or file organization is disabled in Settings</td><td>Verify File Organization is enabled in <strong>Settings &rarr; Processing & Organization</strong>. Fix Input Path first.</td></tr>
<tr><td>Permission denied in logs</td><td>Container user can't write to the output folder on the host</td><td>Set PUID/PGID to match the host user that owns the music folder. Run <code>chmod -R 755</code> on the output host folder.</td></tr>
<tr><td>Media server doesn't see new files</td><td>Output Path doesn't map to the folder your media server monitors</td><td>Ensure the <strong>host path</strong> in your SoulSync volume mount (<code>/mnt/media/music:/app/Transfer</code>) is the same folder Plex/Jellyfin/Navidrome watches.</td></tr>
<tr><td>slskd downloads work fine on their own but not through SoulSync</td><td>slskd's download folder and SoulSync's Input Path point to different physical locations</td><td>Both containers must mount the <strong>same host directory</strong>. Check the left side of <code>:</code> in both docker-compose volume entries &mdash; they must match.</td></tr>
</tbody>
</table>
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div><strong>Still stuck?</strong> Enable DEBUG logging in Settings, download a single track, and check <code>logs/app.log</code>. The post-processing log will show exactly where the file pipeline breaks &mdash; whether it's a path not found, permission denied, or verification failure. If the post-processing log is empty, the issue is almost certainly a path mismatch (SoulSync never found the file to process).</div></div>
@ -392,7 +392,7 @@ const DOCS_SECTIONS = [
<div class="docs-subsection" id="wf-download">
<h3 class="docs-subsection-title">How to: Download an Album</h3>
<p class="docs-text"><strong>Goal:</strong> Find an album and download it to your library with full metadata, cover art, and proper file organization.</p>
<p class="docs-text"><strong>Prerequisites:</strong> At least one download source connected (Soulseek, YouTube, Tidal, or Qobuz). Download and Transfer paths configured.</p>
<p class="docs-text"><strong>Prerequisites:</strong> At least one download source connected (Soulseek, YouTube, Tidal, or Qobuz). Input and Output paths configured.</p>
<ol class="docs-steps">
<li><strong>Open Search</strong> &mdash; Click the Search page in the sidebar (make sure Enhanced Search is active)</li>
<li><strong>Type the album name</strong> &mdash; Results appear in a categorized dropdown: Artists, Albums, Singles & EPs, Tracks</li>
@ -401,7 +401,7 @@ const DOCS_SECTIONS = [
<li><strong>Click Download</strong> &mdash; SoulSync searches for each track, downloads, tags, and organizes the files automatically</li>
</ol>
${docsImg('wf-download-album.gif', 'Downloading an album')}
<p class="docs-text"><strong>Result:</strong> Tracks appear in your Transfer folder as <code>Artist/Album/01 - Title.flac</code> and your media server is notified to scan.</p>
<p class="docs-text"><strong>Result:</strong> Tracks appear in your output folder as <code>Artist/Album/01 - Title.flac</code> and your media server is notified to scan.</p>
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div>If a track fails to download, click the retry icon or use the candidate selector to pick an alternative source file from a different user.</div></div>
</div>
<div class="docs-subsection" id="wf-sync">
@ -432,7 +432,7 @@ const DOCS_SECTIONS = [
<h3 class="docs-subsection-title">How to: Import Existing Music</h3>
<p class="docs-text"><strong>Goal:</strong> Bring music files you already have into SoulSync with proper metadata and organization.</p>
<ol class="docs-steps">
<li><strong>Place files in your staging folder</strong> &mdash; Put album folders (e.g., <code>Artist - Album/</code>) in the Staging path configured in Settings</li>
<li><strong>Place files in your import folder</strong> &mdash; Put album folders (e.g., <code>Artist - Album/</code>) in the Import Path configured in Settings</li>
<li><strong>Go to the Import page</strong> &mdash; SoulSync detects the files and suggests album matches</li>
<li><strong>Search for the correct album</strong> &mdash; If the auto-suggestion is wrong, search Spotify/iTunes for the right album</li>
<li><strong>Match tracks</strong> &mdash; Drag-and-drop files onto the correct track slots, or click Auto-Match</li>
@ -450,7 +450,7 @@ const DOCS_SECTIONS = [
<li><strong>Click Test Connection</strong> &mdash; Verify the connection is working. A green checkmark confirms success</li>
</ol>
${docsImg('wf-media-server.gif', 'Connecting media server')}
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div>Make sure your Transfer Path points to the same folder your media server monitors. This is how new downloads automatically appear in your library.</div></div>
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div>Make sure your Output Path points to the same folder your media server monitors. This is how new downloads automatically appear in your library.</div></div>
</div>
`
},
@ -576,7 +576,7 @@ const DOCS_SECTIONS = [
<thead><tr><th>Job</th><th>What It Does</th></tr></thead>
<tbody>
<tr><td>Track Number Repair</td><td>Fixes missing or incorrect track numbers by comparing against official tracklists</td></tr>
<tr><td>Orphan File Detector</td><td>Finds audio files in your transfer folder not tracked in the database. Can move to staging or delete.</td></tr>
<tr><td>Orphan File Detector</td><td>Finds audio files in your output folder not tracked in the database. Can move to import folder or delete.</td></tr>
<tr><td>Dead File Cleaner</td><td>Removes database entries pointing to files that no longer exist on disk</td></tr>
<tr><td>Duplicate Detector</td><td>Identifies duplicate tracks by fingerprint or metadata match</td></tr>
<tr><td>AcoustID Scanner</td><td>Batch audio fingerprint verification across your library</td></tr>
@ -834,7 +834,7 @@ const DOCS_SECTIONS = [
<li><strong>AcoustID Fingerprint Verification</strong> &mdash; If AcoustID is configured, the downloaded file is fingerprinted and compared against the expected track. Title and artist are fuzzy-matched (title &ge; 70% similarity, artist &ge; 60%). Files that fail verification are <strong>quarantined</strong> instead of added to your library. <em>Note: AcoustID is skipped for streaming sources (Tidal, Qobuz, Deezer, HiFi) since files are downloaded by exact track ID. However, streaming search results are still verified by artist and title matching before download to prevent wrong-track matches (e.g. same title, different artist).</em></li>
<li><strong>Metadata Tagging</strong> &mdash; The file is tagged with official metadata: title, artist, album artist, album, track number, disc number, year, genre, and composer. Tags are written using Mutagen (supports MP3, FLAC, OGG, M4A).</li>
<li><strong>Cover Art Embedding</strong> &mdash; Album artwork is downloaded from the metadata source and embedded directly into the audio file.</li>
<li><strong>File Organization</strong> &mdash; The file is renamed and moved to your transfer path following customizable templates. Separate templates for albums, singles, and playlists are configured in Settings. Available variables include <code>$artist</code>, <code>$album</code>, <code>$title</code>, <code>$track</code>, <code>$year</code>, <code>$quality</code>, and <code>$albumtype</code> (resolves to Album, Single, EP, or Compilation). For <strong>multi-disc albums</strong>, a <code>Disc N/</code> subfolder is automatically created when the album has more than one disc (or use <code>$disc</code> for zero-padded "01" or <code>$discnum</code> for unpadded "1" in your template for manual control).</li>
<li><strong>File Organization</strong> &mdash; The file is renamed and moved to your output path following customizable templates. Separate templates for albums, singles, and playlists are configured in Settings. Available variables include <code>$artist</code>, <code>$album</code>, <code>$title</code>, <code>$track</code>, <code>$year</code>, <code>$quality</code>, and <code>$albumtype</code> (resolves to Album, Single, EP, or Compilation). For <strong>multi-disc albums</strong>, a <code>Disc N/</code> subfolder is automatically created when the album has more than one disc (or use <code>$disc</code> for zero-padded "01" or <code>$discnum</code> for unpadded "1" in your template for manual control).</li>
<li><strong>Lyrics (LRC)</strong> &mdash; Synced lyrics are fetched from the LRClib API and saved as <code>.lrc</code> sidecar files alongside the audio file. Compatible media players (foobar2000, MusicBee, Plex, etc.) will display time-synced lyrics automatically. Falls back to plain-text lyrics if synced versions aren't available.</li>
<li><strong>Lossy Copy</strong> &mdash; If enabled in settings, a lower-bitrate copy is created alongside the original (useful for mobile device syncing).</li>
<li><strong>Media Server Scan</strong> &mdash; Your media server (Plex/Jellyfin) is notified to scan for the new file. Navidrome auto-detects changes.</li>
@ -1111,8 +1111,8 @@ const DOCS_SECTIONS = [
<tr><td><strong>Backup Database</strong></td><td>Create a timestamped database backup</td></tr>
<tr><td><strong>Refresh Beatport Cache</strong></td><td>Scrape Beatport homepage and warm the data cache</td></tr>
<tr><td><strong>Clean Search History</strong></td><td>Remove old searches from Soulseek (keeps 50 most recent)</td></tr>
<tr><td><strong>Clean Completed Downloads</strong></td><td>Clear completed downloads and empty directories from the download folder</td></tr>
<tr><td><strong>Full Cleanup</strong></td><td>Clear quarantine, download queue, staging folder, and search history in one sweep</td></tr>
<tr><td><strong>Clean Completed Downloads</strong></td><td>Clear completed downloads and empty directories from the input folder</td></tr>
<tr><td><strong>Full Cleanup</strong></td><td>Clear quarantine, download queue, import folder, and search history in one sweep</td></tr>
<tr><td><strong>Notify Only</strong></td><td>No action &mdash; just trigger notifications</td></tr>
</tbody>
</table>
@ -1250,7 +1250,7 @@ const DOCS_SECTIONS = [
<h3 class="docs-subsection-title">Library Issues</h3>
<p class="docs-text">The Issues page tracks problems detected in your library by the repair worker. Issues are categorized by type and severity:</p>
<ul class="docs-list">
<li><strong>Orphan files</strong> &mdash; Audio files in your transfer folder not tracked in the database</li>
<li><strong>Orphan files</strong> &mdash; Audio files in your output folder not tracked in the database</li>
<li><strong>Dead references</strong> &mdash; Database entries pointing to files that no longer exist on disk</li>
<li><strong>Duplicate tracks</strong> &mdash; Multiple copies of the same track detected by fingerprint or metadata</li>
<li><strong>Missing cover art</strong> &mdash; Albums or tracks without embedded artwork</li>
@ -1275,16 +1275,16 @@ const DOCS_SECTIONS = [
content: () => `
<div class="docs-subsection" id="imp-setup">
<h3 class="docs-subsection-title">Staging Setup</h3>
<p class="docs-text">Set your <strong>staging folder path</strong> in Settings &rarr; Download Settings. Place audio files you want to import into this folder. SoulSync scans the folder and detects albums from the file structure.</p>
<p class="docs-text">Set your <strong>import folder path</strong> in Settings &rarr; Download Settings. Place audio files you want to import into this folder. SoulSync scans the folder and detects albums from the file structure.</p>
<p class="docs-text">Place albums in subfolders (e.g., <code>Artist - Album/</code>) and loose singles at the root level.</p>
<p class="docs-text">The import page header shows the total files in staging and their combined size.</p>
${docsImg('imp-staging.jpg', 'Import staging page')}
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div><strong>Files not showing up?</strong> Check that your staging folder path is correct in Settings and that the folder has read permissions. Docker users: make sure the staging volume mount is configured in your docker-compose.yml.</div></div>
<div class="docs-callout tip"><span class="docs-callout-icon">&#x1F4A1;</span><div><strong>Files not showing up?</strong> Check that your import folder path is correct in Settings and that the folder has read permissions. Docker users: make sure the import volume mount is configured in your docker-compose.yml.</div></div>
</div>
<div class="docs-subsection" id="imp-workflow">
<h3 class="docs-subsection-title">Import Workflow</h3>
<ol class="docs-steps">
<li>Place audio files in your staging folder</li>
<li>Place audio files in your import folder</li>
<li>Navigate to the <strong>Import</strong> page &mdash; SoulSync detects and suggests album matches</li>
<li>Search for the correct album on Spotify/iTunes if the suggestion is wrong</li>
<li><strong>Match tracks</strong> &mdash; Drag-and-drop staged files onto album track slots, or let auto-match attempt it</li>
@ -1304,7 +1304,7 @@ const DOCS_SECTIONS = [
<li><strong>Drag & Drop</strong> &mdash; Manually drag staged files onto the correct album track slots</li>
<li><strong>Conflict Detection</strong> &mdash; Highlights when a file matches multiple tracks or when tracks are unmatched</li>
</ul>
<p class="docs-text">After matching, the import process tags files with the official metadata (title, artist, album, track number, cover art) and moves them to your transfer path following the standard file organization template.</p>
<p class="docs-text">After matching, the import process tags files with the official metadata (title, artist, album, track number, cover art) and moves them to your output path following the standard file organization template.</p>
</div>
<div class="docs-subsection" id="imp-textfile">
<h3 class="docs-subsection-title">Import from Text File</h3>
@ -1422,9 +1422,9 @@ const DOCS_SECTIONS = [
<h3 class="docs-subsection-title">Download Settings</h3>
<ul class="docs-list">
<li><strong>Download Source Mode</strong> &mdash; Soulseek, YouTube, Tidal, Qobuz, HiFi, Deezer, or Hybrid. Hybrid tries your primary source first, then falls back to alternates with configurable priority via drag-and-drop. Each streaming source has its own quality dropdown and an <strong>Allow quality fallback</strong> toggle. See <em>Download Sources</em> and <em>Quality Profiles</em> in the Music Downloads section for details.</li>
<li><strong>Download Path</strong> &mdash; The folder where files are initially downloaded. This <strong>must match</strong> the folder your download source (slskd) writes to. In Docker, this is the container-side mount point (e.g., <code>/app/downloads</code>), not the host path. SoulSync monitors this folder for completed downloads to begin post-processing.</li>
<li><strong>Transfer Path</strong> &mdash; The final destination for processed music files. After tagging, renaming, and organizing, files are moved here. This <strong>must</strong> point to your media server's monitored music folder (the folder Plex/Jellyfin/Navidrome watches for new content). In Docker, use the container-side path (e.g., <code>/app/Transfer</code>).</li>
<li><strong>Staging Path</strong> &mdash; Folder for the Import feature (files placed here appear on the Import page). Separate from the download/transfer pipeline.</li>
<li><strong>Input Path</strong> &mdash; The folder where files are initially downloaded. This <strong>must match</strong> the folder your download source (slskd) writes to. In Docker, this is the container-side mount point (e.g., <code>/app/downloads</code>), not the host path. SoulSync monitors this folder for completed downloads to begin post-processing.</li>
<li><strong>Output Path</strong> &mdash; The final destination for processed music files. After tagging, renaming, and organizing, files are moved here. This <strong>must</strong> point to your media server's monitored music folder (the folder Plex/Jellyfin/Navidrome watches for new content). In Docker, use the container-side path (e.g., <code>/app/Transfer</code>).</li>
<li><strong>Import Path</strong> &mdash; Folder for the Import feature (files placed here appear on the Import page). Separate from the input/output pipeline.</li>
<li><strong>iTunes Country</strong> &mdash; Storefront region for iTunes/Apple Music lookups (US, GB, FR, JP, etc.). Changes apply immediately to all searches without restarting. ID-based lookups automatically try up to 10 regional storefronts as fallback when the primary country returns no results.</li>
<li><strong>Lossy Copy</strong> &mdash; When enabled, creates a lower-bitrate MP3 copy of every downloaded file. Configure the output bitrate (default 320kbps) and output folder. Optionally delete the original lossless file after creating the lossy copy. Useful for syncing to mobile devices or streaming servers with bandwidth constraints.</li>
<li><strong>Content Filtering</strong> &mdash; Toggle explicit content filtering to control whether explicit tracks appear in search results and downloads.</li>
@ -1439,7 +1439,7 @@ const DOCS_SECTIONS = [
<li><strong>AcoustID Verification</strong> &mdash; Toggle on/off. When enabled, every download is fingerprinted and compared against the expected track. Failed matches are quarantined.</li>
<li><strong>Metadata Enhancement</strong> &mdash; Master toggle for all enrichment workers. When disabled, no background metadata fetching occurs.</li>
<li><strong>Embed Album Art</strong> &mdash; Automatically embed cover art into audio file tags during post-processing.</li>
<li><strong>File Organization</strong> &mdash; Toggle automatic file renaming and folder placement. When disabled, files stay in the download folder as-is.</li>
<li><strong>File Organization</strong> &mdash; Toggle automatic file renaming and folder placement. When disabled, files stay in the input folder as-is.</li>
<li><strong>Path Template</strong> &mdash; Customize the folder structure using variables: <code>{artist}</code>, <code>{album}</code>, <code>{title}</code>, <code>{track_number}</code>, <code>{year}</code>, <code>{genre}</code>. Default: <code>{artist}/{album}/{track_number} - {title}</code></li>
<li><strong>Disc Label</strong> &mdash; Customize the multi-disc subfolder prefix (default: "Disc"). Multi-disc albums create <code>Disc 1/</code>, <code>Disc 2/</code>, etc.</li>
<li><strong>Soulseek Search Timeout</strong> &mdash; How long to wait for Soulseek search results before giving up (seconds).</li>
@ -1594,7 +1594,7 @@ const DOCS_SECTIONS = [
<h3 class="docs-subsection-title">Common Issues</h3>
<h4>Downloads complete but tracks don't appear in library</h4>
<ul class="docs-list">
<li>Check that your <strong>Transfer path</strong> is correct and writable (see Paths in debug info)</li>
<li>Check that your <strong>Output Path</strong> is correct and writable (see Paths in debug info)</li>
<li>If using a media server, trigger a library scan after downloads complete</li>
<li>Check <code>post_processing.log</code> for file move errors</li>
</ul>
@ -2502,9 +2502,9 @@ function initializeDocsPage() {
text += '── Paths ──\n';
const pathStatus = (exists, writable) => exists ? (writable ? ck + ' ok' : ck + ' exists ' + ex + ' not writable') : ex + ' missing';
text += `Download: ${data.paths?.download_path || '(not set)'} [${pathStatus(data.paths?.download_path_exists, data.paths?.download_path_writable)}]\n`;
text += `Transfer: ${data.paths?.transfer_folder || '(not set)'} [${pathStatus(data.paths?.transfer_folder_exists, data.paths?.transfer_folder_writable)}]\n`;
text += `Staging: ${data.paths?.staging_folder ? data.paths.staging_folder + ' [' + (data.paths.staging_folder_exists ? ck + ' ok' : ex + ' missing') + ']' : '(not configured — optional)'}\n`;
text += `Input: ${data.paths?.download_path || '(not set)'} [${pathStatus(data.paths?.download_path_exists, data.paths?.download_path_writable)}]\n`;
text += `Output: ${data.paths?.transfer_folder || '(not set)'} [${pathStatus(data.paths?.transfer_folder_exists, data.paths?.transfer_folder_writable)}]\n`;
text += `Import: ${data.paths?.staging_folder ? data.paths.staging_folder + ' [' + (data.paths.staging_folder_exists ? ck + ' ok' : ex + ' missing') + ']' : '(not configured — optional)'}\n`;
if (data.paths?.music_videos_path) {
text += `Videos: ${data.paths.music_videos_path} [${data.paths.music_videos_path_exists ? ck + ' ok' : ex + ' missing'}]\n`;
}

View file

@ -125,7 +125,7 @@ const HELPER_CONTENT = {
},
'.nav-button[data-page="import"]': {
title: 'Music Import',
description: 'Import music files from your staging folder. SoulSync identifies tracks using AcoustID fingerprinting, matches them to metadata, and organizes them into your library with proper tagging.',
description: 'Import music files from your import folder. SoulSync identifies tracks using AcoustID fingerprinting, matches them to metadata, and organizes them into your library with proper tagging.',
docsId: 'import'
},
'.nav-button[data-page="settings"]': {
@ -230,7 +230,7 @@ const HELPER_CONTENT = {
},
'#import-button': {
title: 'Quick Import',
description: 'Shortcut to the Import page. Drop music files in your staging folder and import them into your library with metadata matching and tagging.',
description: 'Shortcut to the Import page. Drop music files in your import folder and import them into your library with metadata matching and tagging.',
docsId: 'import'
},
@ -1856,16 +1856,16 @@ const HELPER_CONTENT = {
'.import-page-container': {
title: 'Import Music',
description: 'Import audio files from your staging folder into your library. Match files to album metadata, tag them, and organize into your collection.',
description: 'Import audio files from your import folder into your library. Match files to album metadata, tag them, and organize into your collection.',
docsId: 'import'
},
'.import-page-refresh-btn': {
title: 'Refresh',
description: 'Re-scan your staging folder for new audio files. Use after dropping new files into the staging path.',
description: 'Re-scan your import folder for new audio files. Use after dropping new files in.',
},
'#import-staging-bar': {
title: 'Staging Folder',
description: 'Shows your configured staging folder path and the number of audio files found. Set the staging path in Settings → Download Settings.',
title: 'Import Folder',
description: 'Shows your configured import folder path and the number of audio files found. Set the import path in Settings → Download Settings.',
docsId: 'imp-setup'
},
'#import-page-queue': {
@ -1874,7 +1874,7 @@ const HELPER_CONTENT = {
},
'#import-page-tab-album': {
title: 'Albums Tab',
description: 'Import complete albums. Search for an album, match staging files to tracks, then process. Suggestions appear automatically from your staging folder.',
description: 'Import complete albums. Search for an album, match import files to tracks, then process. Suggestions appear automatically from your import folder.',
docsId: 'imp-workflow'
},
'#import-page-tab-singles': {
@ -1884,15 +1884,15 @@ const HELPER_CONTENT = {
},
'#import-page-suggestions-grid': {
title: 'Suggestions',
description: 'Albums automatically detected from your staging folder based on folder names and file metadata. Click a suggestion to start the matching process.',
description: 'Albums automatically detected from your import folder based on folder names and file metadata. Click a suggestion to start the matching process.',
},
'#import-page-album-search-input': {
title: 'Album Search',
description: 'Search your metadata source for an album to match against staging files. Enter the album name or artist + album.',
description: 'Search your metadata source for an album to match against import files. Enter the album name or artist + album.',
},
'#import-page-album-match-section': {
title: 'Track Matching',
description: 'Match your staging files to album tracks. Drag files from the unmatched pool onto tracks, or let auto-matching do it. Green = matched, red = unmatched.',
description: 'Match your import files to album tracks. Drag files from the unmatched pool onto tracks, or let auto-matching do it. Green = matched, red = unmatched.',
tips: [
'Drag and drop files from the unmatched pool to track slots',
'"Re-match Automatically" re-runs the matching algorithm',
@ -1902,7 +1902,7 @@ const HELPER_CONTENT = {
},
'#import-page-unmatched-pool': {
title: 'Unmatched Files',
description: 'Audio files in your staging folder that haven\'t been matched to an album track yet. Drag them onto the correct track slot above.',
description: 'Audio files in your import folder that haven\'t been matched to an album track yet. Drag them onto the correct track slot above.',
docsId: 'imp-matching'
},
'#import-page-album-process-btn': {
@ -1911,7 +1911,7 @@ const HELPER_CONTENT = {
},
'#import-page-singles-list': {
title: 'Singles List',
description: 'Individual audio files in your staging folder. Select files and click "Process Selected" to identify and import them as single tracks.',
description: 'Individual audio files in your import folder. Select files and click "Process Selected" to identify and import them as single tracks.',
docsId: 'imp-singles'
},
'#import-page-singles-process-btn': {
@ -2696,9 +2696,9 @@ const HELPER_TOURS = {
icon: '📥',
steps: [
// Header
{ page: 'import', selector: '.import-page-header', title: 'Import Music', description: 'Import audio files from your staging folder into your organized library. Files are matched to album metadata, tagged, and moved to the correct location.' },
{ page: 'import', selector: '.import-page-staging-bar', title: 'Staging Folder', description: 'Shows your configured staging folder path and stats (file count, total size). This is where you drop audio files before importing. Configure the path in Settings → Downloads.' },
{ page: 'import', selector: '.import-page-refresh-btn', title: 'Refresh', description: 'Re-scans your staging folder for new audio files. Hit this after dropping new files in.' },
{ page: 'import', selector: '.import-page-header', title: 'Import Music', description: 'Import audio files from your import folder into your organized library. Files are matched to album metadata, tagged, and moved to the correct location.' },
{ page: 'import', selector: '.import-page-staging-bar', title: 'Import Folder', description: 'Shows your configured import folder path and stats (file count, total size). This is where you drop audio files before importing. Configure the path in Settings → Downloads.' },
{ page: 'import', selector: '.import-page-refresh-btn', title: 'Refresh', description: 'Re-scans your import folder for new audio files. Hit this after dropping new files in.' },
// Queue
{ page: 'import', selector: '#import-page-queue', title: 'Processing Queue', description: 'When you process albums or singles, jobs appear here with progress indicators. "Clear finished" removes completed jobs from the list.' },
@ -2707,7 +2707,7 @@ const HELPER_TOURS = {
{ page: 'import', selector: '.import-page-tab-bar', title: 'Albums vs Singles', description: 'Two modes: Albums tab matches full albums to metadata (cover art, track numbers, disc info). Singles tab processes individual files one at a time.' },
// Album workflow
{ page: 'import', selector: '#import-page-suggestions', title: 'Album Suggestions', description: 'The importer analyzes your staging files and suggests album matches based on embedded tags. Click a suggestion to start the matching process.' },
{ page: 'import', selector: '#import-page-suggestions', title: 'Album Suggestions', description: 'The importer analyzes your import files and suggests album matches based on embedded tags. Click a suggestion to start the matching process.' },
{ page: 'import', selector: '#import-page-album-search-input', title: 'Album Search', description: 'If suggestions don\'t match, search manually. Type an album name, click Search, and select the correct result.' },
{ page: 'import', selector: '#import-page-album-search-input', title: 'Track Matching', description: 'After selecting an album, you\'ll see a track matching table. Files are auto-matched to tracks by name/number. Drag unmatched files from the pool to the correct track slot, then click "Process Album".' },
@ -3612,7 +3612,7 @@ const WHATS_NEW = {
// --- April 17, 2026 ---
{ date: 'April 17, 2026' },
{ title: 'SoulSync Standalone Library', desc: 'New "Standalone" server option — manage your library without Plex, Jellyfin, or Navidrome. Downloads and imports write directly to the library database with pre-populated enrichment IDs. Deep scan finds untracked files and cleans stale records. Select in Settings → Connections', page: 'settings' },
{ title: 'Auto-Import', desc: 'Background staging folder watcher that automatically identifies and imports music. Three strategies: audio tags, folder name parsing, and AcoustID fingerprinting. Confidence-gated: 90%+ auto-imports, 70-90% queued for review, below 70% left for manual. Enable on the Import page Auto tab', page: 'import' },
{ title: 'Auto-Import', desc: 'Background import folder watcher that automatically identifies and imports music. Three strategies: audio tags, folder name parsing, and AcoustID fingerprinting. Confidence-gated: 90%+ auto-imports, 70-90% queued for review, below 70% left for manual. Enable on the Import page Auto tab', page: 'import' },
{ title: 'Wishlist Nebula', desc: 'Wishlist redesigned as an interactive artist orb visualization. Each artist is a glowing orb with their photo — album fans and single moons orbit around them. Click orbs to expand, download albums/singles directly. Processing state shows live progress', page: 'wishlist' },
{ title: 'Automation Group Management', desc: 'Rename, delete, and bulk-toggle automation groups. Drag-and-drop automations between groups. Right-click group headers for context menu', page: 'automations' },
{ title: 'Bidirectional Artist Sync', desc: 'Artist Sync button now pulls new content from your media server AND removes stale library entries no longer on the server. Deep scan mode fetches full metadata for new tracks', page: 'library' },
@ -3629,7 +3629,7 @@ const WHATS_NEW = {
{ title: 'Downloads Batch Panel', desc: 'Downloads page now shows a batch context panel on the right side. Each active batch (wishlist, sync, album download) gets a color-coded card with progress, cancel button, and expandable track list. Color indicators on download rows link them to their batch. Completed batch history shows the last 7 days', page: 'active-downloads' },
{ title: 'Fix Unknown Artist on Wishlist Downloads', desc: 'Adding tracks to wishlist from a playlist download modal was storing "Unknown Artist" as the artist context. Now resolves the artist per-track from the track\'s own metadata instead of the playlist-level artist which is only set for album downloads' },
{ title: 'Fix Download Modal Freezing Mid-Download', desc: 'Download modals (wishlist, sync, album) would freeze and stop updating after the first track completed. Caused by M3U auto-save firing every 2 seconds during downloads, exhausting Flask server threads. Now saves M3U once on completion only' },
{ title: 'Auto-Import Improvements', desc: 'Recursive staging folder scan (any folder depth), single file support, expandable track match details, stats bar with filters, Scan Now button, Approve All / Clear History batch actions. Tag-based identification preferred over weak metadata matches. AcoustID fallback for untagged files. Race condition fix prevents duplicate processing', page: 'import' },
{ title: 'Auto-Import Improvements', desc: 'Recursive import folder scan (any folder depth), single file support, expandable track match details, stats bar with filters, Scan Now button, Approve All / Clear History batch actions. Tag-based identification preferred over weak metadata matches. AcoustID fallback for untagged files. Race condition fix prevents duplicate processing', page: 'import' },
{ title: 'Album Delete with File Removal', desc: 'Enhanced library album delete now offers "Delete Files Too" option alongside "Remove from Library" — deletes audio files from disk and cleans up empty album folders', page: 'library' },
// --- April 15, 2026 ---
@ -3650,7 +3650,7 @@ const WHATS_NEW = {
// --- April 14, 2026 ---
{ date: 'April 14, 2026' },
{ title: 'Fix Staging Files Ignoring Path Template', desc: 'Files matched from the Staging folder were copied to the transfer root with their original filename instead of applying the configured path template. Post-processing now receives full artist/album context for staging matches' },
{ title: 'Fix Import Files Ignoring Path Template', desc: 'Files matched from the import folder were copied to the output root with their original filename instead of applying the configured path template. Post-processing now receives full artist/album context for import matches' },
// --- April 4, 2026 ---
{ date: 'April 4, 2026' },

View file

@ -23288,7 +23288,7 @@ const TOOL_HELP_CONTENT = {
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>
<p>The Duplicate Cleaner scans your output 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>
@ -23937,7 +23937,7 @@ const TOOL_HELP_CONTENT = {
title: 'Duplicate Scan Done',
content: `
<h4>What is this trigger?</h4>
<p>Fires when the duplicate cleaner finishes scanning your transfer folder for duplicate audio files.</p>
<p>Fires when the duplicate cleaner finishes scanning your output folder for duplicate audio files.</p>
<h4>Available variables for notifications</h4>
<p><code>{files_scanned}</code>, <code>{duplicates_found}</code>, <code>{space_freed}</code></p>
@ -24135,7 +24135,7 @@ const TOOL_HELP_CONTENT = {
title: 'Run Duplicate Cleaner',
content: `
<h4>What does this action do?</h4>
<p>Scans your transfer folder for duplicate audio files (same filename, different format) and removes the lower-quality version. For example, if you have both <code>Song.flac</code> and <code>Song.mp3</code>, the MP3 is removed.</p>
<p>Scans your output folder for duplicate audio files (same filename, different format) and removes the lower-quality version. For example, if you have both <code>Song.flac</code> and <code>Song.mp3</code>, the MP3 is removed.</p>
<h4>Safety</h4>
<p>Removed files are moved to a <code>deleted/</code> subfolder, not permanently deleted. You can recover them if needed.</p>
@ -24227,12 +24227,12 @@ const TOOL_HELP_CONTENT = {
title: 'Clean Completed Downloads',
content: `
<h4>What does this action do?</h4>
<p>Clears completed downloads from the transfer list and removes any empty directories left behind in the staging folder.</p>
<p>Clears completed downloads from the transfer list and removes any empty directories left behind in the import folder.</p>
<h4>Good for</h4>
<ul>
<li>Automatic cleanup after batch downloads</li>
<li>Preventing staging folder clutter</li>
<li>Preventing import folder clutter</li>
<li>Chaining after a batch complete trigger</li>
</ul>
`
@ -24245,8 +24245,8 @@ const TOOL_HELP_CONTENT = {
<ol>
<li><strong>Clear Quarantine</strong> permanently deletes all quarantined files</li>
<li><strong>Clear Download Queue</strong> removes completed, errored, and cancelled downloads from Soulseek</li>
<li><strong>Sweep Empty Directories</strong> removes empty folders left behind in the downloads directory</li>
<li><strong>Sweep Staging Folder</strong> removes empty directories from the import staging area</li>
<li><strong>Sweep Empty Directories</strong> removes empty folders left behind in the input directory</li>
<li><strong>Sweep Import Folder</strong> removes empty directories from the import folder</li>
<li><strong>Clean Search History</strong> trims old Soulseek search queries</li>
</ol>
@ -65528,7 +65528,7 @@ async function fixAllMatchingFindings() {
} else if (fixAction === 'staging') {
if (!await showConfirmDialog({
title: 'Move to Staging',
message: `Move ${_repairFindingsTotal} orphan files to the staging folder? Files are NOT deleted — you can review and import them from staging.`,
message: `Move ${_repairFindingsTotal} orphan files to the import folder? Files are NOT deleted — you can review and import them.`,
confirmText: 'Move All to Staging',
destructive: false
})) return;
@ -66517,12 +66517,12 @@ async function importPageRefreshStaging() {
const resp = await fetch('/api/import/staging/files');
const data = await resp.json();
if (!data.success) {
document.getElementById('import-page-staging-path').textContent = `Staging folder: error`;
document.getElementById('import-page-staging-path').textContent = `Import folder: error`;
return;
}
importPageState.stagingFiles = data.files || [];
document.getElementById('import-page-staging-path').textContent = `Staging: ${data.staging_path || 'Not configured'}`;
document.getElementById('import-page-staging-path').textContent = `Import: ${data.staging_path || 'Not configured'}`;
const totalSize = importPageState.stagingFiles.reduce((s, f) => s + (f.size || 0), 0);
const sizeStr = totalSize > 1073741824 ? `${(totalSize / 1073741824).toFixed(1)} GB`
@ -66669,7 +66669,7 @@ async function _autoImportLoadResults() {
if (!data.success || !data.results || data.results.length === 0) {
if (!container.querySelector('.auto-import-card')) {
container.innerHTML = `<div class="auto-import-empty">
<p>No imports yet. Drop album folders or single tracks into your staging directory.</p>
<p>No imports yet. Drop album folders or single tracks into your import folder.</p>
</div>`;
}
// Hide stats and filters
@ -67391,7 +67391,7 @@ function importPageRenderSinglesList() {
const files = importPageState.stagingFiles;
if (files.length === 0) {
list.innerHTML = '<div class="import-page-empty-state">No audio files found in staging folder</div>';
list.innerHTML = '<div class="import-page-empty-state">No audio files found in import folder</div>';
return;
}
@ -71194,7 +71194,7 @@ const _RESULT_DISPLAY_MAP = {
],
'full_cleanup': [
{ key: 'quarantine_removed', label: 'Quarantine Removed' },
{ key: 'staging_removed', label: 'Staging Dirs Removed' },
{ key: 'staging_removed', label: 'Import Dirs Removed' },
{ key: 'total_removed', label: 'Total Items Removed' },
],
'playlist_pipeline': [

View file

@ -504,10 +504,10 @@ function _renderPaths(el) {
<h2>Paths & Media Server</h2>
<p class="setup-subtitle">Where should downloaded music go?</p>
<div class="setup-info-box">
<strong>Two-folder system:</strong> Music downloads to the <strong>Download Folder</strong> first as raw files. After post-processing (metadata tagging, file organization), finished tracks are moved to the <strong>Transfer Folder</strong> organized into Artist/Album subfolders. Point your media server at the Transfer folder.
<strong>Two-folder system:</strong> Music downloads to the <strong>Input Folder</strong> first as raw files. After post-processing (metadata tagging, file organization), finished tracks are moved to the <strong>Output Folder</strong> organized into Artist/Album subfolders. Point your media server at the output folder.
</div>
<div class="setup-input-group">
<label>Download Folder (where raw downloads land)</label>
<label>Input Folder (where raw downloads land)</label>
<div class="setup-path-row">
<input class="setup-input setup-path-input" type="text" id="setup-download-path"
placeholder="/app/downloads"
@ -520,7 +520,7 @@ function _renderPaths(el) {
</div>
</div>
<div class="setup-input-group">
<label>Music Library / Transfer Folder (organized music)</label>
<label>Music Library / Output Folder (organized music)</label>
<div class="setup-path-row">
<input class="setup-input setup-path-input" type="text" id="setup-transfer-path"
placeholder="/app/Transfer"
@ -769,7 +769,7 @@ function _renderFirstDownload(el) {
<h2>Your First Download</h2>
<p class="setup-subtitle">Try searching for a track to see the full pipeline in action.</p>
<div class="setup-info-box">
<strong>How it works:</strong> Type a song name below. SoulSync searches your metadata source for the track, then finds the best matching audio file from your download source. The track is tagged with full metadata (artist, album, track number, artwork) and organized into your Transfer folder.
<strong>How it works:</strong> Type a song name below. SoulSync searches your metadata source for the track, then finds the best matching audio file from your download source. The track is tagged with full metadata (artist, album, track number, artwork) and organized into your output folder.
</div>
<div class="setup-search-wrapper">
<span class="setup-search-icon">
@ -936,7 +936,7 @@ function _renderDone(el) {
summaryRows.push({ label: 'Metadata Source', value: cap(_wizardSettings.metadata_source) });
const dlName = _wizardSettings.download_source === 'deezer_dl' ? 'Deezer' : cap(_wizardSettings.download_source);
summaryRows.push({ label: 'Download Source', value: dlName });
if (_wizardSettings.download_path) summaryRows.push({ label: 'Download Folder', value: _wizardSettings.download_path });
if (_wizardSettings.download_path) summaryRows.push({ label: 'Input Folder', value: _wizardSettings.download_path });
if (_wizardSettings.transfer_path) summaryRows.push({ label: 'Music Library', value: _wizardSettings.transfer_path });
if (_wizardSettings.media_server !== 'none') summaryRows.push({ label: 'Media Server', value: cap(_wizardSettings.media_server) });
if (_wizardAddedArtists.length > 0) summaryRows.push({ label: 'Artists Added', value: _wizardAddedArtists.length.toString() });