Refactor code structure for improved readability and maintainability
All checks were successful
Build and Push Docker Image / docker (push) Successful in 21s

This commit is contained in:
2025-09-04 21:53:54 +02:00
parent 80f8c4ca90
commit 8c5ca0044f
27 changed files with 3398 additions and 326 deletions

View File

@@ -25,7 +25,9 @@ function getArgValue(name, defVal) {
if (i === -1) return defVal;
const a = process.argv[i];
if (a.includes('=')) return a.split('=')[1];
return process.argv[i + 1] && !process.argv[i + 1].startsWith('--') ? process.argv[i + 1] : defVal;
return process.argv[i + 1] && !process.argv[i + 1].startsWith('--')
? process.argv[i + 1]
: defVal;
}
const MAX = parseInt(getArgValue('--max', '0'), 10) || 0;
const FORCE = args.has('--force');
@@ -35,9 +37,18 @@ function normalize(str) {
if (!str) return '';
let s = String(str)
.replace(/\s*\([^)]*(feat\.|ft\.|featuring)[^)]*\)/gi, '') // remove (feat. ...)
.replace(/\s*\[(?:radio edit|remaster(?:ed)?(?: \d{2,4})?|single version|album version|mono|stereo|live|version)\]/gi, '')
.replace(/\s*-\s*(?:radio edit|remaster(?:ed)?(?: \d{2,4})?|single version|album version|mono|stereo|live|version)\b/gi, '')
.replace(/\s*\((?:radio edit|remaster(?:ed)?(?: \d{2,4})?|single version|album version|mono|stereo|live|version|short mix|original mix|201\d remaster|20\d\d remaster)\)/gi, '')
.replace(
/\s*\[(?:radio edit|remaster(?:ed)?(?: \d{2,4})?|single version|album version|mono|stereo|live|version)\]/gi,
''
)
.replace(
/\s*-\s*(?:radio edit|remaster(?:ed)?(?: \d{2,4})?|single version|album version|mono|stereo|live|version)\b/gi,
''
)
.replace(
/\s*\((?:radio edit|remaster(?:ed)?(?: \d{2,4})?|single version|album version|mono|stereo|live|version|short mix|original mix|201\d remaster|20\d\d remaster)\)/gi,
''
)
.replace(/\s*&\s*/g, ' and ')
.replace(/\s+feat\.?\s+/gi, ' ')
.replace(/\s+ft\.?\s+/gi, ' ')
@@ -68,7 +79,9 @@ async function getMeta(fp) {
return {
title: meta.common.title || '',
artist: meta.common.artist || '',
durationMs: Number.isFinite(meta.format.duration) ? Math.round(meta.format.duration * 1000) : null,
durationMs: Number.isFinite(meta.format.duration)
? Math.round(meta.format.duration * 1000)
: null,
yearTag: meta.common.year || null,
};
} catch {
@@ -80,7 +93,9 @@ async function readCache() {
try {
const j = JSON.parse(await fsp.readFile(CACHE_JSON, 'utf8'));
return j || {};
} catch { return {}; }
} catch {
return {};
}
}
async function writeCache(cache) {
@@ -88,7 +103,8 @@ async function writeCache(cache) {
}
function similar(a, b) {
a = normalize(a); b = normalize(b);
a = normalize(a);
b = normalize(b);
if (!a || !b) return 0;
if (a === b) return 1;
// simple token overlap Jaccard
@@ -101,7 +117,9 @@ function similar(a, b) {
async function mbFetchJson(url, retries = 3) {
for (let i = 0; i < retries; i++) {
const res = await fetch(url, { headers: { 'User-Agent': USER_AGENT, 'Accept': 'application/json' } });
const res = await fetch(url, {
headers: { 'User-Agent': USER_AGENT, Accept: 'application/json' },
});
if (res.status === 503 || res.status === 429) {
const ra = Number(res.headers.get('Retry-After')) || 2;
await wait(ra * 1000);
@@ -139,7 +157,10 @@ function pickBestRecording(candidates, artist, title, durationMs) {
const viable = [];
for (const r of candidates) {
const rTitle = r.title || '';
const rArtists = (r['artist-credit'] || []).map((ac) => ac.name || ac.artist?.name).filter(Boolean).join(' ');
const rArtists = (r['artist-credit'] || [])
.map((ac) => ac.name || ac.artist?.name)
.filter(Boolean)
.join(' ');
const titleSim = similar(rTitle, nTitle);
const artistSim = similar(rArtists, nArtist);
let score = (r.score || 0) / 100 + titleSim * 1.5 + artistSim * 1.2;
@@ -156,7 +177,10 @@ function pickBestRecording(candidates, artist, title, durationMs) {
const ageBias = Math.max(0, 2100 - firstYear) / 2100; // ~0.5 for 1050, ~0.95 for 100
score += ageBias * 0.3;
}
if (score > bestScore) { bestScore = score; best = r; }
if (score > bestScore) {
bestScore = score;
best = r;
}
if (titleSim >= 0.55 && artistSim >= 0.55) {
viable.push({ r, firstYear: firstYear || null, titleSim, artistSim, score });
}
@@ -178,7 +202,10 @@ function parseDateToYear(dateStr) {
}
function earliestDate(dates) {
const valid = dates.filter(Boolean).map((d) => ({ d, y: parseDateToYear(d) })).filter((x) => x.y);
const valid = dates
.filter(Boolean)
.map((d) => ({ d, y: parseDateToYear(d) }))
.filter((x) => x.y);
if (!valid.length) return { date: null, year: null };
valid.sort((a, b) => {
if (a.d < b.d) return -1;
@@ -204,7 +231,13 @@ async function resolveOne(file, meta, cache) {
.map((r) => ({
r,
titleSim: similar(r.title || '', meta.title),
artistSim: similar((r['artist-credit'] || []).map((ac) => ac.name || ac.artist?.name).filter(Boolean).join(' '), meta.artist),
artistSim: similar(
(r['artist-credit'] || [])
.map((ac) => ac.name || ac.artist?.name)
.filter(Boolean)
.join(' '),
meta.artist
),
firstYear: parseDateToYear(r['first-release-date']) || null,
}))
.filter((v) => v.titleSim >= 0.5 && v.artistSim >= 0.5);
@@ -220,7 +253,9 @@ async function resolveOne(file, meta, cache) {
try {
const details = await getRecordingDetails(v.r.id);
detailsUsed++;
const dates = (details.releases || []).map((re) => re.date || re['release-events']?.[0]?.date || null);
const dates = (details.releases || []).map(
(re) => re.date || re['release-events']?.[0]?.date || null
);
const er = earliestDate(dates);
y = er.year;
d = er.date;
@@ -244,7 +279,13 @@ async function resolveOne(file, meta, cache) {
confidence: {
mbScore: best.score || null,
titleSim: similar(best.title || '', meta.title),
artistSim: similar((best['artist-credit'] || []).map((ac) => ac.name || ac.artist?.name).filter(Boolean).join(' '), meta.artist),
artistSim: similar(
(best['artist-credit'] || [])
.map((ac) => ac.name || ac.artist?.name)
.filter(Boolean)
.join(' '),
meta.artist
),
durationMs: meta.durationMs,
matchedDurationMs: best.length || null,
},
@@ -281,15 +322,30 @@ async function main() {
try {
const r = await resolveOne(f, meta, cache);
results.push(r);
console.log(` ✓ Earliest: ${r.earliestDate || 'n/a'} (year=${r.year || 'n/a'}) [${r.fromCache ? 'cache' : 'MB'}]`);
console.log(
` ✓ Earliest: ${r.earliestDate || 'n/a'} (year=${r.year || 'n/a'}) [${r.fromCache ? 'cache' : 'MB'}]`
);
} catch (e) {
console.warn(' ! Failed:', e.message);
results.push({ file: f, title, artist, mbid: null, earliestDate: null, year: null, error: e.message });
results.push({
file: f,
title,
artist,
mbid: null,
earliestDate: null,
year: null,
error: e.message,
});
}
}
// Build index by file
const byFile = Object.fromEntries(results.map((r) => [r.file, { year: r.year, date: r.earliestDate, title: r.title, artist: r.artist, mbid: r.mbid }]));
const byFile = Object.fromEntries(
results.map((r) => [
r.file,
{ year: r.year, date: r.earliestDate, title: r.title, artist: r.artist, mbid: r.mbid },
])
);
const out = { generatedAt: new Date().toISOString(), total: results.length, byFile, results };
await fsp.writeFile(OUT_JSON, JSON.stringify(out, null, 2));
await writeCache(cache);