feat(0.9): romaji sort-tags only, robust dupe detection, Mittelweg no-duration, autofill all fields

- Japanese tracks stored with original names in folder/filename/tags; romaji only in sort tags
- findExistingTrack: case-insensitive, searches all album folders under artist, strips release suffix
- --max-filesize 30M + 2x duration cap on all yt-dlp calls
- iTunes collectionArtistName captured as album_artist; fixes compilation album splitting
- album_artist tag uses mainArtist (not rawArtist) so all tracks in one album are consistent
- No-duration cascade: steps 5-8 (title-only/no-filter) removed — fail cleanly like with-duration
- Autofill iTunes: fills title, artist, albumArtist, discNum (was album/genre/year/track/total only)
- Autofill MusicBrainz: fills albumArtist, discNum (was missing both)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-21 13:26:11 +02:00
1 parent 8be3a515e6
commit fa4c954ab9
6 files changed
+148 -98

No files matched your search

+22 -13
View File
@@ -292,13 +292,16 @@ app.get('/api/itunes/lookup', async (req, res, next) => {
if (!meta.album && !meta.genre && !meta.year) return res.json({ found: false });
res.json({
found: true,
album: meta.album || undefined,
year: meta.year || undefined,
trackNum: meta.trackNum || undefined,
totalTracks: meta.totalTracks || undefined,
genre: meta.genre || undefined,
coverUrl: meta.artworkUrl || undefined,
title: meta.trackName || undefined,
artist: meta.itunesArtistName || undefined,
albumArtist: meta.albumArtist || undefined,
album: meta.album || undefined,
year: meta.year || undefined,
trackNum: meta.trackNum || undefined,
totalTracks: meta.totalTracks || undefined,
discNum: meta.discNum || undefined,
genre: meta.genre || undefined,
coverUrl: meta.artworkUrl || undefined,
});
} catch (e) {
console.error('[itunes/lookup]', e.message);
@@ -994,15 +997,20 @@ app.get('/api/musicbrainz/lookup', async (req, res) => {
const trackNum = release?.media?.[0]?.['track-offset'] != null
? (release.media[0]['track-offset'] + 1) : undefined;
const totalTracks = release?.media?.[0]?.['track-count'] || undefined;
const discNum = release?.media?.[0]?.position || undefined;
const coverUrl = release?.id ? `https://coverartarchive.org/release/${release.id}/front` : null;
// album-artist from release artist-credit if present, else fall back to recording artist
const mbAlbumArtist = (release?.['artist-credit'] || []).map(c => c.name || c.artist?.name).filter(Boolean).join('; ') || mbArtist;
res.json({
found: true,
title: rec.title || title,
artist: mbArtist || artist,
artist: mbArtist || artist,
albumArtist: mbAlbumArtist || undefined,
album: release?.title || '',
year: year ? parseInt(year, 10) : undefined,
trackNum: trackNum,
totalTracks: totalTracks,
discNum: discNum,
genre: genre,
coverUrl: coverUrl,
mbid: rec.id,
@@ -1253,9 +1261,10 @@ app.post('/api/edit-metadata', async (req, res) => {
let currentFile = filePath;
// 1. Rename file if title changed — convention: {01 - }?{Artist} - {Title}.ext
// Use original names (may be Japanese); romaji goes only into sort tags.
if (title) {
const safeTitle = sanitize(romajiTitle || title);
const fileArtist = (romajiArtist || ndSong?.artist || '').replace(/;/g, ',');
const safeTitle = sanitize(title);
const fileArtist = (normalArtist || ndSong?.artist || '').replace(/;/g, ',');
const safeArtistPart = fileArtist ? sanitize(fileArtist) + ' - ' : '';
const prefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
const newName = `${prefix}${safeArtistPart}${safeTitle}${fileExt}`;
@@ -1269,8 +1278,8 @@ app.post('/api/edit-metadata', async (req, res) => {
// 2. Move to new artist/album folder when albumArtist or album changed
const oldAlbumDir = path.dirname(currentFile);
const oldArtistDir = path.dirname(oldAlbumDir);
const newAlbumName = romajiAlbum ? sanitize(romajiAlbum) : path.basename(oldAlbumDir);
const newArtistName = (albumArtistSort || romajiAlbumArtist) ? sanitize((albumArtistSort || romajiAlbumArtist).split(/[,;&]/)[0].trim()) : path.basename(oldArtistDir);
const newAlbumName = album ? sanitize(album) : path.basename(oldAlbumDir);
const newArtistName = normalAlbumArtist ? sanitize(normalAlbumArtist.split(/[,;&]/)[0].trim()) : path.basename(oldArtistDir);
const newDir = path.join(CONFIG.musicPath, newArtistName, newAlbumName);
if (newDir !== oldAlbumDir) {
@@ -1291,9 +1300,9 @@ app.post('/api/edit-metadata', async (req, res) => {
} catch {}
}
} catch (e) { console.error('[edit-metadata] move:', e.message); }
} else if (romajiAlbum && path.basename(oldAlbumDir) !== sanitize(romajiAlbum)) {
} else if (album && path.basename(oldAlbumDir) !== sanitize(album)) {
// Only album name changed (no artist move) — rename album folder in place
const renamedDir = path.join(oldArtistDir, sanitize(romajiAlbum));
const renamedDir = path.join(oldArtistDir, sanitize(album));
if (!fs.existsSync(renamedDir)) {
try { fs.renameSync(oldAlbumDir, renamedDir); }
catch (e) { console.error('[edit-metadata] folder rename:', e.message); }