mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 11:41:02 +00:00
feat(dz): add playlist download, existence check, and improved queue handling
Add ability to download entire playlists as M3U8 files, with UI integration and per-track download actions. Implement track existence checking to avoid duplicate downloads, respecting the overwrite setting. Improve queue manager to sync downloaded tracks to the library incrementally. Refactor playlist parsing and metadata reading to use the Rust backend for better performance and accuracy. Update UI to reflect track existence and download status in playlist views. BREAKING CHANGE: Deezer playlist and track download logic now relies on Rust backend for metadata and new existence checking; some APIs and internal behaviors have changed.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use tauri_plugin_sql::{Migration, MigrationKind};
|
||||
|
||||
mod tagger;
|
||||
mod metadata;
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
@@ -19,6 +20,12 @@ fn tag_audio_file(
|
||||
tagger::tag_audio_file(&path, &metadata, cover_data.as_deref(), embed_lyrics)
|
||||
}
|
||||
|
||||
/// Read metadata from an audio file (MP3 or FLAC)
|
||||
#[tauri::command]
|
||||
fn read_audio_metadata(path: String) -> Result<metadata::AudioMetadata, String> {
|
||||
metadata::read_audio_metadata(&path)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let library_migrations = vec![Migration {
|
||||
@@ -136,7 +143,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.invoke_handler(tauri::generate_handler![greet, tag_audio_file])
|
||||
.invoke_handler(tauri::generate_handler![greet, tag_audio_file, read_audio_metadata])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
87
src-tauri/src/metadata.rs
Normal file
87
src-tauri/src/metadata.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use metaflac::Tag as FlacTag;
|
||||
use id3::{Tag as ID3Tag, TagLike};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
/// Audio file metadata structure
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AudioMetadata {
|
||||
pub title: Option<String>,
|
||||
pub artist: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub album_artist: Option<String>,
|
||||
pub track_number: Option<u32>,
|
||||
pub duration: Option<f64>, // in seconds
|
||||
}
|
||||
|
||||
/// Read metadata from an audio file (MP3 or FLAC)
|
||||
pub fn read_audio_metadata(path: &str) -> Result<AudioMetadata, String> {
|
||||
let path_obj = Path::new(path);
|
||||
|
||||
// Check if file exists
|
||||
if !path_obj.exists() {
|
||||
return Err(format!("File not found: {}", path));
|
||||
}
|
||||
|
||||
// Determine file type by extension
|
||||
let extension = path_obj
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.to_lowercase())
|
||||
.ok_or_else(|| "File has no extension".to_string())?;
|
||||
|
||||
match extension.as_str() {
|
||||
"mp3" => read_mp3_metadata(path),
|
||||
"flac" => read_flac_metadata(path),
|
||||
_ => Err(format!("Unsupported file format: {}", extension)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read metadata from MP3 file
|
||||
fn read_mp3_metadata(path: &str) -> Result<AudioMetadata, String> {
|
||||
let tag = ID3Tag::read_from_path(path)
|
||||
.map_err(|e| format!("Failed to read MP3 tags: {}", e))?;
|
||||
|
||||
Ok(AudioMetadata {
|
||||
title: tag.title().map(|s| s.to_string()),
|
||||
artist: tag.artist().map(|s| s.to_string()),
|
||||
album: tag.album().map(|s| s.to_string()),
|
||||
album_artist: tag.album_artist().map(|s| s.to_string()),
|
||||
track_number: tag.track(),
|
||||
duration: tag.duration().map(|d| d as f64 / 1000.0), // Convert ms to seconds
|
||||
})
|
||||
}
|
||||
|
||||
/// Read metadata from FLAC file
|
||||
fn read_flac_metadata(path: &str) -> Result<AudioMetadata, String> {
|
||||
let tag = FlacTag::read_from_path(path)
|
||||
.map_err(|e| format!("Failed to read FLAC tags: {}", e))?;
|
||||
|
||||
// Helper to get first value from vorbis comment
|
||||
let get_first = |key: &str| -> Option<String> {
|
||||
tag.vorbis_comments()
|
||||
.and_then(|vorbis| vorbis.get(key))
|
||||
.and_then(|values| values.first().map(|s| s.to_string()))
|
||||
};
|
||||
|
||||
// Parse track number
|
||||
let track_number = get_first("TRACKNUMBER")
|
||||
.and_then(|s| s.parse::<u32>().ok());
|
||||
|
||||
// Get duration from streaminfo block (in samples)
|
||||
let duration = tag.get_streaminfo().map(|info| {
|
||||
let samples = info.total_samples;
|
||||
let sample_rate = info.sample_rate;
|
||||
samples as f64 / sample_rate as f64
|
||||
});
|
||||
|
||||
Ok(AudioMetadata {
|
||||
title: get_first("TITLE"),
|
||||
artist: get_first("ARTIST"),
|
||||
album: get_first("ALBUM"),
|
||||
album_artist: get_first("ALBUMARTIST"),
|
||||
track_number,
|
||||
duration,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user