feat(downloads): add metadata to videos

This commit is contained in:
Daniel 2026-03-20 12:52:07 -05:00
parent 5d0d375242
commit f2b8cdc812
2 changed files with 55 additions and 51 deletions

100
js/api.js
View file

@ -15,7 +15,7 @@ import { APICache } from './cache.js';
import { DashDownloader } from './dash-downloader.ts'; import { DashDownloader } from './dash-downloader.ts';
import { HlsDownloader } from './hls-downloader.js'; import { HlsDownloader } from './hls-downloader.js';
import { MP3EncodingError } from './mp3-encoder.js'; import { MP3EncodingError } from './mp3-encoder.js';
import { loadFfmpeg, FfmpegError } from './ffmpeg.js'; import { loadFfmpeg, FfmpegError, ffmpeg } from './ffmpeg.js';
import { triggerDownload, applyAudioPostProcessing } from './download-utils.ts'; import { triggerDownload, applyAudioPostProcessing } from './download-utils.ts';
import { isCustomFormat } from './ffmpegFormats.ts'; import { isCustomFormat } from './ffmpegFormats.ts';
import { DownloadProgress } from './progressEvents.js'; import { DownloadProgress } from './progressEvents.js';
@ -1504,59 +1504,63 @@ export class LosslessAPI {
if (!isVideo) { if (!isVideo) {
blob = await applyAudioPostProcessing(blob, quality, onProgress, options.signal); blob = await applyAudioPostProcessing(blob, quality, onProgress, options.signal);
}
// Add metadata if track information is provided // Add metadata if track information is provided
if (track) { if (track) {
onProgress?.({ onProgress?.({
stage: 'processing', stage: 'processing',
message: 'Adding metadata...', message: 'Adding metadata...',
}); });
const enrichedTrack = { ...track }; const enrichedTrack = { ...track };
if (lookup.info) { if (lookup.info) {
enrichedTrack.replayGain = { enrichedTrack.replayGain = {
trackReplayGain: lookup.info.trackReplayGain, trackReplayGain: lookup.info.trackReplayGain,
trackPeakAmplitude: lookup.info.trackPeakAmplitude, trackPeakAmplitude: lookup.info.trackPeakAmplitude,
albumReplayGain: lookup.info.albumReplayGain, albumReplayGain: lookup.info.albumReplayGain,
albumPeakAmplitude: lookup.info.albumPeakAmplitude, albumPeakAmplitude: lookup.info.albumPeakAmplitude,
}; };
} }
if ( if (track.album?.id && (track.album?.totalDiscs == null || track.album?.numberOfTracksOnDisc == null)) {
track.album?.id &&
(track.album?.totalDiscs == null || track.album?.numberOfTracksOnDisc == null)
) {
try {
const albumData = await this.getAlbum(track.album.id);
if (albumData.tracks?.length > 0) {
const discTrackCounts = new Map();
let maxDiscNumber = 0;
for (const t of albumData.tracks) {
const dn = getTrackDiscNumber(t);
discTrackCounts.set(dn, (discTrackCounts.get(dn) || 0) + 1);
if (dn > maxDiscNumber) maxDiscNumber = dn;
}
const totalDiscs = maxDiscNumber || 1;
const discNumber = getTrackDiscNumber(track);
enrichedTrack.album = {
...(enrichedTrack.album || {}),
totalDiscs: track.album?.totalDiscs ?? totalDiscs,
numberOfTracksOnDisc:
track.album?.numberOfTracksOnDisc ?? discTrackCounts.get(discNumber),
};
}
} catch (e) {
console.warn('Failed to fetch album for disc info:', e);
}
}
onProgress?.(new DownloadProgress('Adding metadata'));
try { try {
blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises); const albumData = await this.getAlbum(track.album.id);
} catch (err) { if (albumData.tracks?.length > 0) {
console.error(err); const discTrackCounts = new Map();
let maxDiscNumber = 0;
for (const t of albumData.tracks) {
const dn = getTrackDiscNumber(t);
discTrackCounts.set(dn, (discTrackCounts.get(dn) || 0) + 1);
if (dn > maxDiscNumber) maxDiscNumber = dn;
}
const totalDiscs = maxDiscNumber || 1;
const discNumber = getTrackDiscNumber(track);
enrichedTrack.album = {
...(enrichedTrack.album || {}),
totalDiscs: track.album?.totalDiscs ?? totalDiscs,
numberOfTracksOnDisc:
track.album?.numberOfTracksOnDisc ?? discTrackCounts.get(discNumber),
};
}
} catch (e) {
console.warn('Failed to fetch album for disc info:', e);
} }
} }
onProgress?.(new DownloadProgress('Adding metadata'));
try {
if (isVideo) {
blob = new File(
[await ffmpeg(blob, ['-c', 'copy'], 'output.mp4', 'video/mp4', onProgress, options.signal)],
'output.mp4',
{ type: 'video/mp4' }
);
}
blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises);
} catch (err) {
console.error(err);
}
} }
if (options.triggerDownload ?? true) { if (options.triggerDownload ?? true) {

View file

@ -34,12 +34,12 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
try { try {
data.title = getTrackTitle(track); data.title = getTrackTitle(track);
data.artist = getFullArtistString(track); data.artist = getFullArtistString(track);
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 = track.volumeNumber ?? track.discNumber;
data.totalTracks = track.album.numberOfTracksOnDisc ?? track.album.numberOfTracks; data.totalTracks = track.album?.numberOfTracksOnDisc ?? track.album?.numberOfTracks;
data.totalDiscs = track.album.totalDiscs; data.totalDiscs = track.album?.totalDiscs;
data.copyright = track.copyright; data.copyright = track.copyright;
data.isrc = track.isrc; data.isrc = track.isrc;
data.explicit = Boolean(track.explicit); data.explicit = Boolean(track.explicit);