feat: load actual file sort tags in edit modal + remove settings hints

- backend: new GET /api/song-sorttags reads TITLESORT/ARTISTSORT/ALBUMARTISTSORT/
  ALBUMSORT directly from the file via ffprobe
- ui.js: pre-fill sort fields from romaji index, then overwrite with real file
  tags from the backend (shows actual values even if manually set)
- index.html: remove redundant hint texts in settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-25 22:42:34 +02:00
1 parent 5dda1a7101
commit 255ea1969c
3 files changed
+48 -3

No files matched your search

+37
View File
@@ -612,6 +612,43 @@ app.get('/api/musicbrainz/lookup', async (req, res) => {
}
});
// ─── SORT TAGS READ ──────────────────────────────────────────────────────────
app.get('/api/song-sorttags', async (req, res) => {
const { ndUrl, user, pass, songId } = req.query;
if (!ndUrl || !user || !pass || !songId) return res.status(400).json({ error: 'ndUrl, user, pass, songId required' });
let filePath;
try {
const salt = Math.random().toString(36).slice(2, 10);
const token = crypto.createHash('md5').update(pass + salt).digest('hex');
const ndRes = await fetch(`${ndUrl}/rest/getSong?id=${songId}&u=${encodeURIComponent(user)}&t=${token}&s=${salt}&v=1.16.1&c=novasonic&f=json`, { signal: AbortSignal.timeout(8000) });
const nd = await ndRes.json();
filePath = nd?.['subsonic-response']?.song?.path;
if (!filePath) return res.status(404).json({ error: 'Song not found' });
} catch (e) { return res.status(502).json({ error: e.message }); }
if (!path.isAbsolute(filePath)) filePath = path.join(CONFIG.musicPath, filePath);
if (!fs.existsSync(filePath)) {
const found = _findFileRecursive(CONFIG.musicPath, path.basename(filePath));
if (found) filePath = found; else return res.status(404).json({ error: 'File not found' });
}
try {
const out = await new Promise((resolve, reject) => {
const proc = spawn('ffprobe', ['-v', 'quiet', '-print_format', 'json', '-show_entries', 'format_tags', filePath]);
let buf = '';
proc.stdout.on('data', d => buf += d);
proc.on('error', reject);
proc.on('close', () => { try { resolve(JSON.parse(buf)); } catch { resolve({}); } });
});
const tags = out?.format?.tags || {};
const findTag = (...keys) => { for (const k of keys) { const v = tags[k] || tags[k.toLowerCase()] || tags[k.toUpperCase()]; if (v) return v; } return ''; };
res.json({
titleSort: findTag('TITLESORT', 'titlesort'),
artistSort: findTag('ARTISTSORT', 'artistsort'),
albumArtistSort: findTag('ALBUMARTISTSORT', 'albumartistsort', 'TSO2'),
albumSort: findTag('ALBUMSORT', 'albumsort'),
});
} catch (e) { res.status(500).json({ error: e.message }); }
});
// ─── METADATA EDIT ───────────────────────────────────────────────────────────
app.post('/api/edit-metadata', async (req, res) => {
const { ndUrl, user, pass, songId, title, artist, albumArtist, album, year, trackNum, totalTracks,
-2
View File
@@ -654,12 +654,10 @@
<span>Abspielhäufigkeit anzeigen</span>
<div id="s-show-playcount" class="toggle" onclick="this.classList.toggle('on')"></div>
</div>
<div class="hint" style="margin-top:6px">Zeigt Wiedergabezahl</div>
<div class="edit-pl-toggle-row" style="margin-top:12px">
<span>Romaji anzeigen</span>
<div id="s-show-romaji" class="toggle" onclick="this.classList.toggle('on')"></div>
</div>
<div class="hint" style="margin-top:6px">Zeigt japanische Titel und Künstlernamen in Romaji</div>
</div>
<div class="settings-section">
<h3 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
+11 -1
View File
@@ -356,12 +356,22 @@ export function toggleInfoEdit() {
document.getElementById('ie-disc').value = s.discNumber || '';
document.getElementById('ie-totaltracks').value = s.albumSongCount || '';
// Sort tags — pre-fill from romaji index when available
// Sort tags — pre-fill from romaji index, then overwrite with actual file tags if backend available
const _rm = romajiLookup(s.id);
document.getElementById('ie-titlesort').value = _rm?.titleRomaji || '';
document.getElementById('ie-artistsort').value = _rm?.artistRomaji || '';
document.getElementById('ie-albumartistsort').value = _rm?.artistRomaji || '';
document.getElementById('ie-albumsort').value = _rm?.albumRomaji || '';
if (S.cfg.backendUrl && s.id) {
const p = new URLSearchParams({ ndUrl: S.cfg.url, user: S.cfg.user, pass: S.cfg.pass, songId: s.id });
fetch(`${S.cfg.backendUrl}/api/song-sorttags?${p}`).then(r => r.ok ? r.json() : null).then(t => {
if (!t) return;
if (t.titleSort) document.getElementById('ie-titlesort').value = t.titleSort;
if (t.artistSort) document.getElementById('ie-artistsort').value = t.artistSort;
if (t.albumArtistSort) document.getElementById('ie-albumartistsort').value = t.albumArtistSort;
if (t.albumSort) document.getElementById('ie-albumsort').value = t.albumSort;
}).catch(() => {});
}
// Art preview — use existing cover
const cv = coverUrl(s.coverArt, 200);