Playlist implémentée testée
This commit is contained in:
parent
194cfdff0d
commit
1477471196
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -369,6 +369,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "file-format"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ffe3a660c3a1b10e96f304a9413d673b2118d62e4520f7ddf4a4faccfe8b9b9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@ -906,6 +912,19 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
@ -946,9 +965,12 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|||||||
name = "sweetmusic"
|
name = "sweetmusic"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"file-format",
|
||||||
"metadata",
|
"metadata",
|
||||||
"regex",
|
"regex",
|
||||||
"rodio",
|
"rodio",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1201,6 +1223,12 @@ version = "0.1.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
file-format = "0.25.0"
|
||||||
metadata = "0.1.9"
|
metadata = "0.1.9"
|
||||||
regex = "1.10.6"
|
regex = "1.10.6"
|
||||||
rodio = { version = "0.19.0", features = ["symphonia-all"] }
|
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;
|
pub mod songmeta;
|
||||||
use crate::songmeta::songmeta::*;
|
use crate::songmeta::songmeta::*;
|
||||||
|
|
||||||
|
pub mod playlist;
|
||||||
|
use crate::playlist::playlist::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mysong = SongMeta::frompath(&String::from("/home/justine/NAS/Musique/A_classer/Bleach/12 Big Cheese.mp3"));
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
118
src/playlist.rs
118
src/playlist.rs
@ -1,31 +1,117 @@
|
|||||||
pub mod songmeta;
|
pub mod playlist {
|
||||||
use crate::songmeta::songmeta::*;
|
|
||||||
|
|
||||||
///Stores a list of SongMetas
|
use std::path::{Path,PathBuf};
|
||||||
#[derive(Debug, Clone)]
|
use std::error::Error;
|
||||||
struct Playlist {
|
use std::fmt;
|
||||||
songs: Vec<SongMeta>,
|
use std::fs;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::songmeta::songmeta::*;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//----------------ERROR
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PlaylistError {
|
||||||
|
IndexNotFound,
|
||||||
|
FileNotFound,
|
||||||
|
FileCantWrite,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl fmt::Display for PlaylistError {
|
||||||
enum PlaylistError {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
IndexNotFound,
|
match self {
|
||||||
SomethingElse,
|
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 {
|
impl Playlist {
|
||||||
///Returns a new empty playlist.
|
///Returns a new empty playlist.
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
songs: vec![],
|
songs: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///Returns a playlist from a file containing a list of audiofile paths.
|
///Returns a playlist from a file.
|
||||||
fn from_file(path: &Path) -> Self;
|
///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)
|
///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
|
//-----------------------------------------Structs
|
||||||
|
|
||||||
|
|
||||||
///Stores a list of SongMetas
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct Playlist {
|
|
||||||
songs: Vec<SongMeta>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AudioPlayer {
|
struct AudioPlayer {
|
||||||
sink: Sink,
|
sink: Sink,
|
||||||
@ -27,33 +23,10 @@ enum PlayerError {
|
|||||||
SomethingElse,
|
SomethingElse,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum PlaylistError {
|
|
||||||
IndexNotFound,
|
|
||||||
SomethingElse,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------Impls
|
//-------------------------------------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 {
|
impl AudioPlayer {
|
||||||
|
|
||||||
@ -87,11 +60,17 @@ impl AudioPlayer {
|
|||||||
self.playlist.append(&mut playlist);
|
self.playlist.append(&mut playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//renvoie un result, avance d'une chanson
|
||||||
fn skip_forward(&mut self);
|
fn skip_forward(&mut self);
|
||||||
|
//renvoie un result, recule d'une chanson
|
||||||
fn skip_backwards(&mut self);
|
fn skip_backwards(&mut self);
|
||||||
|
//met en pause renvoie un result
|
||||||
fn pause(&mut self);
|
fn pause(&mut self);
|
||||||
|
//Enleve la pause ou lit la première chanson de la playlist renvoie un result
|
||||||
fn play(&mut self);
|
fn play(&mut self);
|
||||||
|
//Change le volume envoie un result
|
||||||
fn set_vol(&mut self, vol: f32);
|
fn set_vol(&mut self, vol: f32);
|
||||||
|
//Saute à la chanson donnée renvoie un result
|
||||||
fn goto(&mut self, index: usize) -> Result<(), PlayerError>;
|
fn goto(&mut self, index: usize) -> Result<(), PlayerError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,19 +7,12 @@ pub mod songmeta {
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use metadata::media_file::MediaFileMetadata;
|
use metadata::media_file::MediaFileMetadata;
|
||||||
use std::time::Duration;
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum SongError {
|
pub enum SongError {
|
||||||
///Given path is not existing or is not a media file.
|
///Given path is not existing or is not a media file.
|
||||||
@ -39,6 +32,20 @@ pub mod songmeta {
|
|||||||
|
|
||||||
impl Error for SongError {}
|
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 {
|
impl SongMeta {
|
||||||
|
|
||||||
@ -46,6 +53,10 @@ pub mod songmeta {
|
|||||||
///Returns an error if need be.
|
///Returns an error if need be.
|
||||||
///Also looks for the path of the cover, which may be None.
|
///Also looks for the path of the cover, which may be None.
|
||||||
pub fn frompath(path: &String) -> Result<Self, Box<dyn Error>> {
|
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 fpath = Path::new(path);
|
||||||
let md = match MediaFileMetadata::new(&fpath) {
|
let md = match MediaFileMetadata::new(&fpath) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
@ -142,5 +153,30 @@ pub mod songmeta {
|
|||||||
}
|
}
|
||||||
return None;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user