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}
31
32impl Track {
33    /// Create a new track with required fields
34    pub fn new(file_path: PathBuf, quality: QualityProfile) -> Self {
35        Self {
36            file_path,
37            quality,
38            title: None,
39            track_number: None,
40            album: None,
41            artist: None,
42            album_artist: None,
43            year: None,
44            genre: None,
45            comment: None,
46        }
47    }
48
49    /// Get the filename without extension
50    pub fn get_filename_stem(&self) -> String {
51        self.file_path
52            .file_stem()
53            .and_then(|s| s.to_str())
54            .unwrap_or("unknown")
55            .to_string()
56    }
57
58    /// Get the file extension
59    pub fn get_extension(&self) -> Option<String> {
60        self.file_path
61            .extension()
62            .and_then(|s| s.to_str())
63            .map(|s| s.to_lowercase())
64    }
65
66    /// Check if this is an MP3 file
67    pub fn is_mp3(&self) -> bool {
68        matches!(self.get_extension().as_deref(), Some("mp3"))
69    }
70
71    /// Check if this is an M4A/M4B file
72    pub fn is_m4a(&self) -> bool {
73        matches!(self.get_extension().as_deref(), Some("m4a" | "m4b"))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_track_creation() {
83        let quality = QualityProfile::new(128, 44100, 2, "mp3".to_string(), 3600.0).unwrap();
84        let track = Track::new(PathBuf::from("/path/to/track.mp3"), quality);
85
86        assert_eq!(track.get_filename_stem(), "track");
87        assert_eq!(track.get_extension(), Some("mp3".to_string()));
88        assert!(track.is_mp3());
89        assert!(!track.is_m4a());
90    }
91
92    #[test]
93    fn test_track_extensions() {
94        let quality = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
95        let track_m4a = Track::new(PathBuf::from("/path/to/track.m4a"), quality.clone());
96        let track_m4b = Track::new(PathBuf::from("/path/to/track.m4b"), quality);
97
98        assert!(track_m4a.is_m4a());
99        assert!(track_m4b.is_m4a());
100        assert!(!track_m4a.is_mp3());
101    }
102}