change(backend): nur noch der erste Kuenstler im Dateinamen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d25bf0404
commit
073593d963
5 files changed
+84
-6
No files matched your search
@@ -208,6 +208,20 @@ nur zu zeigen, was drinsteht.
|
|||||||
geprüft werden können: sieben neue Fälle in `backend/test/metadata.test.js` (49 Prüfungen grün).
|
geprüft werden können: sieben neue Fälle in `backend/test/metadata.test.js` (49 Prüfungen grün).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- **Nur noch ein Künstler im Dateinamen (Backend + Editor)** — bisher stand dort die vollständige
|
||||||
|
Gästeliste: `01 - K_DA,Madison Beer,i-dle,Lexie Liu,Jaira Burns,Seraphine,League of Legends - MORE.mp3`.
|
||||||
|
Üblich ist das nirgends — Picard, beets und Lidarr schreiben höchstens **einen** Künstler in den
|
||||||
|
Namen —, und es hatte zwei handfeste Nachteile: `sanitize()` schneidet bei 100 Zeichen hart ab,
|
||||||
|
also mitten im letzten Namen, und mit dem Dateisystemlimit von 255 Byte wird es bei mehrbytigen
|
||||||
|
Namen eng, was Umbenennen und Verschieben scheitern lässt. Jetzt `01 - K_DA - MORE.mp3`.
|
||||||
|
- Genommen wird der **erste Künstler des Titels**, nicht der des Albums — so trägt ein
|
||||||
|
Soundtrack-Titel im Namen weiterhin seinen eigenen Interpreten, auch wenn der Ordner am
|
||||||
|
Album-Künstler hängt. Die vollständige Liste steht unverändert im `artist`-Tag, und nur den
|
||||||
|
liest Navidrome.
|
||||||
|
- Gilt für **neue** Downloads und für Dateien, die bearbeitet werden; Bestehendes bleibt liegen.
|
||||||
|
Beide Namensformen stehen also eine Weile nebeneinander. Die Dublettenprüfung erkennt beide —
|
||||||
|
eigener Testfall, damit die alten Dateien nicht ein zweites Mal geladen werden.
|
||||||
|
|
||||||
- **Künstlerbilder laden endlich zügig (Web)** — im Künstlerraster lief **pro Künstler ein eigener
|
- **Künstlerbilder laden endlich zügig (Web)** — im Künstlerraster lief **pro Künstler ein eigener
|
||||||
`getArtistInfo2`-Aufruf**, nur um die Bildadresse zu erfahren. Bei 200 Künstlern sind das 200
|
`getArtistInfo2`-Aufruf**, nur um die Bildadresse zu erfahren. Bei 200 Künstlern sind das 200
|
||||||
API-Aufrufe auf einen Schlag, die der Browser zu sechst abarbeitet (mehr Verbindungen erlaubt er
|
API-Aufrufe auf einen Schlag, die der Browser zu sechst abarbeitet (mehr Verbindungen erlaubt er
|
||||||
|
|||||||
+14
-1
@@ -6,6 +6,19 @@ function sanitize(s) {
|
|||||||
return (s || 'Unknown').replace(/[\\/:*?"<>|]/g, '_').trim().slice(0, 100);
|
return (s || 'Unknown').replace(/[\\/:*?"<>|]/g, '_').trim().slice(0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Welcher Kuenstler in den DATEINAMEN gehoert. Bisher stand dort die vollstaendige Gaesteliste
|
||||||
|
// ("K_DA,Madison Beer,i-dle,Lexie Liu,Jaira Burns,Seraphine,League of Legends"). Das ist nirgends
|
||||||
|
// ueblich — Picard, beets und Lidarr schreiben hoechstens einen Kuenstler in den Namen —, und es
|
||||||
|
// hatte zwei handfeste Nachteile: sanitize() schneidet bei 100 Zeichen hart ab, also mitten im
|
||||||
|
// letzten Namen, und mit dem Dateisystemlimit von 255 Byte wird es bei mehrbytigen Namen eng.
|
||||||
|
// Die vollstaendige Liste steht ohnehin im artist-Tag, und nur den liest Navidrome.
|
||||||
|
// Genommen wird der ERSTE Kuenstler — bei gemischten Alben der des Titels, nicht der des Albums,
|
||||||
|
// damit ein Soundtrack-Titel im Namen weiterhin seinen eigenen Interpreten traegt.
|
||||||
|
function dateinamensKuenstler(artist, fallback) {
|
||||||
|
const erster = (s) => String(s || '').split(/[;,]|\s+&\s+/)[0].trim();
|
||||||
|
return erster(artist) || erster(fallback) || '';
|
||||||
|
}
|
||||||
|
|
||||||
function navidromeArtist(name) {
|
function navidromeArtist(name) {
|
||||||
if (!name) return name;
|
if (!name) return name;
|
||||||
return name
|
return name
|
||||||
@@ -202,7 +215,7 @@ async function fetchLyrics(artist, title, album) {
|
|||||||
// fetchAlbumTracks, searchAlbumTracks and fetchArtistAlbums.
|
// fetchAlbumTracks, searchAlbumTracks and fetchArtistAlbums.
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sanitize, navidromeArtist,
|
sanitize, navidromeArtist, dateinamensKuenstler,
|
||||||
downloadArtwork, findExistingTrack,
|
downloadArtwork, findExistingTrack,
|
||||||
normStem, findFileByStem, albumDirFromTags,
|
normStem, findFileByStem, albumDirFromTags,
|
||||||
};
|
};
|
||||||
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|||||||
const CONFIG = require('./config');
|
const CONFIG = require('./config');
|
||||||
const { log } = require('./jobs');
|
const { log } = require('./jobs');
|
||||||
const {
|
const {
|
||||||
sanitize, navidromeArtist,
|
sanitize, navidromeArtist, dateinamensKuenstler,
|
||||||
downloadArtwork, findExistingTrack,
|
downloadArtwork, findExistingTrack,
|
||||||
} = require('./metadata');
|
} = require('./metadata');
|
||||||
const { romajiForStorage, isTokenizerReady } = require('./romaji');
|
const { romajiForStorage, isTokenizerReady } = require('./romaji');
|
||||||
@@ -140,8 +140,9 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb
|
|||||||
|
|
||||||
const safeTitle = sanitize(cleanTitle);
|
const safeTitle = sanitize(cleanTitle);
|
||||||
const trackPrefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
|
const trackPrefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
|
||||||
// Filename format: "{01 - }Artist1, Artist2 - Title.ext"
|
// Filename format: "{01 - }Artist - Title.ext" — nur der erste Kuenstler, siehe
|
||||||
const safeArtistForFile = sanitize((artist || '').replace(/;/g, ','));
|
// dateinamensKuenstler() in lib/metadata.js. Die volle Liste steht im artist-Tag.
|
||||||
|
const safeArtistForFile = sanitize(dateinamensKuenstler(artist, mainArtist));
|
||||||
const fileBase = `${trackPrefix}${safeArtistForFile} - ${safeTitle}`;
|
const fileBase = `${trackPrefix}${safeArtistForFile} - ${safeTitle}`;
|
||||||
|
|
||||||
// Dublettenpruefung: findExistingTrack durchsucht NUR den Ordner des uebergebenen Kuenstlers.
|
// Dublettenpruefung: findExistingTrack durchsucht NUR den Ordner des uebergebenen Kuenstlers.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const crypto = require('crypto');
|
|||||||
|
|
||||||
const CONFIG = require('./lib/config');
|
const CONFIG = require('./lib/config');
|
||||||
const { jobs, updateJob, log } = require('./lib/jobs');
|
const { jobs, updateJob, log } = require('./lib/jobs');
|
||||||
const { sanitize, findExistingTrack, findFileByStem, albumDirFromTags } = require('./lib/metadata');
|
const { sanitize, findExistingTrack, findFileByStem, albumDirFromTags, dateinamensKuenstler } = require('./lib/metadata');
|
||||||
const dz = require('./lib/deezer');
|
const dz = require('./lib/deezer');
|
||||||
const tm = require('./lib/trackmeta'); // Deezer → MusicBrainz → clean fallback (single tracks)
|
const tm = require('./lib/trackmeta'); // Deezer → MusicBrainz → clean fallback (single tracks)
|
||||||
const { downloadTrack, downloadAlbumTracks } = require('./lib/downloader');
|
const { downloadTrack, downloadAlbumTracks } = require('./lib/downloader');
|
||||||
@@ -1553,7 +1553,7 @@ app.post('/api/edit-metadata', async (req, res) => {
|
|||||||
// Use original names (may be Japanese); romaji goes only into sort tags.
|
// Use original names (may be Japanese); romaji goes only into sort tags.
|
||||||
if (title) {
|
if (title) {
|
||||||
const safeTitle = sanitize(title);
|
const safeTitle = sanitize(title);
|
||||||
const fileArtist = (normalArtist || ndSong?.artist || '').replace(/;/g, ',');
|
const fileArtist = dateinamensKuenstler(normalArtist, ndSong?.artist);
|
||||||
const safeArtistPart = fileArtist ? sanitize(fileArtist) + ' - ' : '';
|
const safeArtistPart = fileArtist ? sanitize(fileArtist) + ' - ' : '';
|
||||||
const prefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
|
const prefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
|
||||||
const newName = `${prefix}${safeArtistPart}${safeTitle}${fileExt}`;
|
const newName = `${prefix}${safeArtistPart}${safeTitle}${fileExt}`;
|
||||||
|
|||||||
@@ -170,3 +170,53 @@ test('Mehrere Kuenstler: Ordner haengt am ersten', () => {
|
|||||||
test('Kein Treffer bleibt null', () => {
|
test('Kein Treffer bleibt null', () => {
|
||||||
assert.equal(albumDirFromTags(SLASH_ROOT, 'Niemand', 'Nichts'), null);
|
assert.equal(albumDirFromTags(SLASH_ROOT, 'Niemand', 'Nichts'), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Kuenstler im Dateinamen ────────────────────────────────────────────────
|
||||||
|
// Frueher stand die vollstaendige Gaesteliste im Namen und wurde bei 100 Zeichen mitten im
|
||||||
|
// letzten Namen abgeschnitten. Jetzt nur noch der erste Kuenstler.
|
||||||
|
const { dateinamensKuenstler } = require('../lib/metadata');
|
||||||
|
|
||||||
|
test('Nur der erste Kuenstler landet im Dateinamen', () => {
|
||||||
|
assert.equal(
|
||||||
|
dateinamensKuenstler('K/DA;Madison Beer;i-dle;Lexie Liu;Jaira Burns;Seraphine;League of Legends'),
|
||||||
|
'K/DA',
|
||||||
|
);
|
||||||
|
assert.equal(dateinamensKuenstler('Dove Cameron, Sofia Carson'), 'Dove Cameron');
|
||||||
|
assert.equal(dateinamensKuenstler('Calvin Harris & Dua Lipa'), 'Calvin Harris');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Bandnamen mit & bleiben ganz, wenn sie keine Liste sind', () => {
|
||||||
|
// "&" trennt nur MIT umgebenden Leerzeichen — "Earth, Wind & Fire" waere ohnehin durch das
|
||||||
|
// Komma getrennt, aber ein reiner Bandname ohne Liste darf nicht zerlegt werden.
|
||||||
|
assert.equal(dateinamensKuenstler('AC/DC'), 'AC/DC');
|
||||||
|
assert.equal(dateinamensKuenstler('Simon&Garfunkel'), 'Simon&Garfunkel');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Leerer Kuenstler faellt auf den Album-Kuenstler zurueck', () => {
|
||||||
|
assert.equal(dateinamensKuenstler('', 'K/DA;Madison Beer'), 'K/DA');
|
||||||
|
assert.equal(dateinamensKuenstler(undefined, undefined), '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Der Dateiname bleibt kurz genug fuers Dateisystem', () => {
|
||||||
|
const lang = 'K/DA;Madison Beer;(G)I-DLE;Lexie Liu;Jaira Burns;Seraphine;League of Legends;Bea Miller;Wolftyla;Duckwrth';
|
||||||
|
const teil = dateinamensKuenstler(lang);
|
||||||
|
assert.equal(teil, 'K/DA');
|
||||||
|
assert.ok(teil.length < 100, 'wird nie von sanitize() abgeschnitten');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Dublettenpruefung findet auch die alt benannten Dateien', () => {
|
||||||
|
// Nach der Umstellung liegen beide Namensformen nebeneinander. Beide muessen als "schon da"
|
||||||
|
// erkannt werden, sonst laedt der Downloader die alten Dateien ein zweites Mal.
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-namen-'));
|
||||||
|
const put = (album, file) => {
|
||||||
|
const dir = path.join(root, 'K_DA', album);
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(dir, file), '');
|
||||||
|
};
|
||||||
|
put('MORE', '01 - K_DA,Madison Beer,i-dle,Lexie Liu,Jaira Burns,Seraphine - MORE.mp3'); // alt
|
||||||
|
put('ALL OUT', '03 - K_DA - VILLAIN.mp3'); // neu
|
||||||
|
assert.ok(findExistingTrack(root, 'K/DA', 'MORE'), 'alte Namensform nicht erkannt');
|
||||||
|
assert.ok(findExistingTrack(root, 'K/DA', 'VILLAIN'), 'neue Namensform nicht erkannt');
|
||||||
|
assert.equal(findExistingTrack(root, 'K/DA', 'POP/STARS'), null);
|
||||||
|
fs.rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
Reference in new issue
Block a user