mirror of
https://github.com/markuryy/shark.git
synced 2025-12-15 12:41:02 +00:00
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:
@@ -6,6 +6,7 @@
|
||||
import { settings, loadSettings } from '$lib/stores/settings';
|
||||
import { scanPlaylists, type Playlist } from '$lib/library/scanner';
|
||||
import { downloadQueue } from '$lib/stores/downloadQueue';
|
||||
import { deezerQueueManager } from '$lib/services/deezer/queueManager';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
@@ -22,6 +23,9 @@
|
||||
onMount(async () => {
|
||||
await loadSettings();
|
||||
await loadPlaylists();
|
||||
|
||||
// Start background queue processor
|
||||
deezerQueueManager.start();
|
||||
});
|
||||
|
||||
async function loadPlaylists() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { downloadQueue, clearCompleted, removeFromQueue, type QueueItem } from '$lib/stores/downloadQueue';
|
||||
import { deezerQueueManager } from '$lib/services/deezer/queueManager';
|
||||
|
||||
let queueItems = $state<QueueItem[]>([]);
|
||||
|
||||
@@ -15,9 +14,6 @@
|
||||
.map(id => state.queue[id])
|
||||
.filter(item => item !== undefined);
|
||||
});
|
||||
|
||||
// Start queue processor
|
||||
deezerQueueManager.start();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
setDeezerConcurrency,
|
||||
setDeezerFormat,
|
||||
setDeezerOverwrite,
|
||||
setEmbedCoverArt,
|
||||
setSaveCoverToFolder,
|
||||
setEmbedLyrics,
|
||||
setSaveLrcFile,
|
||||
setCoverImageQuality,
|
||||
loadSettings
|
||||
} from '$lib/stores/settings';
|
||||
import { clearLibrary as clearLibraryDb } from '$lib/library/database';
|
||||
@@ -17,6 +22,11 @@
|
||||
let currentDeezerConcurrency = $state<number>(1);
|
||||
let currentDeezerFormat = $state<'FLAC' | 'MP3_320' | 'MP3_128'>('FLAC');
|
||||
let currentDeezerOverwrite = $state<boolean>(false);
|
||||
let currentEmbedCoverArt = $state<boolean>(true);
|
||||
let currentSaveCoverToFolder = $state<boolean>(true);
|
||||
let currentEmbedLyrics = $state<boolean>(true);
|
||||
let currentSaveLrcFile = $state<boolean>(true);
|
||||
let currentCoverImageQuality = $state<number>(90);
|
||||
let activeTab = $state<'library' | 'deezer' | 'advanced'>('library');
|
||||
|
||||
onMount(async () => {
|
||||
@@ -26,6 +36,11 @@
|
||||
currentDeezerConcurrency = $settings.deezerConcurrency;
|
||||
currentDeezerFormat = $settings.deezerFormat;
|
||||
currentDeezerOverwrite = $settings.deezerOverwrite;
|
||||
currentEmbedCoverArt = $settings.embedCoverArt;
|
||||
currentSaveCoverToFolder = $settings.saveCoverToFolder;
|
||||
currentEmbedLyrics = $settings.embedLyrics;
|
||||
currentSaveLrcFile = $settings.saveLrcFile;
|
||||
currentCoverImageQuality = $settings.coverImageQuality;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
@@ -34,6 +49,11 @@
|
||||
currentDeezerConcurrency = $settings.deezerConcurrency;
|
||||
currentDeezerFormat = $settings.deezerFormat;
|
||||
currentDeezerOverwrite = $settings.deezerOverwrite;
|
||||
currentEmbedCoverArt = $settings.embedCoverArt;
|
||||
currentSaveCoverToFolder = $settings.saveCoverToFolder;
|
||||
currentEmbedLyrics = $settings.embedLyrics;
|
||||
currentSaveLrcFile = $settings.saveLrcFile;
|
||||
currentCoverImageQuality = $settings.coverImageQuality;
|
||||
});
|
||||
|
||||
async function selectMusicFolder() {
|
||||
@@ -196,6 +216,66 @@
|
||||
<label for="deezer-overwrite">Overwrite existing files</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Metadata & Artwork</legend>
|
||||
|
||||
<div class="field-row">
|
||||
<input
|
||||
id="embed-cover"
|
||||
type="checkbox"
|
||||
bind:checked={currentEmbedCoverArt}
|
||||
onchange={() => setEmbedCoverArt(currentEmbedCoverArt)}
|
||||
/>
|
||||
<label for="embed-cover">Embed cover art in files</label>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<input
|
||||
id="save-cover"
|
||||
type="checkbox"
|
||||
bind:checked={currentSaveCoverToFolder}
|
||||
onchange={() => setSaveCoverToFolder(currentSaveCoverToFolder)}
|
||||
/>
|
||||
<label for="save-cover">Save cover art to album folder</label>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<input
|
||||
id="embed-lyrics"
|
||||
type="checkbox"
|
||||
bind:checked={currentEmbedLyrics}
|
||||
onchange={() => setEmbedLyrics(currentEmbedLyrics)}
|
||||
/>
|
||||
<label for="embed-lyrics">Embed lyrics in files</label>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<input
|
||||
id="save-lrc"
|
||||
type="checkbox"
|
||||
bind:checked={currentSaveLrcFile}
|
||||
onchange={() => setSaveLrcFile(currentSaveLrcFile)}
|
||||
/>
|
||||
<label for="save-lrc">Save .lrc lyric files (for Rockbox/FLAC)</label>
|
||||
</div>
|
||||
|
||||
<div class="field-row-stacked">
|
||||
<label for="cover-quality">Cover Image Quality</label>
|
||||
<div class="slider-container">
|
||||
<input
|
||||
id="cover-quality"
|
||||
type="range"
|
||||
min="60"
|
||||
max="100"
|
||||
bind:value={currentCoverImageQuality}
|
||||
onchange={() => setCoverImageQuality(currentCoverImageQuality)}
|
||||
/>
|
||||
<span class="slider-value">{currentCoverImageQuality}%</span>
|
||||
</div>
|
||||
<small class="help-text">JPEG quality for cover images (default: 90%)</small>
|
||||
</div>
|
||||
</fieldset>
|
||||
</section>
|
||||
{:else if activeTab === 'advanced'}
|
||||
<section class="tab-content">
|
||||
|
||||
Reference in New Issue
Block a user