From 14774711964799419c99f020f031c674bff20cb4 Mon Sep 17 00:00:00 2001 From: Justine Date: Sun, 11 Aug 2024 17:18:32 +0200 Subject: [PATCH] =?UTF-8?q?Playlist=20impl=C3=A9ment=C3=A9e=20test=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 28 +++++++++++ Cargo.toml | 3 ++ README.md | 10 ++++ cool.yml | 20 ++++++++ src/main.rs | 16 ++++-- src/playlist.rs | 130 ++++++++++++++++++++++++++++++++++++++++-------- src/proto.rs | 35 +++---------- src/songmeta.rs | 58 +++++++++++++++++---- 8 files changed, 235 insertions(+), 65 deletions(-) create mode 100644 README.md create mode 100644 cool.yml diff --git a/Cargo.lock b/Cargo.lock index e470061..2369ea9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index ab0cb22..a003222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c25ce6 --- /dev/null +++ b/README.md @@ -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. + diff --git a/cool.yml b/cool.yml new file mode 100644 index 0000000..0b5ac91 --- /dev/null +++ b/cool.yml @@ -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 diff --git a/src/main.rs b/src/main.rs index ba1be04..8b50999 100644 --- a/src/main.rs +++ b/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")); + + + //playlist.remove(0).unwrap(); + let playlist = Playlist::from_file(&String::from("./cool.yml")); + + dbg!(playlist); +} + - let mysong2 = SongMeta::frompath(&String::from("/home/justine/NAS/Musique/Folk/Galaverna - Dodsdans/Galaverna - Dodsdans - 07 Smell of ember.flac")); - dbg!(mysong2); -} \ No newline at end of file diff --git a/src/playlist.rs b/src/playlist.rs index 4ca3e3b..b4ed2d7 100644 --- a/src/playlist.rs +++ b/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, -} + 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 { - IndexNotFound, - SomethingElse, -} -impl Playlist { - ///Returns a new empty playlist. - fn new() -> Self { - Self { - songs: vec![], + + + //----------------ERROR + + #[derive(Debug)] + pub enum PlaylistError { + IndexNotFound, + FileNotFound, + FileCantWrite, + } + + 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 ?"), + } } } - ///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 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, + } + + impl Playlist { + ///Returns a new empty playlist. + pub fn new() -> Self { + Self { + songs: vec![], + } + } + + ///Returns a playlist from a file. + ///File must be yaml + pub fn from_file(path: &String) -> Result> { + + 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>{ + 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> { + 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) + pub fn remove(&mut self, index: usize) -> Result<(), Box> { + if !self.check_index(index.clone()) { + return Err(Box::new(PlaylistError::IndexNotFound)); + } + self.songs.remove(index); + return Ok(()); + } + } } \ No newline at end of file diff --git a/src/proto.rs b/src/proto.rs index c5bba47..5bf274b 100644 --- a/src/proto.rs +++ b/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, -} + 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>; } diff --git a/src/songmeta.rs b/src/songmeta.rs index 86a9e15..2d5fe27 100644 --- a/src/songmeta.rs +++ b/src/songmeta.rs @@ -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, - 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, + 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> { + 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; + } } } + +