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,