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:
1 parent
8be3a515e6
commit
fa4c954ab9
6 files changed
+148
-98
No files matched your search
+22
-13
@@ -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); }
|
||||
|
||||
Reference in new issue
Block a user