Refactor code structure for improved readability and maintainability
All checks were successful
Build and Push Docker Image / docker (push) Successful in 21s
All checks were successful
Build and Push Docker Image / docker (push) Successful in 21s
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user