feat: normalize Japanese artist/title names to romaji at storage time
- Add romajiForStorage() to romaji.js — smart word spacing + Title Case - ytdlp.js: apply to folder names, filenames, and embedded tags - novasonic-server.js: apply in edit-metadata endpoint and Spotify resolve - spotify.js: update track artist/name from iTunes canonical English names Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
c03463b66b
commit
446c583f7f
4 files changed
+57
-15
No files matched your search
@@ -19,7 +19,7 @@ const CONFIG = require('./lib/config');
|
||||
const { jobs, updateJob, log } = require('./lib/jobs');
|
||||
const { sanitize, lookupItunesTrackMeta, fetchItunesTracks, searchItunesAlbumTracks } = require('./lib/metadata');
|
||||
const { downloadTrack, downloadAlbumTracks } = require('./lib/downloader');
|
||||
const { buildRomajiIndex, clearRomajiCache, isTokenizerReady, fetchNdSongs } = require('./lib/romaji');
|
||||
const { buildRomajiIndex, clearRomajiCache, isTokenizerReady, fetchNdSongs, romajiForStorage } = require('./lib/romaji');
|
||||
|
||||
// ─── INIT ─────────────────────────────────────────────────────────────────────
|
||||
const app = express();
|
||||
@@ -225,6 +225,13 @@ app.post('/api/spotify/resolve', async (req, res) => {
|
||||
setTimeout(() => reject(new Error('Zeitüberschreitung — Spotify API antwortet nicht (>120s)')), 120000)
|
||||
);
|
||||
const data = await Promise.race([_resolveSpotifyViaSpotdl(url), timeout]);
|
||||
// Normalize Japanese artist/title names to romaji before sending to frontend
|
||||
if (data.tracks) {
|
||||
for (const t of data.tracks) {
|
||||
if (t.artistName) t.artistName = romajiForStorage(t.artistName) || t.artistName;
|
||||
if (t.trackName) t.trackName = romajiForStorage(t.trackName) || t.trackName;
|
||||
}
|
||||
}
|
||||
res.json(data);
|
||||
} catch(e) {
|
||||
console.error('[spotify/resolve]', e.message);
|
||||
@@ -609,6 +616,11 @@ app.post('/api/edit-metadata', async (req, res) => {
|
||||
const normalArtist = _normalizeArtists(artist, ';');
|
||||
const normalAlbumArtist = _normalizeArtists(albumArtist, ', ');
|
||||
|
||||
// Convert Japanese names to romaji for storage
|
||||
const romajiArtist = normalArtist ? romajiForStorage(normalArtist) || normalArtist : normalArtist;
|
||||
const romajiAlbumArtist = normalAlbumArtist ? romajiForStorage(normalAlbumArtist) || normalAlbumArtist : normalAlbumArtist;
|
||||
const romajiTitle = title ? romajiForStorage(title) || title : title;
|
||||
|
||||
// Resolve file path from Navidrome
|
||||
let filePath;
|
||||
let ndSong;
|
||||
@@ -673,10 +685,10 @@ app.post('/api/edit-metadata', async (req, res) => {
|
||||
ffArgs.push('-map', '0:v?'); // '?' = optional, no error if file has no cover
|
||||
}
|
||||
ffArgs.push('-c', 'copy', '-map_metadata', '-1', '-id3v2_version', '3');
|
||||
if (title) ffArgs.push('-metadata', `title=${title}`);
|
||||
if (normalArtist) ffArgs.push('-metadata', `artist=${normalArtist}`);
|
||||
const resolvedAlbumArtist = normalAlbumArtist || (normalArtist ? normalArtist.split(';')[0].trim() : null);
|
||||
if (resolvedAlbumArtist) ffArgs.push('-metadata', `album_artist=${resolvedAlbumArtist}`);
|
||||
if (romajiTitle) ffArgs.push('-metadata', `title=${romajiTitle}`);
|
||||
if (romajiArtist) ffArgs.push('-metadata', `artist=${romajiArtist}`);
|
||||
const resolvedAlbumArtist = romajiAlbumArtist || (romajiArtist ? romajiArtist.split(';')[0].trim() : null);
|
||||
if (resolvedAlbumArtist) ffArgs.push('-metadata', `album_artist=${resolvedAlbumArtist}`);
|
||||
if (album) ffArgs.push('-metadata', `album=${album}`);
|
||||
if (year) ffArgs.push('-metadata', `date=${year}`);
|
||||
if (trackNum) ffArgs.push('-metadata', `track=${totalTracks ? `${trackNum}/${totalTracks}` : String(trackNum)}`);
|
||||
@@ -704,8 +716,8 @@ app.post('/api/edit-metadata', async (req, res) => {
|
||||
|
||||
// 1. Rename file if title changed — convention: {01 - }?{Artist} - {Title}.ext
|
||||
if (title) {
|
||||
const safeTitle = sanitize(title);
|
||||
const fileArtist = (normalArtist || ndSong?.artist || '').replace(/;/g, ',');
|
||||
const safeTitle = sanitize(romajiTitle || title);
|
||||
const fileArtist = (romajiArtist || ndSong?.artist || '').replace(/;/g, ',');
|
||||
const safeArtistPart = fileArtist ? sanitize(fileArtist) + ' - ' : '';
|
||||
const prefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
|
||||
const newName = `${prefix}${safeArtistPart}${safeTitle}${fileExt}`;
|
||||
@@ -720,7 +732,7 @@ app.post('/api/edit-metadata', async (req, res) => {
|
||||
const oldAlbumDir = path.dirname(currentFile);
|
||||
const oldArtistDir = path.dirname(oldAlbumDir);
|
||||
const newAlbumName = album ? sanitize(album) : path.basename(oldAlbumDir);
|
||||
const newArtistName = normalAlbumArtist ? sanitize(normalAlbumArtist.split(/[,;&]/)[0].trim()) : path.basename(oldArtistDir);
|
||||
const newArtistName = romajiAlbumArtist ? sanitize(romajiAlbumArtist.split(/[,;&]/)[0].trim()) : path.basename(oldArtistDir);
|
||||
const newDir = path.join(CONFIG.musicPath, newArtistName, newAlbumName);
|
||||
|
||||
if (newDir !== oldAlbumDir) {
|
||||
|
||||
Reference in new issue
Block a user