Playlist implémentée testée
This commit is contained in:
		
							
								
								
									
										28
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -369,6 +369,12 @@ dependencies = [ | ||||
|  "vcpkg", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "file-format" | ||||
| version = "0.25.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9ffe3a660c3a1b10e96f304a9413d673b2118d62e4520f7ddf4a4faccfe8b9b9" | ||||
|  | ||||
| [[package]] | ||||
| name = "generic-array" | ||||
| version = "0.14.7" | ||||
| @ -906,6 +912,19 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_yaml" | ||||
| version = "0.9.34+deprecated" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" | ||||
| dependencies = [ | ||||
|  "indexmap", | ||||
|  "itoa", | ||||
|  "ryu", | ||||
|  "serde", | ||||
|  "unsafe-libyaml", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "sha2" | ||||
| version = "0.9.9" | ||||
| @ -946,9 +965,12 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" | ||||
| name = "sweetmusic" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "file-format", | ||||
|  "metadata", | ||||
|  "regex", | ||||
|  "rodio", | ||||
|  "serde", | ||||
|  "serde_yaml", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1201,6 +1223,12 @@ version = "0.1.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" | ||||
|  | ||||
| [[package]] | ||||
| name = "unsafe-libyaml" | ||||
| version = "0.2.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" | ||||
|  | ||||
| [[package]] | ||||
| name = "vcpkg" | ||||
| version = "0.2.15" | ||||
|  | ||||
| @ -4,6 +4,9 @@ version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| file-format = "0.25.0" | ||||
| metadata = "0.1.9" | ||||
| regex = "1.10.6" | ||||
| rodio = { version = "0.19.0", features = ["symphonia-all"] } | ||||
| serde = { version = "1.0.206", features = ["derive"] } | ||||
| serde_yaml = "0.9.34" | ||||
|  | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| # BazNew | ||||
|  | ||||
| Because newer is better. | ||||
|  | ||||
| ## TODO | ||||
| * SongMeta -> Implémenté, testé | ||||
| * Playlist -> Implémenté, testé | ||||
| * Player -> TODO : voir proto.rs pour la structure et les méthodes. | ||||
| * UI -> Après ces trois là. D'abord, faire le player en mode headless. | ||||
|  | ||||
							
								
								
									
										20
									
								
								cool.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								cool.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| --- | ||||
| songs: | ||||
| - title: Big Cheese | ||||
|   artist: Nirvana | ||||
|   album: Bleach | ||||
|   track: 12 | ||||
|   duration: | ||||
|     secs: 221 | ||||
|     nanos: 0 | ||||
|   cover_path: /home/justine/NAS/Musique/A_classer/Bleach/cover.jpg | ||||
|   path: /home/justine/NAS/Musique/A_classer/Bleach/12 Big Cheese.mp3 | ||||
| - title: Dods... | ||||
|   artist: Galaverna | ||||
|   album: Dodsdans | ||||
|   track: 1 | ||||
|   duration: | ||||
|     secs: 148 | ||||
|     nanos: 0 | ||||
|   cover_path: /home/justine/NAS/Musique/Folk/Galaverna - Dodsdans/cover.jpg | ||||
|   path: /home/justine/NAS/Musique/Folk/Galaverna - Dodsdans/Galaverna - Dodsdans - 01 Dods....flac | ||||
							
								
								
									
										14
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,10 +1,18 @@ | ||||
| pub mod songmeta; | ||||
| use crate::songmeta::songmeta::*; | ||||
|  | ||||
| pub mod playlist; | ||||
| use crate::playlist::playlist::*; | ||||
|  | ||||
| fn main() { | ||||
|     let mysong = SongMeta::frompath(&String::from("/home/justine/NAS/Musique/A_classer/Bleach/12 Big Cheese.mp3")); | ||||
|     dbg!(mysong); | ||||
|     let mysong2 = SongMeta::frompath(&String::from("/home/justine/NAS/Musique/Folk/Galaverna - Dodsdans/Galaverna - Dodsdans - 01 Dods....flac")); | ||||
|  | ||||
|     let mysong2 = SongMeta::frompath(&String::from("/home/justine/NAS/Musique/Folk/Galaverna - Dodsdans/Galaverna - Dodsdans - 07 Smell of ember.flac")); | ||||
|     dbg!(mysong2); | ||||
|  | ||||
|     //playlist.remove(0).unwrap(); | ||||
|     let playlist = Playlist::from_file(&String::from("./cool.yml")); | ||||
|  | ||||
|     dbg!(playlist); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										122
									
								
								src/playlist.rs
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								src/playlist.rs
									
									
									
									
									
								
							| @ -1,31 +1,117 @@ | ||||
| pub mod songmeta; | ||||
| use crate::songmeta::songmeta::*; | ||||
| pub mod playlist { | ||||
|  | ||||
| ///Stores a list of SongMetas | ||||
| #[derive(Debug, Clone)] | ||||
| struct Playlist { | ||||
|     songs: Vec<SongMeta>, | ||||
| } | ||||
|     use std::path::{Path,PathBuf}; | ||||
|     use std::error::Error; | ||||
|     use std::fmt; | ||||
|     use std::fs; | ||||
|     use serde::{Deserialize, Serialize}; | ||||
|     use crate::songmeta::songmeta::*; | ||||
|     use std::io::Write; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| enum PlaylistError { | ||||
|  | ||||
|  | ||||
|  | ||||
|     //----------------ERROR | ||||
|  | ||||
|     #[derive(Debug)] | ||||
|     pub enum PlaylistError { | ||||
|         IndexNotFound, | ||||
|     SomethingElse, | ||||
| } | ||||
|         FileNotFound, | ||||
|         FileCantWrite, | ||||
|     } | ||||
|  | ||||
| impl Playlist { | ||||
|     impl fmt::Display for PlaylistError { | ||||
|         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|             match self { | ||||
|                 PlaylistError::IndexNotFound => write!(f, "Playlist does not have that index"), | ||||
|                 PlaylistError::FileNotFound => write!(f, "Given playlist file is not readable or does not exist"), | ||||
|                 PlaylistError::FileCantWrite => write!(f, "Can't write playlist file at the given path. Is it valid ?"), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Error for PlaylistError {} | ||||
|  | ||||
|  | ||||
|     //----------------STRUCT | ||||
|  | ||||
|     ///Stores a list of SongMetas to be used with a player. | ||||
|     ///Index starts at zero !  | ||||
|     #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
|     pub struct Playlist { | ||||
|         pub songs: Vec<SongMeta>, | ||||
|     } | ||||
|  | ||||
|     impl Playlist { | ||||
|         ///Returns a new empty playlist. | ||||
|     fn new() -> Self { | ||||
|         pub fn new() -> Self { | ||||
|             Self { | ||||
|                 songs: vec![], | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     ///Returns a playlist from a file containing a list of audiofile paths. | ||||
|     fn from_file(path: &Path) -> Self; | ||||
|         ///Returns a playlist from a file. | ||||
|         ///File must be yaml | ||||
|         pub fn from_file(path: &String) -> Result<Self, Box<dyn Error>> { | ||||
|  | ||||
|             let p = Path::new(path); | ||||
|  | ||||
|             //Check existence | ||||
|             if !p.exists() { | ||||
|                 return Err(Box::new(PlaylistError::FileNotFound)); | ||||
|             } | ||||
|  | ||||
|             //Read the yaml | ||||
|             let content = fs::read_to_string(p)?; | ||||
|             let res: Playlist = serde_yaml::from_str(&content)?; | ||||
|             return Ok(res); | ||||
|         } | ||||
|  | ||||
|         ///Writes the playlist with its index to a file with the path p | ||||
|         ///Path must be fully qualified | ||||
|         pub fn to_file(&self, path: &String) -> Result<(), Box<dyn Error>>{ | ||||
|             let p = Path::new(&path); | ||||
|  | ||||
|             //Check parent | ||||
|             let mut parent = PathBuf::from(path); | ||||
|             let _ = &parent.pop(); | ||||
|             if !&parent.exists() { | ||||
|                 return Err(Box::new(PlaylistError::FileCantWrite)); | ||||
|             } | ||||
|  | ||||
|             //Serialize and write | ||||
|             let serialized = serde_yaml::to_string(&self.clone())?; | ||||
|             let mut file = fs::OpenOptions::new() | ||||
|                 .write(true) | ||||
|                 .append(false) | ||||
|                 .create(true) | ||||
|                 .open(&p)?; | ||||
|             file.write_all(format!("---\n{}", serialized).as_bytes())?; | ||||
|             return Ok(()); | ||||
|         } | ||||
|  | ||||
|         ///shortcut to myplaylist.songs.push(SongMeta::frompath(&String::from("/a/file/path"))) | ||||
|         pub fn add_song_from_path(&mut self, path: &String) -> Result<(), Box<dyn Error>> { | ||||
|             let song = SongMeta::frompath(path)?; | ||||
|             self.songs.push(song); | ||||
|             return Ok(()); | ||||
|         } | ||||
|  | ||||
|         ///Checks if a number is a valid index in this playlist. | ||||
|         pub fn check_index(&self, index: usize) -> bool { | ||||
|             if index > self.songs.len() { | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         ///Removes a song at index (if possible) | ||||
|     fn pop() -> Result<(), PlayListError>; | ||||
|  | ||||
|  | ||||
|         pub fn remove(&mut self, index: usize) -> Result<(), Box<dyn Error>> { | ||||
|             if !self.check_index(index.clone()) { | ||||
|                 return Err(Box::new(PlaylistError::IndexNotFound)); | ||||
|             } | ||||
|             self.songs.remove(index); | ||||
|             return Ok(()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/proto.rs
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/proto.rs
									
									
									
									
									
								
							| @ -8,11 +8,7 @@ use rodio::{Decoder, OutputStream, Sink, Source}; | ||||
| //-----------------------------------------Structs | ||||
|  | ||||
|  | ||||
| ///Stores a list of SongMetas | ||||
| #[derive(Debug, Clone)] | ||||
| struct Playlist { | ||||
|     songs: Vec<SongMeta>, | ||||
| } | ||||
|  | ||||
|  | ||||
| struct AudioPlayer { | ||||
|     sink: Sink, | ||||
| @ -27,33 +23,10 @@ enum PlayerError { | ||||
|     SomethingElse, | ||||
| } | ||||
|  | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| enum PlaylistError { | ||||
|     IndexNotFound, | ||||
|     SomethingElse, | ||||
| } | ||||
|  | ||||
|  | ||||
| //-------------------------------------Impls | ||||
|  | ||||
|  | ||||
| impl Playlist { | ||||
|     ///Returns a new empty playlist. | ||||
|     fn new() -> Self { | ||||
|         Self { | ||||
|             songs: vec![], | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ///Returns a playlist from a file containing a list of audiofile paths. | ||||
|     fn from_file(path: &Path) -> Self; | ||||
|  | ||||
|     ///Removes a song at index (if possible) | ||||
|     fn pop() -> Result<(), PlayListError>; | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| impl AudioPlayer { | ||||
|  | ||||
| @ -87,11 +60,17 @@ impl AudioPlayer { | ||||
|         self.playlist.append(&mut playlist); | ||||
|     } | ||||
|  | ||||
|     //renvoie un result, avance d'une chanson | ||||
|     fn skip_forward(&mut self); | ||||
|     //renvoie un result, recule d'une chanson | ||||
|     fn skip_backwards(&mut self); | ||||
|     //met en pause renvoie un result | ||||
|     fn pause(&mut self); | ||||
|     //Enleve la pause ou lit la première chanson de la playlist renvoie un result | ||||
|     fn play(&mut self); | ||||
|     //Change le volume envoie un result | ||||
|     fn set_vol(&mut self, vol: f32); | ||||
|     //Saute à la chanson donnée renvoie un result | ||||
|     fn goto(&mut self, index: usize) -> Result<(), PlayerError>; | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -7,19 +7,12 @@ pub mod songmeta { | ||||
|     use std::ffi::OsStr; | ||||
|     use metadata::media_file::MediaFileMetadata; | ||||
|     use std::time::Duration; | ||||
|     use file_format::FileFormat; | ||||
|     use serde::{Deserialize, Serialize}; | ||||
|     use std::io::Write; | ||||
|  | ||||
|     ///Stores metadata about a song. | ||||
|     #[derive(Debug, Clone)] | ||||
|     pub struct SongMeta { | ||||
|         pub title: String, | ||||
|         pub artist: String, | ||||
|         pub album: String, | ||||
|         pub track: usize, | ||||
|         pub duration: Duration, | ||||
|         pub cover_path: Option<String>, | ||||
|         pub path: String, | ||||
|     } | ||||
|  | ||||
|     //----------------------------ERROR | ||||
|     #[derive(Debug)] | ||||
|     pub enum SongError { | ||||
|         ///Given path is not existing or is not a media file. | ||||
| @ -39,6 +32,20 @@ pub mod songmeta { | ||||
|  | ||||
|     impl Error for SongError {} | ||||
|  | ||||
|     //----------------------------STRUCT | ||||
|  | ||||
|     ///Stores metadata about a song. | ||||
|     #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
|     pub struct SongMeta { | ||||
|         pub title: String, | ||||
|         pub artist: String, | ||||
|         pub album: String, | ||||
|         pub track: usize, | ||||
|         pub duration: Duration, | ||||
|         pub cover_path: Option<String>, | ||||
|         pub path: String, | ||||
|     } | ||||
|  | ||||
|  | ||||
|     impl SongMeta { | ||||
|  | ||||
| @ -46,6 +53,10 @@ pub mod songmeta { | ||||
|         ///Returns an error if need be. | ||||
|         ///Also looks for the path of the cover, which may be None. | ||||
|         pub fn frompath(path: &String) -> Result<Self, Box<dyn Error>> { | ||||
|             if !SongMeta::validate_file(path) { | ||||
|                 return Err(Box::new(SongError::NotAudio)); | ||||
|             } | ||||
|  | ||||
|             let fpath = Path::new(path); | ||||
|             let md = match MediaFileMetadata::new(&fpath) { | ||||
|                 Ok(v) => v, | ||||
| @ -142,5 +153,30 @@ pub mod songmeta { | ||||
|             } | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         ///Validates that the file at path is audio. | ||||
|         fn validate_file(path: &String) -> bool { | ||||
|             let valid_formats = vec![ | ||||
|                 "audio/mpeg", | ||||
|                 "audio/x-flac", | ||||
|                 "audio/ogg", | ||||
|                 "audio/vnd.wave", | ||||
|                 "audio/aac", | ||||
|             ]; | ||||
|      | ||||
|             let fmt = match FileFormat::from_file(path.as_str()) { | ||||
|                 Ok(v) => v, | ||||
|                 Err(_) => { | ||||
|                     return false; | ||||
|                 } | ||||
|             }; | ||||
|      | ||||
|             if valid_formats.contains(&fmt.media_type()) { | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user