feat(dl): add metadata, lyrics, and cover art tagging

Introduce metadata handling for online downloads:
- Embed cover art and lyrics (synced/unsynced) into MP3 files
- Save cover art to album folders and .lrc lyric files as sidecars
- Fetch and parse album/track metadata and lyrics from Deezer API
- Add user settings for artwork and lyrics embedding, LRC export, and cover quality
- Refactor queue manager to run continuously in background
This commit is contained in:
2025-10-02 10:57:27 -04:00
parent d1edc8b7f7
commit 36c0bc7dc7
11 changed files with 568 additions and 15 deletions

View File

@@ -6,6 +6,10 @@ import { fetch } from '@tauri-apps/plugin-http';
import { writeFile, mkdir, remove, rename, exists } from '@tauri-apps/plugin-fs';
import { generateBlowfishKey, decryptChunk } from './crypto';
import { generateTrackPath } from './paths';
import { tagMP3 } from './tagger';
import { downloadCover, saveCoverToAlbumFolder } from './imageDownload';
import { settings } from '$lib/stores/settings';
import { get } from 'svelte/store';
import type { DeezerTrack } from '$lib/types/deezer';
export interface DownloadProgress {
@@ -88,9 +92,36 @@ export async function downloadTrack(
decryptedData = encryptedData;
}
// Write to temp file
console.log('Writing to temp file...');
await writeFile(paths.tempPath, decryptedData);
// Get user settings
const appSettings = get(settings);
// Download cover art if enabled
let coverData: Uint8Array | undefined;
if ((appSettings.embedCoverArt || appSettings.saveCoverToFolder) && track.albumCoverUrl) {
try {
console.log('Downloading cover art...');
coverData = await downloadCover(track.albumCoverUrl);
} catch (error) {
console.warn('Failed to download cover art:', error);
}
}
// Apply tags (currently MP3 only)
let finalData = decryptedData;
if (format === 'MP3_320' || format === 'MP3_128') {
console.log('Tagging MP3 file...');
finalData = await tagMP3(
decryptedData,
track,
appSettings.embedCoverArt ? coverData : undefined,
appSettings.embedLyrics
);
}
// TODO: Add FLAC tagging when library is ready
// Write tagged file to temp
console.log('Writing tagged file to temp...');
await writeFile(paths.tempPath, finalData);
// Move to final location
const finalPath = `${paths.filepath}/${paths.filename}`;
@@ -104,6 +135,27 @@ export async function downloadTrack(
await rename(paths.tempPath, finalPath);
// Save LRC sidecar file if enabled
if (appSettings.saveLrcFile && track.lyrics?.sync) {
try {
const lrcPath = finalPath.replace(/\.[^.]+$/, '.lrc');
console.log('Saving LRC file to:', lrcPath);
await writeFile(lrcPath, new TextEncoder().encode(track.lyrics.sync));
} catch (error) {
console.warn('Failed to save LRC file:', error);
}
}
// Save cover art to album folder if enabled
if (appSettings.saveCoverToFolder && coverData) {
try {
console.log('Saving cover art to album folder...');
await saveCoverToAlbumFolder(coverData, paths.filepath, 'cover');
} catch (error) {
console.warn('Failed to save cover art to folder:', error);
}
}
console.log('Download complete!');
return finalPath;