feat: add support for multi-disc albums

This commit is contained in:
2026-07-25 21:54:25 +01:00
parent 5185d4dc0b
commit c6080b975a
5 changed files with 292 additions and 108 deletions
+17 -2
View File
@@ -61,9 +61,13 @@ export async function POST(request: Request) {
// Calculate total size
const totalSize = tracks.reduce((acc, track) => acc + (track.size || 0), 0);
// Sort tracks by track number if available, else by filename
// Sort tracks by disc number first, then track number, else by filename
tracks.sort((a, b) => {
if ('error' in a || 'error' in b) return 0; // Keep order if error
const discA = a.common?.disk?.no ?? 1;
const discB = b.common?.disk?.no ?? 1;
if (discA !== discB) return discA - discB;
const trackA = a.common?.track?.no ?? 0;
const trackB = b.common?.track?.no ?? 0;
if (trackA && trackB) return trackA - trackB;
@@ -95,6 +99,11 @@ export async function POST(request: Request) {
}
});
const discNumbers = new Set(
tracks.map(t => ('error' in t ? 1 : (t.common?.disk?.no || 1)))
);
const isMultiDisc = discNumbers.size > 1 || Array.from(discNumbers).some(d => d > 1);
if (sampleRates.size > 1 || bitDepths.size > 1) {
mixed = true;
qualityDetails = `NOTE: Mixed formats. Sample Rates: ${Array.from(sampleRates).join(', ')} Hz. Bit Depths: ${Array.from(bitDepths).join(', ')} bit.`;
@@ -102,7 +111,13 @@ export async function POST(request: Request) {
// Generate detailed message for mixed content
const mixedDetails = tracks.map(t => {
if ('error' in t) return `${t.filename}: Error`;
return `Track ${t.common?.track?.no || '?'}: ${t.format?.bitsPerSample}bit / ${t.format?.sampleRate}Hz`;
const discNo = t.common?.disk?.no || 1;
const trackNo = t.common?.track?.no;
let trackStr = trackNo !== undefined && trackNo !== null ? String(trackNo).padStart(2, '0') : '?';
if (isMultiDisc) {
trackStr = `${discNo}-${trackStr}`;
}
return `Track ${trackStr}: ${t.format?.bitsPerSample}bit / ${t.format?.sampleRate}Hz`;
}).join('\n');
qualityDetails += '\n' + mixedDetails;
+42 -5
View File
@@ -34,6 +34,10 @@ export async function POST(request: Request) {
// Sort tracks
tracks.sort((a, b) => {
if ('error' in a || 'error' in b) return 0;
const discA = a.common?.disk?.no ?? 1;
const discB = b.common?.disk?.no ?? 1;
if (discA !== discB) return discA - discB;
const trackA = a.common?.track?.no ?? 0;
const trackB = b.common?.track?.no ?? 0;
if (trackA && trackB) return trackA - trackB;
@@ -58,13 +62,24 @@ export async function POST(request: Request) {
if (!sr || sr < 48000 || !bd || bd < 24) isHiRes = false;
});
const discNumbers = new Set(
tracks.map(t => ('error' in t ? 1 : (t.common?.disk?.no || 1)))
);
const isMultiDisc = discNumbers.size > 1 || Array.from(discNumbers).some(d => d > 1);
if (sampleRates.size > 1 || bitDepths.size > 1) {
mixed = true;
qualityDetails = `\n\nNOTE: Mixed formats. Sample Rates: ${Array.from(sampleRates).join(', ')} Hz. Bit Depths: ${Array.from(bitDepths).join(', ')} bit.`;
const mixedDetails = tracks.map(t => {
if ('error' in t) return `${t.filename}: Error`;
return `Track ${t.common?.track?.no || '?'}: ${t.format?.bitsPerSample}bit / ${t.format?.sampleRate}Hz`;
const discNo = t.common?.disk?.no || 1;
const trackNo = t.common?.track?.no;
let trackStr = trackNo !== undefined && trackNo !== null ? String(trackNo).padStart(2, '0') : '?';
if (isMultiDisc) {
trackStr = `${discNo}-${trackStr}`;
}
return `Track ${trackStr}: ${t.format?.bitsPerSample}bit / ${t.format?.sampleRate}Hz`;
}).join('\n');
qualityDetails += '\n' + mixedDetails;
@@ -155,10 +170,32 @@ export async function POST(request: Request) {
}
// Tracklist
const tracklist = tracks.map(t => {
if ('error' in t) return `${t.filename} (Error)`;
return `${t.common?.track?.no || '00'}. ${t.common?.title || t.filename}`;
}).join('\n');
let currentDisc: number | null = null;
const tracklistLines: string[] = [];
tracks.forEach(t => {
if ('error' in t) {
tracklistLines.push(`${t.filename} (Error)`);
return;
}
const discNo = t.common?.disk?.no || 1;
const trackNo = t.common?.track?.no
? String(t.common.track.no).padStart(2, '0')
: '00';
if (isMultiDisc && discNo !== currentDisc) {
if (currentDisc !== null) tracklistLines.push('');
tracklistLines.push(`[b]Disc ${discNo}[/b]`);
currentDisc = discNo;
}
const trackLabel = isMultiDisc ? `${discNo}-${trackNo}` : trackNo;
tracklistLines.push(`${trackLabel}. ${t.common?.title || t.filename}`);
});
const tracklist = tracklistLines.join('\n');
// Description
let description = "Sourced from TIDAL";
+7 -2
View File
@@ -32,9 +32,14 @@ export async function POST(request: Request) {
if ('error' in track) return;
const trackNo = track.common?.track?.no;
const discNo = track.common?.disk?.no;
if (trackNo === undefined || trackNo === null) return;
const outputFilename = `${trackNo}.png`;
const discPrefix = (discNo && discNo > 1) || tracks.some(t => !('error' in t) && (t.common?.disk?.no ?? 1) > 1)
? `${discNo ?? 1}-`
: '';
const outputFilename = `${discPrefix}${trackNo}.png`;
const outputPath = path.join(albumSpecDir, outputFilename);
const relativePath = path.join(albumId, outputFilename);
const publicUrl = `/api/spectrogram?path=${encodeURIComponent(relativePath)}`;
@@ -43,7 +48,7 @@ export async function POST(request: Request) {
if (!exists) {
// Generate
const title = `${trackNo} - ${track.common?.title || track.filename}`;
const title = `${discPrefix}${trackNo} - ${track.common?.title || track.filename}`;
const args = [
track.path,