Add configurable music library paths for file resolution

New setting in Settings > Library lets users add folder paths where
their music files live. The file resolver checks these paths when
looking for library files, solving Docker path mismatches and multi-
folder libraries. Required for tag writing, streaming, and orphan
detection when the media server reports paths that differ from what
SoulSync can see. Docker users mount their music folder(s) with
read-write access and add the container-side path. Default is empty
— existing users see no change.
This commit is contained in:
Broque Thomas 2026-04-08 13:24:15 -07:00
parent 195484441e
commit f603f92868
4 changed files with 84 additions and 0 deletions

View file

@ -460,6 +460,9 @@ class ConfigManager:
"enabled": True,
"poll_interval": 30
},
"library": {
"music_paths": []
},
"import": {
"staging_path": "./Staging",
"replace_lower_quality": False

View file

@ -12913,6 +12913,18 @@ def _resolve_library_file_path(file_path):
except Exception:
pass
# Check user-configured music library paths (Settings > Library)
try:
music_paths = config_manager.get('library.music_paths', [])
if isinstance(music_paths, list):
for p in music_paths:
if p and isinstance(p, str):
resolved_p = docker_resolve_path(p.strip())
if resolved_p:
library_dirs.add(resolved_p)
except Exception:
pass
path_parts = file_path.replace('\\', '/').split('/')
# Try progressively shorter path suffixes against each candidate directory

View file

@ -5693,6 +5693,25 @@
</div>
<!-- Music Library Paths -->
<div class="settings-group" data-stg="library">
<h3>📂 Music Library Paths</h3>
<div class="help-text" style="margin-bottom: 12px;">
Tell SoulSync where your music files live. Required for tag writing, streaming, and file detection
when your media server stores files at a different path than SoulSync can see.
<strong>Docker users:</strong> mount your music folder(s) into the SoulSync container with read-write access,
then add the <em>container-side</em> path here (e.g. <code>/music</code>).
</div>
<div id="music-paths-list"></div>
<div class="form-actions" style="margin-top: 8px;">
<button class="test-button" onclick="addMusicPathRow()">+ Add Path</button>
</div>
</div>
<!-- Content Filter Settings -->
<div class="settings-group" data-stg="library">
<h3>🔞 Content Filter</h3>

View file

@ -5969,6 +5969,10 @@ async function loadSettingsData() {
document.getElementById('lossy-copy-options').style.display =
settings.lossy_copy?.enabled ? 'block' : 'none';
// Populate Music Library Paths
const _musicPaths = settings.library?.music_paths || [];
renderMusicPaths(_musicPaths);
// Populate Content Filter settings
document.getElementById('allow-explicit').checked = settings.content_filter?.allow_explicit !== false;
@ -6606,6 +6610,49 @@ async function toggleHydrabaseFromSettings() {
}
}
// ── Music Library Paths ──
function renderMusicPaths(paths) {
const container = document.getElementById('music-paths-list');
if (!container) return;
if (!paths || paths.length === 0) {
container.innerHTML = '<div style="color: rgba(255,255,255,0.3); font-size: 0.85em; padding: 4px 0;">No paths configured. Click "Add Path" to add your music folder(s).</div>';
return;
}
container.innerHTML = paths.map((p, i) => `
<div class="form-group music-path-row" style="margin-bottom: 4px;">
<input type="text" class="music-path-input" value="${escapeHtml(p)}" placeholder="/music or C:\\Music" style="flex:1;">
<button class="test-button" onclick="this.closest('.music-path-row').remove()" style="padding: 8px 12px; color: #ef5350; border-color: rgba(239,83,80,0.3);">&times;</button>
</div>
`).join('');
}
function addMusicPathRow() {
const container = document.getElementById('music-paths-list');
if (!container) return;
// Clear the "no paths" message if present
const placeholder = container.querySelector('div[style*="color: rgba"]');
if (placeholder && !container.querySelector('.music-path-row')) placeholder.remove();
const row = document.createElement('div');
row.className = 'form-group music-path-row';
row.style.marginBottom = '4px';
row.innerHTML = `
<input type="text" class="music-path-input" value="" placeholder="/music or C:\\Music" style="flex:1;">
<button class="test-button" onclick="this.closest('.music-path-row').remove()" style="padding: 8px 12px; color: #ef5350; border-color: rgba(239,83,80,0.3);">&times;</button>
`;
container.appendChild(row);
row.querySelector('input').focus();
}
function collectMusicPaths() {
const inputs = document.querySelectorAll('.music-path-input');
const paths = [];
inputs.forEach(input => {
const val = input.value.trim();
if (val) paths.push(val);
});
return paths;
}
// ── Database Maintenance ──
async function loadDbMaintenanceInfo() {
try {
@ -7079,6 +7126,9 @@ async function saveSettings(quiet = false) {
content_filter: {
allow_explicit: document.getElementById('allow-explicit').checked
},
library: {
music_paths: collectMusicPaths()
},
import: {
replace_lower_quality: document.getElementById('import-replace-lower-quality').checked
},