feat(downloads): add discNumber template for file name.
Also update disc number handling in download logic and metadata extraction
This commit is contained in:
parent
8cf7979d47
commit
aa728f970b
4 changed files with 57 additions and 38 deletions
|
|
@ -5141,8 +5141,8 @@
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Filename Template</span>
|
<span class="label">Filename Template</span>
|
||||||
<span class="description"
|
<span class="description"
|
||||||
>Customize download filenames. Available: {trackNumber}, {artist}, {title},
|
>Customize download filenames. Available: {discNumber}, {trackNumber},
|
||||||
{album}</span
|
{artist}, {title}, {album}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
getCoverBlob,
|
getCoverBlob,
|
||||||
getExtensionFromBlob,
|
getExtensionFromBlob,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
getTrackDiscNumber,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js';
|
import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js';
|
||||||
import { addMetadataToAudio, prefetchMetadataObjects } from './metadata.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) {
|
async function createDiscLayoutContext(tracks, api) {
|
||||||
if (!playlistSettings.shouldSeparateDiscsInZip()) {
|
if (!playlistSettings.shouldSeparateDiscsInZip()) {
|
||||||
return { separateByDisc: false, resolveDiscNumber: () => 1 };
|
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));
|
const explicitDistinct = new Set(explicitDiscNumbers.filter(Boolean));
|
||||||
|
|
||||||
if (explicitDistinct.size > 1) {
|
if (explicitDistinct.size > 1) {
|
||||||
|
|
@ -85,7 +56,7 @@ async function createDiscLayoutContext(tracks, api) {
|
||||||
if (explicitDiscNumbers[index]) return explicitDiscNumbers[index];
|
if (explicitDiscNumbers[index]) return explicitDiscNumbers[index];
|
||||||
try {
|
try {
|
||||||
const fullTrack = await api.getTrackMetadata(track.id);
|
const fullTrack = await api.getTrackMetadata(track.id);
|
||||||
return getExplicitTrackDiscNumber(fullTrack);
|
return getTrackDiscNumber(fullTrack);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -590,7 +561,7 @@ async function bulkDownloadToZipStream(
|
||||||
...track,
|
...track,
|
||||||
trackPath: trackPaths[index],
|
trackPath: trackPaths[index],
|
||||||
})),
|
})),
|
||||||
(track) => String(getExplicitTrackDiscNumber(track) || 1)
|
(track) => String(getTrackDiscNumber(track) || 1)
|
||||||
);
|
);
|
||||||
const multiDisc = Object.keys(tracksByVolume).length > 1;
|
const multiDisc = Object.keys(tracksByVolume).length > 1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { fetchTagLib, addMetadataWithTagLib, getMetadataWithTagLib } from './taglib.ts';
|
||||||
import { doTimed, doTimedAsync } from './doTimed.ts';
|
import { doTimed, doTimedAsync } from './doTimed.ts';
|
||||||
import { managers } from './app.js';
|
import { managers } from './app.js';
|
||||||
|
|
@ -35,7 +42,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
|
||||||
const { coverFetch, lyricsFetch } = prefetchPromises;
|
const { coverFetch, lyricsFetch } = prefetchPromises;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("./taglib.worker.ts").TagLibMetadata}
|
* @type {import("./taglib.types.ts").TagLibMetadata}
|
||||||
*/
|
*/
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
|
|
@ -47,7 +54,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
|
||||||
data.albumTitle = track.album.title;
|
data.albumTitle = track.album.title;
|
||||||
data.albumArtist = track.album?.artist?.name || track.artist?.name;
|
data.albumArtist = track.album?.artist?.name || track.artist?.name;
|
||||||
data.trackNumber = track.trackNumber;
|
data.trackNumber = track.trackNumber;
|
||||||
data.discNumber = track.volumeNumber ?? track.discNumber;
|
data.discNumber = getTrackDiscNumber(track) || undefined;
|
||||||
data.totalTracks = track.album.numberOfTracks;
|
data.totalTracks = track.album.numberOfTracks;
|
||||||
data.copyright = track.copyright;
|
data.copyright = track.copyright;
|
||||||
data.isrc = track.isrc;
|
data.isrc = track.isrc;
|
||||||
|
|
|
||||||
41
js/utils.js
41
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 artistName = track.artist?.name || track.artists?.[0]?.name || 'Unknown Artist';
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
discNumber: getTrackDiscNumber(track) || 1,
|
||||||
trackNumber: track.trackNumber,
|
trackNumber: track.trackNumber,
|
||||||
artist: artistName,
|
artist: artistName,
|
||||||
title: getTrackTitle(track),
|
title: getTrackTitle(track),
|
||||||
|
|
@ -629,3 +630,43 @@ export function getTrackCoverId(track) {
|
||||||
null
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue