Skip to main content

audiobook_forge/models/
track.rs

1//! Audio track model
2
3use super::QualityProfile;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7/// Represents a single audio track in an audiobook
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Track {
10    /// Path to the audio file
11    pub file_path: PathBuf,
12    /// Quality profile of this track
13    pub quality: QualityProfile,
14    /// Track title (from metadata or filename)
15    pub title: Option<String>,
16    /// Track number
17    pub track_number: Option<u32>,
18    /// Album/book title
19    pub album: Option<String>,
20    /// Artist/author
21    pub artist: Option<String>,
22    /// Album artist
23    pub album_artist: Option<String>,
24    /// Year
25    pub year: Option<u32>,
26    /// Genre
27    pub genre: Option<String>,
28    /// Comment
29    pub comment: Option<String>,
30    /// Composer
31    pub composer: Option<String>,
32}
33
34impl Track {
35    /// Create a new track with required fields
36    pub fn new(file_path: PathBuf, quality: QualityProfile) -> Self {
37        Self {
38            file_path,
39            quality,
40            title: None,
41            track_number: None,
42            album: None,
43            artist: None,
44            album_artist: None,
45            year: None,
46            genre: None,
47            comment: None,
48            composer: None,
49        }
50    }
51
52    /// Get the filename without extension
53    pub fn get_filename_stem(&self) -> String {
54        self.file_path
55            .file_stem()
56            .and_then(|s| s.to_str())
57            .unwrap_or("unknown")
58            .to_string()
59    }
60
61    /// Get the file extension
62    pub fn get_extension(&self) -> Option<String> {
63        self.file_path
64            .extension()
65            .and_then(|s| s.to_str())
66            .map(|s| s.to_lowercase())
67    }
68
69    /// Check if this is an MP3 file
70    pub fn is_mp3(&self) -> bool {
71        matches!(self.get_extension().as_deref(), Some("mp3"))
72    }
73
74    /// Check if this is an M4A/M4B file
75    pub fn is_m4a(&self) -> bool {
76        matches!(self.get_extension().as_deref(), Some("m4a" | "m4b"))
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_track_creation() {
86        let quality = QualityProfile::new(128, 44100, 2, "mp3".to_string(), 3600.0).unwrap();
87        let track = Track::new(PathBuf::from("/path/to/track.mp3"), quality);
88
89        assert_eq!(track.get_filename_stem(), "track");
90        assert_eq!(track.get_extension(), Some("mp3".to_string()));
91        assert!(track.is_mp3());
92        assert!(!track.is_m4a());
93    }
94
95    #[test]
96    fn test_track_extensions() {
97        let quality = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
98        let track_m4a = Track::new(PathBuf::from("/path/to/track.m4a"), quality.clone());
99        let track_m4b = Track::new(PathBuf::from("/path/to/track.m4b"), quality);
100
101        assert!(track_m4a.is_m4a());
102        assert!(track_m4b.is_m4a());
103        assert!(!track_m4a.is_mp3());
104    }
105}