feat(library): integrate local library module from Codex
Full local library implementation with: - Filesystem scanner for FLAC, WAV, AIFF, APE, ALAC files - Metadata extraction using lofty crate - CUE sheet parsing for single-file albums - SQLite persistence with WAL mode - 13 Tauri commands for library management Commands: - library_add_folder, library_remove_folder, library_get_folders - library_scan, library_get_scan_progress - library_get_albums, library_get_album_tracks, library_get_artists - library_search, library_get_stats, library_clear - library_get_track, library_play_track (with CUE seek support) Based on implementation by Codex in local-library worktree.
This commit is contained in:
1 parent
8b378ddb28
commit
1895812830
10 files changed
+1704
No files matched your search
@@ -0,0 +1,168 @@
|
||||
//! Data models for local library
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Supported audio formats
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AudioFormat {
|
||||
Flac,
|
||||
Alac,
|
||||
Wav,
|
||||
Aiff,
|
||||
Ape,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Default for AudioFormat {
|
||||
fn default() -> Self {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AudioFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AudioFormat::Flac => write!(f, "FLAC"),
|
||||
AudioFormat::Alac => write!(f, "ALAC"),
|
||||
AudioFormat::Wav => write!(f, "WAV"),
|
||||
AudioFormat::Aiff => write!(f, "AIFF"),
|
||||
AudioFormat::Ape => write!(f, "APE"),
|
||||
AudioFormat::Unknown => write!(f, "Unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A track from the local library
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LocalTrack {
|
||||
pub id: i64,
|
||||
pub file_path: String,
|
||||
|
||||
// Metadata
|
||||
pub title: String,
|
||||
pub artist: String,
|
||||
pub album: String,
|
||||
pub album_artist: Option<String>,
|
||||
pub track_number: Option<u32>,
|
||||
pub disc_number: Option<u32>,
|
||||
pub year: Option<u32>,
|
||||
pub genre: Option<String>,
|
||||
|
||||
// Audio properties
|
||||
pub duration_secs: u64,
|
||||
pub format: AudioFormat,
|
||||
pub bit_depth: Option<u32>,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u8,
|
||||
pub file_size_bytes: u64,
|
||||
|
||||
// CUE support
|
||||
pub cue_file_path: Option<String>,
|
||||
pub cue_start_secs: Option<f64>,
|
||||
pub cue_end_secs: Option<f64>,
|
||||
|
||||
// Artwork
|
||||
pub artwork_path: Option<String>,
|
||||
|
||||
// Indexing
|
||||
pub last_modified: i64,
|
||||
pub indexed_at: i64,
|
||||
}
|
||||
|
||||
impl Default for LocalTrack {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
file_path: String::new(),
|
||||
title: String::new(),
|
||||
artist: "Unknown Artist".to_string(),
|
||||
album: "Unknown Album".to_string(),
|
||||
album_artist: None,
|
||||
track_number: None,
|
||||
disc_number: None,
|
||||
year: None,
|
||||
genre: None,
|
||||
duration_secs: 0,
|
||||
format: AudioFormat::Unknown,
|
||||
bit_depth: None,
|
||||
sample_rate: 44100,
|
||||
channels: 2,
|
||||
file_size_bytes: 0,
|
||||
cue_file_path: None,
|
||||
cue_start_secs: None,
|
||||
cue_end_secs: None,
|
||||
artwork_path: None,
|
||||
last_modified: 0,
|
||||
indexed_at: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An album aggregated from local tracks
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LocalAlbum {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub artist: String,
|
||||
pub year: Option<u32>,
|
||||
pub artwork_path: Option<String>,
|
||||
pub track_count: u32,
|
||||
pub total_duration_secs: u64,
|
||||
pub format: AudioFormat,
|
||||
pub directory_path: String,
|
||||
}
|
||||
|
||||
/// An artist aggregated from local tracks
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LocalArtist {
|
||||
pub name: String,
|
||||
pub album_count: u32,
|
||||
pub track_count: u32,
|
||||
}
|
||||
|
||||
/// Scan progress for UI updates
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ScanProgress {
|
||||
pub status: ScanStatus,
|
||||
pub total_files: u32,
|
||||
pub processed_files: u32,
|
||||
pub current_file: Option<String>,
|
||||
pub errors: Vec<ScanError>,
|
||||
}
|
||||
|
||||
impl Default for ScanProgress {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
status: ScanStatus::Idle,
|
||||
total_files: 0,
|
||||
processed_files: 0,
|
||||
current_file: None,
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan status
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ScanStatus {
|
||||
Idle,
|
||||
Scanning,
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// A scan error for a specific file
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ScanError {
|
||||
pub file_path: String,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// Audio properties extracted from a file
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AudioProperties {
|
||||
pub duration_secs: u64,
|
||||
pub bit_depth: Option<u32>,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u8,
|
||||
}
|
||||
Reference in new issue
Block a user