Create the base cmus utils module
This commit is contained in:
parent
278f0f2bbd
commit
d3ae9f809b
5 changed files with 213 additions and 0 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -208,6 +208,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"lrc",
|
"lrc",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
|
"typed-builder",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1530,6 +1531,17 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-builder"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6179333b981641242a768f30f371c9baccbfcc03749627000c500ab88bf4528b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lrc = { version = "0.1.7", optional = true }
|
lrc = { version = "0.1.7", optional = true }
|
||||||
notify-rust = { version = "4.7.0", features = ["images"] }
|
notify-rust = { version = "4.7.0", features = ["images"] }
|
||||||
|
typed-builder = "0.12.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
|
|
167
src/cmus/mod.rs
Normal file
167
src/cmus/mod.rs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, TypedBuilder)]
|
||||||
|
pub struct TrackMetadata {
|
||||||
|
tags: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum TrackStatus {
|
||||||
|
Playing,
|
||||||
|
Paused,
|
||||||
|
Stopped,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, TypedBuilder, PartialEq)]
|
||||||
|
pub struct Track {
|
||||||
|
pub status: TrackStatus,
|
||||||
|
pub path: String,
|
||||||
|
pub metadata: TrackMetadata,
|
||||||
|
pub duration: u32,
|
||||||
|
pub position: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for TrackStatus {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"playing" => Ok(TrackStatus::Playing),
|
||||||
|
"paused" => Ok(TrackStatus::Paused),
|
||||||
|
"stopped" => Ok(TrackStatus::Stopped),
|
||||||
|
_ => Err(format!("Unknown status: {}", s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Track {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
/// Creates a `Track` from the output of `cmus-remote -Q`.
|
||||||
|
///
|
||||||
|
/// Pares the first 4 lines.
|
||||||
|
/// The first line is the status, the second is the path, the third is the duration, and the fourth is the position.
|
||||||
|
/// The rest of the lines are tags, and the player settings, so we'll send them to `TrackMetadata::parse`, to get the tags.
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut lines = s.lines();
|
||||||
|
|
||||||
|
Ok(Track::builder().status(
|
||||||
|
TrackStatus::from_str(lines.next().ok_or("Missing status")?.split_once(' ')
|
||||||
|
.ok_or("Unknown status")?.1)?
|
||||||
|
)
|
||||||
|
.path(lines.next().ok_or("Missing path")?.split_once(' ')
|
||||||
|
.ok_or("Empty path")?.1.to_string())
|
||||||
|
.duration(
|
||||||
|
lines.next().ok_or("Missing duration")?.split_once(' ')
|
||||||
|
.ok_or("Empty duration")?.1.parse().map_err(|e: ParseIntError| e.to_string())?
|
||||||
|
)
|
||||||
|
.position(
|
||||||
|
lines.next().ok_or("Missing position")?.split_once(' ')
|
||||||
|
.ok_or("Empty position")?.1.parse().map_err(|e: ParseIntError| e.to_string())?
|
||||||
|
)
|
||||||
|
.metadata(TrackMetadata::parse(lines))
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackMetadata {
|
||||||
|
/// Parse the tags from the rest of `cmus-remote -Q` output.
|
||||||
|
/// This function will assume you processed the first 4 lines, and remove them from the iterator.
|
||||||
|
///
|
||||||
|
/// and also assume the all tags is contained in the iterator.
|
||||||
|
fn parse<'a>(mut lines: impl Iterator<Item=&'a str>) -> Self {
|
||||||
|
let mut tags = HashMap::new();
|
||||||
|
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
match line.trim().split_once(' ') {
|
||||||
|
Some(("tag", rest)) => {
|
||||||
|
let Some((key, value)) = rest.split_once(' ') else {
|
||||||
|
continue; // Ignore lines that don't have a key and a value.
|
||||||
|
};
|
||||||
|
tags.insert(key.to_string(), value.to_string());
|
||||||
|
}
|
||||||
|
_ => break, // We've reached the end of the tags.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackMetadata { tags }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, key: &str) -> Option<&str> {
|
||||||
|
self.tags.get(key).map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::assert_matches::assert_matches;
|
||||||
|
|
||||||
|
const OUTPUT_WITH_ALL_TAGS: &str = include_str!("../../tests/samples/cmus-remote-output-with-all-tags.txt");
|
||||||
|
|
||||||
|
const SOME_TAGS: &str = r#"tag artist Alex Goot
|
||||||
|
tag album Alex Goot & Friends, Vol. 3
|
||||||
|
tag title Photograph
|
||||||
|
tag date 2014
|
||||||
|
tag genre Pop
|
||||||
|
tag discnumber 1
|
||||||
|
tag tracknumber 8
|
||||||
|
tag albumartist Alex Goot
|
||||||
|
tag replaygain_track_gain -9.4 dB
|
||||||
|
tag composer Chad Kroeger
|
||||||
|
tag label mudhutdigital.com
|
||||||
|
tag publisher mudhutdigital.com
|
||||||
|
tag bpm 146
|
||||||
|
set aaa_mode artist
|
||||||
|
..."#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_track_from_str() {
|
||||||
|
let track = Track::from_str(OUTPUT_WITH_ALL_TAGS);
|
||||||
|
|
||||||
|
assert_matches!(track, Ok(_));
|
||||||
|
|
||||||
|
let track = track.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(track.status, TrackStatus::Playing);
|
||||||
|
assert_eq!(track.path, "/mnt/Data/Music/FLAC/Alex Goot/Alex Goot - Alex Goot & Friends, Vol. 3/08 - Photograph.mp3");
|
||||||
|
assert_eq!(track.duration, 284);
|
||||||
|
assert_eq!(track.position, 226);
|
||||||
|
assert_eq!(track.metadata.tags.get("artist"), Some(&"Alex Goot".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("album"), Some(&"Alex Goot & Friends, Vol. 3".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("title"), Some(&"Photograph".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("date"), Some(&"2014".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("genre"), Some(&"Pop".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("discnumber"), Some(&"1".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("tracknumber"), Some(&"8".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("albumartist"), Some(&"Alex Goot".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("replaygain_track_gain"), Some(&"-9.4 dB".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("composer"), Some(&"Chad Kroeger".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("label"), Some(&"mudhutdigital.com".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("publisher"), Some(&"mudhutdigital.com".to_string()));
|
||||||
|
assert_eq!(track.metadata.tags.get("bpm"), Some(&"146".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_metadata_from_the_string() {
|
||||||
|
let metadata = TrackMetadata::parse(SOME_TAGS.lines());
|
||||||
|
|
||||||
|
assert_eq!(metadata.tags.get("artist"), Some(&"Alex Goot".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("album"), Some(&"Alex Goot & Friends, Vol. 3".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("title"), Some(&"Photograph".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("date"), Some(&"2014".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("genre"), Some(&"Pop".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("discnumber"), Some(&"1".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("tracknumber"), Some(&"8".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("albumartist"), Some(&"Alex Goot".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("replaygain_track_gain"), Some(&"-9.4 dB".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("composer"), Some(&"Chad Kroeger".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("label"), Some(&"mudhutdigital.com".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("publisher"), Some(&"mudhutdigital.com".to_string()));
|
||||||
|
assert_eq!(metadata.tags.get("bpm"), Some(&"146".to_string()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
|
#![feature(assert_matches)]
|
||||||
|
|
||||||
mod arguments;
|
mod arguments;
|
||||||
|
mod cmus;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
|
30
tests/samples/cmus-remote-output-with-all-tags.txt
Normal file
30
tests/samples/cmus-remote-output-with-all-tags.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
status playing
|
||||||
|
file /mnt/Data/Music/FLAC/Alex Goot/Alex Goot - Alex Goot & Friends, Vol. 3/08 - Photograph.mp3
|
||||||
|
duration 284
|
||||||
|
position 226
|
||||||
|
tag artist Alex Goot
|
||||||
|
tag album Alex Goot & Friends, Vol. 3
|
||||||
|
tag title Photograph
|
||||||
|
tag date 2014
|
||||||
|
tag genre Pop
|
||||||
|
tag discnumber 1
|
||||||
|
tag tracknumber 8
|
||||||
|
tag albumartist Alex Goot
|
||||||
|
tag replaygain_track_gain -9.4 dB
|
||||||
|
tag composer Chad Kroeger
|
||||||
|
tag label mudhutdigital.com
|
||||||
|
tag publisher mudhutdigital.com
|
||||||
|
tag bpm 146
|
||||||
|
set aaa_mode artist
|
||||||
|
set continue true
|
||||||
|
set play_library true
|
||||||
|
set play_sorted true
|
||||||
|
set replaygain disabled
|
||||||
|
set replaygain_limit true
|
||||||
|
set replaygain_preamp 0.000000
|
||||||
|
set repeat false
|
||||||
|
set repeat_current false
|
||||||
|
set shuffle tracks
|
||||||
|
set softvol false
|
||||||
|
set vol_left 46
|
||||||
|
set vol_right 46
|
Loading…
Reference in a new issue