From aa728f970ba59e225941d1043710c257edf75e40 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 11 Mar 2026 20:10:38 +0000
Subject: [PATCH] feat(downloads): add discNumber template for file name.
Also update disc number handling in download logic and metadata extraction
---
index.html | 4 ++--
js/downloads.js | 37 ++++---------------------------------
js/metadata.js | 13 ++++++++++---
js/utils.js | 41 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 57 insertions(+), 38 deletions(-)
diff --git a/index.html b/index.html
index 5a253c3..e1c3223 100644
--- a/index.html
+++ b/index.html
@@ -5141,8 +5141,8 @@
Filename Template
Customize download filenames. Available: {trackNumber}, {artist}, {title},
- {album}Customize download filenames. Available: {discNumber}, {trackNumber},
+ {artist}, {title}, {album}
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;
diff --git a/js/metadata.js b/js/metadata.js
index 76e24b9..d8bfb61 100644
--- a/js/metadata.js
+++ b/js/metadata.js
@@ -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;
diff --git a/js/utils.js b/js/utils.js
index 3b5bf8f..b9790e0 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -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;
+}