feat(downloads): add discNumber template for file name.

Also update disc number handling in download logic and metadata extraction
This commit is contained in:
Daniel 2026-03-11 20:10:38 +00:00 committed by GitHub
parent 8cf7979d47
commit aa728f970b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 38 deletions

View file

@ -5141,8 +5141,8 @@
<div class="info">
<span class="label">Filename Template</span>
<span class="description"
>Customize download filenames. Available: {trackNumber}, {artist}, {title},
{album}</span
>Customize download filenames. Available: {discNumber}, {trackNumber},
{artist}, {title}, {album}</span
>
</div>
<input

View file

@ -10,6 +10,7 @@ import {
getCoverBlob,
getExtensionFromBlob,
escapeHtml,
getTrackDiscNumber,
} from './utils.js';
import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js';
import { addMetadataToAudio, prefetchMetadataObjects } from './metadata.js';
@ -34,42 +35,12 @@ async function loadClientZip() {
}
}
function toPositiveInt(value) {
const parsed = parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
function getExplicitTrackDiscNumber(track) {
const candidates = [
track?.volumeNumber,
track?.discNumber,
track?.mediaNumber,
track?.media_number,
track?.volume,
track?.disc,
track?.volume?.number,
track?.disc?.number,
track?.media?.number,
track?.disc,
track?.disc_no,
track?.discNo,
track?.disc_number,
track?.mediaMetadata?.discNumber,
];
for (const candidate of candidates) {
const parsed = toPositiveInt(candidate);
if (parsed) return parsed;
}
return null;
}
async function createDiscLayoutContext(tracks, api) {
if (!playlistSettings.shouldSeparateDiscsInZip()) {
return { separateByDisc: false, resolveDiscNumber: () => 1 };
}
const explicitDiscNumbers = tracks.map((track) => getExplicitTrackDiscNumber(track));
const explicitDiscNumbers = tracks.map((track) => getTrackDiscNumber(track));
const explicitDistinct = new Set(explicitDiscNumbers.filter(Boolean));
if (explicitDistinct.size > 1) {
@ -85,7 +56,7 @@ async function createDiscLayoutContext(tracks, api) {
if (explicitDiscNumbers[index]) return explicitDiscNumbers[index];
try {
const fullTrack = await api.getTrackMetadata(track.id);
return getExplicitTrackDiscNumber(fullTrack);
return getTrackDiscNumber(fullTrack);
} catch {
return null;
}
@ -590,7 +561,7 @@ async function bulkDownloadToZipStream(
...track,
trackPath: trackPaths[index],
})),
(track) => String(getExplicitTrackDiscNumber(track) || 1)
(track) => String(getTrackDiscNumber(track) || 1)
);
const multiDisc = Object.keys(tracksByVolume).length > 1;

View file

@ -1,4 +1,11 @@
import { getCoverBlob, getTrackTitle, getFullArtistString, getMimeType, getTrackCoverId } from './utils.js';
import {
getCoverBlob,
getTrackTitle,
getFullArtistString,
getMimeType,
getTrackCoverId,
getTrackDiscNumber,
} from './utils.js';
import { fetchTagLib, addMetadataWithTagLib, getMetadataWithTagLib } from './taglib.ts';
import { doTimed, doTimedAsync } from './doTimed.ts';
import { managers } from './app.js';
@ -35,7 +42,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
const { coverFetch, lyricsFetch } = prefetchPromises;
/**
* @type {import("./taglib.worker.ts").TagLibMetadata}
* @type {import("./taglib.types.ts").TagLibMetadata}
*/
const data = {};
@ -47,7 +54,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
data.albumTitle = track.album.title;
data.albumArtist = track.album?.artist?.name || track.artist?.name;
data.trackNumber = track.trackNumber;
data.discNumber = track.volumeNumber ?? track.discNumber;
data.discNumber = getTrackDiscNumber(track) || undefined;
data.totalTracks = track.album.numberOfTracks;
data.copyright = track.copyright;
data.isrc = track.isrc;

View file

@ -202,6 +202,7 @@ export const buildTrackFilename = (track, quality, extension = null) => {
const artistName = track.artist?.name || track.artists?.[0]?.name || 'Unknown Artist';
const data = {
discNumber: getTrackDiscNumber(track) || 1,
trackNumber: track.trackNumber,
artist: artistName,
title: getTrackTitle(track),
@ -629,3 +630,43 @@ export function getTrackCoverId(track) {
null
);
}
/**
* Converts a value to a positive integer.
* @param {*} value - The value to convert to a positive integer.
* @returns {number|null} The parsed positive integer, or null if the value is not a finite positive number.
*/
export function toPositiveInt(value) {
const parsed = parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
/**
* Extracts the disc number from a track object by checking multiple possible property names.
* @param {Object} track - The track object to extract the disc number from.
* @returns {number|null} The disc number as a positive integer, or null if no valid disc number is found.
*/
export function getTrackDiscNumber(track) {
const candidates = [
track?.volumeNumber,
track?.discNumber,
track?.mediaNumber,
track?.media_number,
track?.volume,
track?.disc,
track?.volume?.number,
track?.disc?.number,
track?.media?.number,
track?.disc,
track?.disc_no,
track?.discNo,
track?.disc_number,
track?.mediaMetadata?.discNumber,
];
for (const candidate of candidates) {
const parsed = toPositiveInt(candidate);
if (parsed) return parsed;
}
return null;
}