diff --git a/js/ModernSettings.ts b/js/ModernSettings.ts
index de90cdc..c114181 100644
--- a/js/ModernSettings.ts
+++ b/js/ModernSettings.ts
@@ -261,6 +261,7 @@ export const modernSettings = new ModernSettings()
transformer: String,
},
})
+ .addProperty('writeArtistsSeparately', false)
.finalize() as ModernSettings & {
/** The last used directory handle for bulk downloads */
bulkDownloadFolder: FileSystemDirectoryHandle | null;
@@ -286,4 +287,7 @@ export const modernSettings = new ModernSettings()
/** Filename template for downloads */
filenameTemplate: string;
+
+ /** Whether to write multiple artists to downloaded files */
+ writeArtistsSeparately: boolean;
};
diff --git a/js/metadata.js b/js/metadata.js
index 57fe1c8..b826ffd 100644
--- a/js/metadata.js
+++ b/js/metadata.js
@@ -1,7 +1,15 @@
-import { getCoverBlob, getTrackTitle, getFullArtistString, getMimeType, getTrackCoverId } from './utils.js';
+import {
+ getCoverBlob,
+ getTrackTitle,
+ getFullArtistString,
+ getMimeType,
+ getTrackCoverId,
+ getFullArtistArray,
+} from './utils.js';
import { addMetadataWithTagLib, getMetadataWithTagLib } from './taglib.ts';
import { LyricsManager } from './lyrics.js';
import { Mp4Stik } from './taglib.types.ts';
+import { modernSettings } from './ModernSettings.js';
/**
* @typedef {import('./container-classes.ts').Track} Track
@@ -35,13 +43,15 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
/**
* @type {TagLibMetadata}
*/
- const data = {};
+ const data = {
+ writeArtistsSeparately: modernSettings.writeArtistsSeparately,
+ };
try {
data.title = getTrackTitle(track);
- data.artist = getFullArtistString(track);
+ data.artist = getFullArtistArray(track);
data.albumTitle = track.album?.title;
- data.albumArtist = track.album?.artist?.name || track.artist?.name;
+ data.albumArtist = track.album?.artist?.name || getFullArtistString(track) || '';
data.trackNumber = track.trackNumber;
data.discNumber = track.volumeNumber ?? track.discNumber;
data.totalTracks = track.album?.numberOfTracksOnDisc ?? track.album?.numberOfTracks;
diff --git a/js/settings.js b/js/settings.js
index faa1df0..19951e6 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -4568,6 +4568,15 @@ export async function initializeSettings(scrobbler, player, api, ui) {
});
}
+ // Write multiple artists toggle
+ const writeArtistsSeparatelyToggle = document.getElementById('write-artists-separately-toggle');
+ if (writeArtistsSeparatelyToggle) {
+ writeArtistsSeparatelyToggle.checked = modernSettings.writeArtistsSeparately;
+ writeArtistsSeparatelyToggle.addEventListener('change', (e) => {
+ modernSettings.writeArtistsSeparately = e.target.checked;
+ });
+ }
+
// Download Lyrics Toggle
const downloadLyricsToggle = document.getElementById('download-lyrics-toggle');
if (downloadLyricsToggle) {
diff --git a/js/taglib.types.ts b/js/taglib.types.ts
index 187d29b..45b3b72 100644
--- a/js/taglib.types.ts
+++ b/js/taglib.types.ts
@@ -16,7 +16,8 @@ export interface TagLibWorkerResponse {
export interface TagLibMetadata {
title?: string;
- artist?: string;
+ artist?: string | string[];
+ writeArtistsSeparately?: boolean;
albumTitle?: string;
albumArtist?: string;
trackNumber?: number;
diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts
index fb91b10..c06c495 100644
--- a/js/taglib.worker.ts
+++ b/js/taglib.worker.ts
@@ -29,8 +29,8 @@ import { FileSystemFileHandleStream } from '!/@dantheman827/taglib-ts/src/toolki
import { FlacFile } from '!/@dantheman827/taglib-ts/src/flac/flacFile.js';
import { MpegFile } from '!/@dantheman827/taglib-ts/src/mpeg/mpegFile.js';
import { Mp4File } from '!/@dantheman827/taglib-ts/src/mp4/mp4File.js';
-import { OggFile } from '!/@dantheman827/taglib-ts/src/ogg/oggFile.js';
import { OggVorbisFile } from '!/@dantheman827/taglib-ts/src/ogg/vorbis/vorbisFile.js';
+import { WavFile } from '!/@dantheman827/taglib-ts/src/riff/wav/wavFile';
export const isWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
@@ -41,6 +41,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise<
filename,
title,
artist,
+ writeArtistsSeparately = false,
albumTitle,
albumArtist,
trackNumber,
@@ -74,17 +75,24 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise<
}
const underlying = ref.file();
+ const isFlac = underlying instanceof FlacFile;
const isMp4 = underlying instanceof Mp4File;
const isMpeg = underlying instanceof MpegFile;
+ const isOgg = underlying instanceof OggVorbisFile;
+ const isWav = underlying instanceof WavFile;
+
const needsCombinedTrackDisc = isMp4 || isMpeg;
+ const artistArray = Array.isArray(artist) ? artist : artist ? [artist] : [];
+ const supportsMultiValuedArtist = writeArtistsSeparately && (isFlac || isOgg || isMp4);
+
doTimed('Tagging file', () => {
const props = ref.properties();
if (title) props.replace('TITLE', [title]);
- if (artist) props.replace('ARTIST', [artist]);
+ if (artistArray.length) props.replace('ARTIST', supportsMultiValuedArtist ? artistArray : [artistArray.join('; ')]);
if (albumTitle) props.replace('ALBUM', [albumTitle]);
- if (albumArtist || artist) props.replace('ALBUMARTIST', [albumArtist || artist!]);
+ if (albumArtist || artistArray.length) props.replace('ALBUMARTIST', albumArtist ? [albumArtist] : [artistArray.join('; ')]);
if (trackNumber) {
const trackStr =
diff --git a/js/utils.js b/js/utils.js
index 6c78b17..bd4be2e 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -616,10 +616,10 @@ export const getShareUrl = (path) => {
};
/**
- * Builds a full artist string by combining the track's listed artists
+ * Builds a full artist array by combining the track's listed artists
* with any featured artists parsed from the title (feat./with).
*/
-export function getFullArtistString(track) {
+export function getFullArtistArray(track) {
const knownArtists =
Array.isArray(track.artists) && track.artists.length > 0
? track.artists.map((a) => (typeof a === 'string' ? a : a.name) || '').filter(Boolean)
@@ -646,6 +646,16 @@ export function getFullArtistString(track) {
}
}
+ return knownArtists;
+}
+
+/**
+ * Builds a full artist string by combining the track's listed artists
+ * with any featured artists parsed from the title (feat./with).
+ */
+export function getFullArtistString(track) {
+ const knownArtists = getFullArtistArray(track);
+
return knownArtists.join('; ') || null;
}