fix(downloads): cue generation now properly outputs correct tracks numbers and splits by disc

This commit is contained in:
Daniel 2026-03-11 20:00:44 +00:00 committed by GitHub
parent 3ef50cb6ce
commit 8cf7979d47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 31 deletions

View file

@ -584,13 +584,25 @@ async function bulkDownloadToZipStream(
// For albums, generate CUE file
if (type === 'album' && playlistSettings.shouldGenerateCUE()) {
const audioFilename = `${sanitizeForFilename(folderName)}.flac`; // Assume FLAC for CUE
const cueContent = generateCUE(metadata, tracks, audioFilename);
yield {
name: `${folderName}/${sanitizeForFilename(folderName)}.cue`,
lastModified: new Date(),
input: cueContent,
};
// Split tracks by volumeNumber and iterate those groups.
const tracksByVolume = Object.groupBy(
tracks.map((track, index) => ({
...track,
trackPath: trackPaths[index],
})),
(track) => String(getExplicitTrackDiscNumber(track) || 1)
);
const multiDisc = Object.keys(tracksByVolume).length > 1;
for (const [volumeNumber, tracks] of Object.entries(tracksByVolume)) {
const trackPaths = tracks.map((track) => track.trackPath);
const cueContent = generateCUE(metadata, tracks, sanitizeForFilename(folderName), trackPaths);
yield {
name: `${folderName}/${sanitizeForFilename(folderName)}${multiDisc ? ` - Disc ${volumeNumber}` : ''}.cue`,
lastModified: new Date(),
input: cueContent,
};
}
}
// Generate m3u/m3u8 last, using actual track paths collected during download
@ -741,8 +753,7 @@ async function bulkDownloadToZipBlob(
// For albums, generate CUE file
if (type === 'album' && playlistSettings.shouldGenerateCUE()) {
const audioFilename = `${sanitizeForFilename(folderName)}.flac`; // Assume FLAC for CUE
const cueContent = generateCUE(metadata, tracks, audioFilename);
const cueContent = generateCUE(metadata, tracks, sanitizeForFilename(folderName), trackPaths);
yield {
name: `${folderName}/${sanitizeForFilename(folderName)}.cue`,
lastModified: new Date(),
@ -899,8 +910,7 @@ async function bulkDownloadToZipNeutralino(
// For albums, generate CUE file
if (type === 'album' && playlistSettings.shouldGenerateCUE()) {
const audioFilename = `${sanitizeForFilename(folderName)}.flac`; // Assume FLAC for CUE
const cueContent = generateCUE(metadata, tracks, audioFilename);
const cueContent = generateCUE(metadata, tracks, sanitizeForFilename(folderName), trackPaths);
yield {
name: `${folderName}/${sanitizeForFilename(folderName)}.cue`,
lastModified: new Date(),
@ -1246,8 +1256,7 @@ export async function downloadDiscography(artist, selectedReleases, api, quality
}
if (playlistSettings.shouldGenerateCUE()) {
const audioFilename = `${sanitizeForFilename(fullAlbum.title)}.flac`;
const cueContent = generateCUE(fullAlbum, tracks, audioFilename);
const cueContent = generateCUE(fullAlbum, tracks, sanitizeForFilename(fullAlbum.title), trackPaths);
yield {
name: `${fullFolderPath}/${sanitizeForFilename(fullAlbum.title)}.cue`,
lastModified: new Date(),

View file

@ -120,40 +120,39 @@ export function generateM3U8(
* Generates CUE sheet content for albums
* @param {Object} album - Album metadata
* @param {Array} tracks - Array of track objects
* @param {string} audioFilename - The main audio file name
* @param {string} _audioFilenameBase - Unused; kept for API compatibility
* @param {Array|null} trackPaths - Actual per-track resolved paths; when provided, each track gets its own FILE entry
* @param {string} audioExtension - Audio file extension for generated paths (used when trackPaths is null)
* @returns {string} CUE content
*/
export function generateCUE(album, tracks, audioFilename) {
export function generateCUE(album, tracks, _audioFilenameBase, trackPaths = null, audioExtension = 'flac') {
const performer = album.artist?.name || album.artist || 'Unknown Artist';
const title = album.title || 'Unknown Album';
let content = `PERFORMER "${performer}"\n`;
content += `TITLE "${title}"\n`;
// Add file reference
const fileExtension = audioFilename.split('.').pop().toUpperCase();
content += `FILE "${audioFilename}" ${fileExtension}\n`;
let currentTime = 0;
tracks.forEach((track, index) => {
const resolvedPath = trackPaths ? trackPaths[index] : null;
if (trackPaths && !resolvedPath) return;
const trackNumber = String(track.trackNumber || index + 1).padStart(2, '0');
const trackTitle = track.title || 'Unknown Track';
const trackPerformer = track.artist?.name || getTrackArtists(track) || performer;
const duration = track.duration || 0;
const path =
resolvedPath ??
(() => {
const filename = getTrackFilename(track, index + 1, audioExtension);
return filename;
})();
const fileExtension = path.split('.').pop().toUpperCase();
content += `FILE "${path}" ${fileExtension}\n`;
content += ` TRACK ${trackNumber} AUDIO\n`;
content += ` TITLE "${trackTitle}"\n`;
content += ` PERFORMER "${trackPerformer}"\n`;
// Calculate time in MM:SS:FF format (Frames = 75 per second)
const minutes = Math.floor(currentTime / 60);
const seconds = Math.floor(currentTime % 60);
const frames = Math.floor((currentTime % 1) * 75);
content += ` INDEX 01 ${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${String(frames).padStart(2, '0')}\n`;
currentTime += duration;
content += ` INDEX 01 00:00:00\n`;
});
return content;