Add a proper library / config example
This commit is contained in:
parent
c3f288eb71
commit
661d848331
9 changed files with 2686 additions and 216 deletions
6
.github/workflows/rust.yml
vendored
6
.github/workflows/rust.yml
vendored
|
@ -30,14 +30,16 @@ jobs:
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
|
- name: Run library tests
|
||||||
|
run: cargo test --verbose --features=library
|
||||||
- name: Run example tests
|
- name: Run example tests
|
||||||
run: cargo test --verbose --examples
|
run: cargo test --verbose --examples
|
||||||
- name: Build benches
|
- name: Build benches
|
||||||
run: cargo +nightly-2022-02-16 bench --verbose --features=bench --no-run
|
run: cargo +nightly-2022-02-16 bench --verbose --features=bench --no-run
|
||||||
- name: Build examples
|
- name: Build examples
|
||||||
run: cargo build --examples --verbose --features=serde
|
run: cargo build --examples --verbose --features=serde,library
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: cargo clippy --examples --features=serde -- -D warnings
|
run: cargo clippy --examples --features=serde,library -- -D warnings
|
||||||
|
|
||||||
build-test-lint-windows:
|
build-test-lint-windows:
|
||||||
name: Windows - build, test and lint
|
name: Windows - build, test and lint
|
||||||
|
|
635
Cargo.lock
generated
635
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
32
Cargo.toml
32
Cargo.toml
|
@ -23,6 +23,12 @@ ffmpeg-static = ["ffmpeg-next/static"]
|
||||||
python-bindings = ["bliss-audio-aubio-rs/fftw3"]
|
python-bindings = ["bliss-audio-aubio-rs/fftw3"]
|
||||||
# Enable the benchmarks with `cargo +nightly bench --features=bench`
|
# Enable the benchmarks with `cargo +nightly bench --features=bench`
|
||||||
bench = []
|
bench = []
|
||||||
|
library = [
|
||||||
|
"serde", "dep:rusqlite", "dep:dirs", "dep:tempdir",
|
||||||
|
"dep:anyhow", "dep:serde_ini", "dep:serde_json",
|
||||||
|
"dep:indicatif",
|
||||||
|
]
|
||||||
|
serde = ["dep:serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ripemd160 = "0.9.0"
|
ripemd160 = "0.9.0"
|
||||||
|
@ -44,13 +50,35 @@ thiserror = "1.0.24"
|
||||||
bliss-audio-aubio-rs = "0.2.0"
|
bliss-audio-aubio-rs = "0.2.0"
|
||||||
strum = "0.21"
|
strum = "0.21"
|
||||||
strum_macros = "0.21"
|
strum_macros = "0.21"
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
|
||||||
rcue = "0.1.1"
|
rcue = "0.1.1"
|
||||||
|
|
||||||
|
# Deps for the library feature
|
||||||
|
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||||
|
serde_json = { version = "1.0.59", optional = true }
|
||||||
|
serde_ini = { version = "0.2.0", optional = true }
|
||||||
|
tempdir = { version = "0.3.7", optional = true }
|
||||||
|
rusqlite = { version = "0.27.0", optional = true }
|
||||||
|
dirs = { version = "4.0.0", optional = true }
|
||||||
|
anyhow = { version = "1.0.58", optional = true }
|
||||||
|
indicatif = { version = "0.17.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mime_guess = "2.0.3"
|
mime_guess = "2.0.3"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
anyhow = "1.0.45"
|
anyhow = "1.0.45"
|
||||||
serde_json = "1.0.59"
|
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
pretty_assertions = "1.2.1"
|
pretty_assertions = "1.2.1"
|
||||||
|
serde_json = "1.0.59"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "library"
|
||||||
|
required-features = ["library"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "library_extra_info"
|
||||||
|
required-features = ["library"]
|
||||||
|
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "playlist"
|
||||||
|
required-features = ["serde"]
|
||||||
|
|
174
examples/library.rs
Normal file
174
examples/library.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/// Basic example of how one would combine bliss with an "audio player",
|
||||||
|
/// through [Library].
|
||||||
|
///
|
||||||
|
/// For simplicity's sake, this example recursively gets songs from a folder
|
||||||
|
/// to emulate an audio player library.
|
||||||
|
use anyhow::Result;
|
||||||
|
use bliss_audio::library::{AppConfigTrait, BaseConfig, Library};
|
||||||
|
use clap::{App, Arg, SubCommand};
|
||||||
|
use glob::glob;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub base_config: BaseConfig,
|
||||||
|
pub music_library_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new(
|
||||||
|
music_library_path: PathBuf,
|
||||||
|
config_path: Option<PathBuf>,
|
||||||
|
database_path: Option<PathBuf>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let base_config = BaseConfig::new(config_path, database_path)?;
|
||||||
|
Ok(Self {
|
||||||
|
base_config,
|
||||||
|
music_library_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppConfigTrait for Config {
|
||||||
|
fn base_config(&self) -> &BaseConfig {
|
||||||
|
&self.base_config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CustomLibrary {
|
||||||
|
fn song_paths(&self) -> Result<Vec<String>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomLibrary for Library<Config> {
|
||||||
|
/// Get all songs in the player library
|
||||||
|
fn song_paths(&self) -> Result<Vec<String>> {
|
||||||
|
let music_path = &self.config.music_library_path;
|
||||||
|
let pattern = Path::new(&music_path).join("**").join("*");
|
||||||
|
|
||||||
|
Ok(glob(&pattern.to_string_lossy())?
|
||||||
|
.map(|e| fs::canonicalize(e.unwrap()).unwrap())
|
||||||
|
.filter(|e| match mime_guess::from_path(e).first() {
|
||||||
|
Some(m) => m.type_() == "audio",
|
||||||
|
None => false,
|
||||||
|
})
|
||||||
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
|
.collect::<Vec<String>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let matches = App::new("library-example")
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.author("Polochon_street")
|
||||||
|
.about("Example binary implementing bliss for an audio player.")
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("init")
|
||||||
|
.about(
|
||||||
|
"Initialize a Library, both storing the config and analyzing folders
|
||||||
|
containing songs.",
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("FOLDER")
|
||||||
|
.help("A folder containing the music library to analyze.")
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("database-path")
|
||||||
|
.short("d")
|
||||||
|
.long("database-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to store the database file containing
|
||||||
|
the songs' analysis. Defaults to XDG_DATA_HOME/bliss-rs/bliss.db.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-path")
|
||||||
|
.short("c")
|
||||||
|
.long("config-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to store the config file containing
|
||||||
|
the library setup. Defaults to XDG_DATA_HOME/bliss-rs/config.json.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("update")
|
||||||
|
.about(
|
||||||
|
"Update a Library's songs, trying to analyze failed songs,
|
||||||
|
as well as songs not in the library.",
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-path")
|
||||||
|
.short("c")
|
||||||
|
.long("config-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to load the config file containing
|
||||||
|
the library setup. Defaults to XDG_DATA_HOME/bliss-rs/config.json.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("playlist")
|
||||||
|
.about(
|
||||||
|
"Make a playlist, starting with the song at SONG_PATH, returning
|
||||||
|
the songs' paths.",
|
||||||
|
)
|
||||||
|
.arg(Arg::with_name("SONG_PATH").takes_value(true))
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-path")
|
||||||
|
.short("c")
|
||||||
|
.long("config-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to load the config file containing
|
||||||
|
the library setup. Defaults to XDG_DATA_HOME/bliss-rs/config.json.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("playlist-length")
|
||||||
|
.short("l")
|
||||||
|
.long("playlist-length")
|
||||||
|
.help("Optional playlist length. Defaults to 20.")
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
if let Some(sub_m) = matches.subcommand_matches("init") {
|
||||||
|
let folder = PathBuf::from(sub_m.value_of("FOLDER").unwrap());
|
||||||
|
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
|
||||||
|
let database_path = sub_m.value_of("database-path").map(PathBuf::from);
|
||||||
|
|
||||||
|
let config = Config::new(folder, config_path, database_path)?;
|
||||||
|
let mut library = Library::new(config)?;
|
||||||
|
|
||||||
|
library.analyze_paths(library.song_paths()?, true)?;
|
||||||
|
} else if let Some(sub_m) = matches.subcommand_matches("update") {
|
||||||
|
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
|
||||||
|
let mut library: Library<Config> = Library::from_config_path(config_path)?;
|
||||||
|
library.update_library(library.song_paths()?, true)?;
|
||||||
|
} else if let Some(sub_m) = matches.subcommand_matches("playlist") {
|
||||||
|
let song_path = sub_m.value_of("SONG_PATH").unwrap();
|
||||||
|
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
|
||||||
|
let playlist_length = sub_m
|
||||||
|
.value_of("playlist-length")
|
||||||
|
.unwrap_or("20")
|
||||||
|
.parse::<usize>()?;
|
||||||
|
let library: Library<Config> = Library::from_config_path(config_path)?;
|
||||||
|
let songs = library.playlist_from::<()>(song_path, playlist_length)?;
|
||||||
|
let song_paths = songs
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.bliss_song.path.to_string_lossy().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
for song in song_paths {
|
||||||
|
println!("{:?}", song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
194
examples/library_extra_info.rs
Normal file
194
examples/library_extra_info.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
/// Basic example of how one would combine bliss with an "audio player",
|
||||||
|
/// through [Library].
|
||||||
|
///
|
||||||
|
/// For simplicity's sake, this example recursively gets songs from a folder
|
||||||
|
/// to emulate an audio player library.
|
||||||
|
use anyhow::Result;
|
||||||
|
use bliss_audio::library::{AppConfigTrait, BaseConfig, Library};
|
||||||
|
use clap::{App, Arg, SubCommand};
|
||||||
|
use glob::glob;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub base_config: BaseConfig,
|
||||||
|
pub music_library_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new(
|
||||||
|
music_library_path: PathBuf,
|
||||||
|
config_path: Option<PathBuf>,
|
||||||
|
database_path: Option<PathBuf>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let base_config = BaseConfig::new(config_path, database_path)?;
|
||||||
|
Ok(Self {
|
||||||
|
base_config,
|
||||||
|
music_library_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppConfigTrait for Config {
|
||||||
|
fn base_config(&self) -> &BaseConfig {
|
||||||
|
&self.base_config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CustomLibrary {
|
||||||
|
fn song_paths_info(&self) -> Result<Vec<(String, ExtraInfo)>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomLibrary for Library<Config> {
|
||||||
|
/// Get all songs in the player library
|
||||||
|
fn song_paths_info(&self) -> Result<Vec<(String, ExtraInfo)>> {
|
||||||
|
let music_path = &self.config.music_library_path;
|
||||||
|
let pattern = Path::new(&music_path).join("**").join("*");
|
||||||
|
|
||||||
|
Ok(glob(&pattern.to_string_lossy())?
|
||||||
|
.map(|e| fs::canonicalize(e.unwrap()).unwrap())
|
||||||
|
.filter_map(|e| {
|
||||||
|
mime_guess::from_path(&e).first().map(|m| {
|
||||||
|
(
|
||||||
|
e.to_string_lossy().to_string(),
|
||||||
|
ExtraInfo {
|
||||||
|
extension: e.extension().map(|e| e.to_string_lossy().to_string()),
|
||||||
|
file_name: e.file_name().map(|e| e.to_string_lossy().to_string()),
|
||||||
|
mime_type: format!("{}/{}", m.type_(), m.subtype()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<(String, ExtraInfo)>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Default)]
|
||||||
|
struct ExtraInfo {
|
||||||
|
extension: Option<String>,
|
||||||
|
file_name: Option<String>,
|
||||||
|
mime_type: String,
|
||||||
|
// TODO add mime-type so it's more real
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let matches = App::new("library-example")
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.author("Polochon_street")
|
||||||
|
.about("Example binary implementing bliss for an audio player.")
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("init")
|
||||||
|
.about(
|
||||||
|
"Initialize a Library, both storing the config and analyzing folders
|
||||||
|
containing songs.",
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("FOLDER")
|
||||||
|
.help("A folder containing the music library to analyze.")
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("database-path")
|
||||||
|
.short("d")
|
||||||
|
.long("database-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to store the database file containing
|
||||||
|
the songs' analysis. Defaults to XDG_DATA_HOME/bliss-rs/bliss.db.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-path")
|
||||||
|
.short("c")
|
||||||
|
.long("config-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to store the config file containing
|
||||||
|
the library setup. Defaults to XDG_DATA_HOME/bliss-rs/config.json.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("update")
|
||||||
|
.about(
|
||||||
|
"Update a Library's songs, trying to analyze failed songs,
|
||||||
|
as well as songs not in the library.",
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-path")
|
||||||
|
.short("c")
|
||||||
|
.long("config-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to load the config file containing
|
||||||
|
the library setup. Defaults to XDG_DATA_HOME/bliss-rs/config.json.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("playlist")
|
||||||
|
.about(
|
||||||
|
"Make a playlist, starting with the song at SONG_PATH, returning
|
||||||
|
the songs' paths.",
|
||||||
|
)
|
||||||
|
.arg(Arg::with_name("SONG_PATH").takes_value(true))
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-path")
|
||||||
|
.short("c")
|
||||||
|
.long("config-path")
|
||||||
|
.help(
|
||||||
|
"Optional path where to load the config file containing
|
||||||
|
the library setup. Defaults to XDG_DATA_HOME/bliss-rs/config.json.",
|
||||||
|
)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("playlist-length")
|
||||||
|
.short("l")
|
||||||
|
.long("playlist-length")
|
||||||
|
.help("Optional playlist length. Defaults to 20.")
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
if let Some(sub_m) = matches.subcommand_matches("init") {
|
||||||
|
let folder = PathBuf::from(sub_m.value_of("FOLDER").unwrap());
|
||||||
|
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
|
||||||
|
let database_path = sub_m.value_of("database-path").map(PathBuf::from);
|
||||||
|
|
||||||
|
let config = Config::new(folder, config_path, database_path)?;
|
||||||
|
let mut library = Library::new(config)?;
|
||||||
|
|
||||||
|
library.analyze_paths_extra_info(library.song_paths_info()?, true)?;
|
||||||
|
} else if let Some(sub_m) = matches.subcommand_matches("update") {
|
||||||
|
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
|
||||||
|
let mut library: Library<Config> = Library::from_config_path(config_path)?;
|
||||||
|
library.update_library_extra_info(library.song_paths_info()?, true)?;
|
||||||
|
} else if let Some(sub_m) = matches.subcommand_matches("playlist") {
|
||||||
|
let song_path = sub_m.value_of("SONG_PATH").unwrap();
|
||||||
|
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
|
||||||
|
let playlist_length = sub_m
|
||||||
|
.value_of("playlist-length")
|
||||||
|
.unwrap_or("20")
|
||||||
|
.parse::<usize>()?;
|
||||||
|
let library: Library<Config> = Library::from_config_path(config_path)?;
|
||||||
|
let songs = library.playlist_from::<ExtraInfo>(song_path, playlist_length)?;
|
||||||
|
let playlist = songs
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
(
|
||||||
|
s.bliss_song.path.to_string_lossy().to_string(),
|
||||||
|
s.extra_info.mime_type,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(String, String)>>();
|
||||||
|
for (path, mime_type) in playlist {
|
||||||
|
println!("{} <{}>", path, mime_type,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,26 +1,16 @@
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use bliss_audio::playlist::{closest_to_first_song, dedup_playlist, euclidean_distance};
|
use bliss_audio::playlist::{closest_to_first_song, dedup_playlist, euclidean_distance};
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use bliss_audio::{analyze_paths, Song};
|
use bliss_audio::{analyze_paths, Song};
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use std::env;
|
use std::env;
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/* Analyzes a folder recursively, and make a playlist out of the file
|
/* Analyzes a folder recursively, and make a playlist out of the file
|
||||||
* provided by the user. */
|
* provided by the user. */
|
||||||
// How to use: ./playlist [-o file.m3u] [-a analysis.json] <folder> <file to start the playlist from>
|
// How to use: ./playlist [-o file.m3u] [-a analysis.json] <folder> <file to start the playlist from>
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let matches = App::new("playlist")
|
let matches = App::new("playlist")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
@ -103,8 +93,3 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "serde"))]
|
|
||||||
fn main() {
|
|
||||||
println!("You need the serde feature enabled to run this file.");
|
|
||||||
}
|
|
||||||
|
|
|
@ -59,6 +59,8 @@
|
||||||
#![warn(rustdoc::missing_doc_code_examples)]
|
#![warn(rustdoc::missing_doc_code_examples)]
|
||||||
mod chroma;
|
mod chroma;
|
||||||
pub mod cue;
|
pub mod cue;
|
||||||
|
#[cfg(feature = "library")]
|
||||||
|
pub mod library;
|
||||||
mod misc;
|
mod misc;
|
||||||
pub mod playlist;
|
pub mod playlist;
|
||||||
mod song;
|
mod song;
|
||||||
|
|
1843
src/library.rs
Normal file
1843
src/library.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -62,6 +62,7 @@ pub struct Song {
|
||||||
/// Song's album's artist name, read from the metadata
|
/// Song's album's artist name, read from the metadata
|
||||||
pub album_artist: Option<String>,
|
pub album_artist: Option<String>,
|
||||||
/// Song's tracked number, read from the metadata
|
/// Song's tracked number, read from the metadata
|
||||||
|
/// TODO normalize this into an integer
|
||||||
pub track_number: Option<String>,
|
pub track_number: Option<String>,
|
||||||
/// Song's genre, read from the metadata (`""` if empty)
|
/// Song's genre, read from the metadata (`""` if empty)
|
||||||
pub genre: Option<String>,
|
pub genre: Option<String>,
|
||||||
|
|
Loading…
Reference in a new issue