use crate::json::Json;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnknownName {
pub(crate) kind: &'static str,
pub(crate) name: String,
}
impl UnknownName {
pub fn name(&self) -> &str {
&self.name
}
}
impl std::fmt::Display for UnknownName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unknown {} name: {:?}", self.kind, self.name)
}
}
impl std::error::Error for UnknownName {}
macro_rules! string_enum {
($(#[$meta:meta])* $name:ident { $($variant:ident => $text:literal),+ $(,)? }) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum $name {
$(
#[doc = concat!("`", stringify!($text), "` in the spec and in JSON.")]
$variant
),+
}
impl $name {
pub const ALL: &'static [$name] = &[$($name::$variant),+];
pub const NAMES: &'static [&'static str] = &[$($text),+];
pub fn as_str(self) -> &'static str {
match self { $($name::$variant => $text),+ }
}
pub fn from_name(name: &str) -> Option<$name> {
match name { $($text => Some($name::$variant),)+ _ => None }
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for $name {
type Err = $crate::types::UnknownName;
fn from_str(name: &str) -> Result<$name, Self::Err> {
$name::from_name(name).ok_or_else(|| $crate::types::UnknownName {
kind: stringify!($name),
name: name.to_string(),
})
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for $name {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<$name, D::Error> {
let name = <String as serde::Deserialize>::deserialize(deserializer)?;
$name::from_name(&name)
.ok_or_else(|| serde::de::Error::unknown_variant(&name, $name::NAMES))
}
}
};
}
pub(crate) use string_enum;
macro_rules! json_methods {
($($name:ty),+ $(,)?) => {
$(
impl $crate::types::sealed::Sealed for $name {}
impl $name {
pub fn to_json(&self) -> String {
$crate::types::ToJson::to_json_value(self).write()
}
}
)+
};
}
pub(crate) use json_methods;
string_enum! {
#[non_exhaustive]
Mode { Clean => "clean", Youtube => "youtube", Filename => "filename", Tracklist => "tracklist" }
}
string_enum! {
#[non_exhaustive]
ArtistRole { Primary => "primary", Featured => "featured", Remixer => "remixer", Producer => "producer" }
}
string_enum! {
#[non_exhaustive]
ArtistSource { Artist => "artist", Title => "title", Version => "version" }
}
string_enum! {
#[non_exhaustive]
VersionType {
Remix => "remix", Bootleg => "bootleg", Vip => "vip", Edit => "edit", Flip => "flip",
Refix => "refix", Rework => "rework", Mashup => "mashup", Blend => "blend", Dub => "dub",
Mix => "mix", Extended => "extended", Radio => "radio", Club => "club",
Original => "original", Instrumental => "instrumental", Acapella => "acapella",
Live => "live", Acoustic => "acoustic", Remaster => "remaster", Demo => "demo",
Reprise => "reprise", Cover => "cover", Version => "version", SpedUp => "spedUp",
Slowed => "slowed", Nightcore => "nightcore",
}
}
string_enum! {
VersionDelimiter { Paren => "(", Square => "[", Curly => "{", Dash => "-" }
}
string_enum! {
#[non_exhaustive]
JunkKind {
Video => "video", Audio => "audio", Lyrics => "lyrics", Quality => "quality",
Promo => "promo", Platform => "platform", Label => "label", Genre => "genre",
Other => "other",
}
}
string_enum! {
#[non_exhaustive]
Warning {
NoSeparator => "noSeparator", AmbiguousSeparator => "ambiguousSeparator",
UnspacedDashSplit => "unspacedDashSplit", AsymmetricDashSplit => "asymmetricDashSplit",
BySplit => "bySplit", QuotedTitleSplit => "quotedTitleSplit",
AmbiguousMixCredit => "ambiguousMixCredit", UnbalancedBrackets => "unbalancedBrackets",
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Artist {
pub name: String,
pub role: ArtistRole,
pub joiner: Option<String>,
pub source: ArtistSource,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Version {
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub version_type: VersionType,
pub raw: String,
pub artists: Vec<Artist>,
pub modifiers: Vec<String>,
pub descriptor: Option<String>,
pub year: Option<u32>,
pub unknown_artist: bool,
pub delimiter: VersionDelimiter,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Junk {
pub raw: String,
pub kind: JunkKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Flags {
pub explicit: bool,
pub clean: bool,
pub unknown_artist: bool,
pub unknown_title: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Position {
pub raw: String,
pub number: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Timestamp {
pub raw: String,
pub seconds: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct ParsedTrack {
pub input: String,
pub mode: Mode,
pub position: Option<Position>,
pub timestamp: Option<Timestamp>,
pub artists: Vec<Artist>,
pub title: String,
pub full_title: String,
pub versions: Vec<Version>,
pub year: Option<u32>,
pub flags: Flags,
pub junk: Vec<Junk>,
pub warnings: Vec<Warning>,
}
json_methods!(Artist, Version, Junk, Flags, Position, Timestamp, ParsedTrack);
pub trait ToJson: sealed::Sealed {
fn to_json_value(&self) -> Json;
fn to_json(&self) -> String {
self.to_json_value().write()
}
}
pub(crate) mod sealed {
pub trait Sealed {}
}
pub(crate) fn obj(entries: Vec<(&str, Json)>) -> Json {
Json::Obj(entries.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
}
pub(crate) fn list<T: ToJson>(items: &[T]) -> Json {
Json::Arr(items.iter().map(ToJson::to_json_value).collect())
}
pub(crate) fn opt<T: ToJson>(v: &Option<T>) -> Json {
v.as_ref().map_or(Json::Null, ToJson::to_json_value)
}
fn num_opt(v: Option<u32>) -> Json {
v.map_or(Json::Null, |n| Json::Num(f64::from(n)))
}
impl ToJson for Artist {
fn to_json_value(&self) -> Json {
obj(vec![
("name", Json::str(&self.name)),
("role", Json::str(self.role.as_str())),
("joiner", Json::opt_str(self.joiner.as_deref())),
("source", Json::str(self.source.as_str())),
])
}
}
impl ToJson for Version {
fn to_json_value(&self) -> Json {
obj(vec![
("type", Json::str(self.version_type.as_str())),
("raw", Json::str(&self.raw)),
("artists", list(&self.artists)),
("modifiers", Json::Arr(self.modifiers.iter().map(|m| Json::str(m)).collect())),
("descriptor", Json::opt_str(self.descriptor.as_deref())),
("year", num_opt(self.year)),
("unknownArtist", Json::Bool(self.unknown_artist)),
("delimiter", Json::str(self.delimiter.as_str())),
])
}
}
impl ToJson for Junk {
fn to_json_value(&self) -> Json {
obj(vec![("raw", Json::str(&self.raw)), ("kind", Json::str(self.kind.as_str()))])
}
}
impl ToJson for Flags {
fn to_json_value(&self) -> Json {
obj(vec![
("explicit", Json::Bool(self.explicit)),
("clean", Json::Bool(self.clean)),
("unknownArtist", Json::Bool(self.unknown_artist)),
("unknownTitle", Json::Bool(self.unknown_title)),
])
}
}
impl ToJson for Position {
fn to_json_value(&self) -> Json {
obj(vec![("raw", Json::str(&self.raw)), ("number", Json::Num(f64::from(self.number)))])
}
}
impl ToJson for Timestamp {
fn to_json_value(&self) -> Json {
obj(vec![("raw", Json::str(&self.raw)), ("seconds", Json::Num(self.seconds as f64))])
}
}
impl ToJson for ParsedTrack {
fn to_json_value(&self) -> Json {
obj(vec![
("input", Json::str(&self.input)),
("mode", Json::str(self.mode.as_str())),
("position", opt(&self.position)),
("timestamp", opt(&self.timestamp)),
("artists", list(&self.artists)),
("title", Json::str(&self.title)),
("fullTitle", Json::str(&self.full_title)),
("versions", list(&self.versions)),
("year", num_opt(self.year)),
("flags", self.flags.to_json_value()),
("junk", list(&self.junk)),
("warnings", Json::Arr(self.warnings.iter().map(|w| Json::str(w.as_str())).collect())),
])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RawVersion {
pub vtype: String,
pub raw: String,
pub artists: Vec<Artist>,
pub modifiers: Vec<String>,
pub descriptor: Option<String>,
pub year: Option<u32>,
pub unknown_artist: bool,
pub delimiter: VersionDelimiter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RawJunk {
pub raw: String,
pub kind: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RawTrack {
pub input: String,
pub mode: Mode,
pub position: Option<Position>,
pub timestamp: Option<Timestamp>,
pub artists: Vec<Artist>,
pub title: String,
pub full_title: String,
pub versions: Vec<RawVersion>,
pub year: Option<u32>,
pub flags: Flags,
pub junk: Vec<RawJunk>,
pub warnings: Vec<Warning>,
}
impl RawTrack {
pub(crate) fn empty(input: &str, mode: Mode) -> RawTrack {
RawTrack {
input: input.to_string(),
mode,
position: None,
timestamp: None,
artists: Vec::new(),
title: String::new(),
full_title: String::new(),
versions: Vec::new(),
year: None,
flags: Flags::default(),
junk: Vec::new(),
warnings: Vec::new(),
}
}
pub(crate) fn into_track(self) -> ParsedTrack {
ParsedTrack {
input: self.input,
mode: self.mode,
position: self.position,
timestamp: self.timestamp,
artists: self.artists,
title: self.title,
full_title: self.full_title,
versions: self
.versions
.into_iter()
.map(|v| Version {
version_type: VersionType::from_name(&v.vtype).unwrap_or(VersionType::Version),
raw: v.raw,
artists: v.artists,
modifiers: v.modifiers,
descriptor: v.descriptor,
year: v.year,
unknown_artist: v.unknown_artist,
delimiter: v.delimiter,
})
.collect(),
year: self.year,
flags: self.flags,
junk: self
.junk
.into_iter()
.map(|j| Junk {
raw: j.raw,
kind: JunkKind::from_name(&j.kind).unwrap_or(JunkKind::Other),
})
.collect(),
warnings: self.warnings,
}
}
pub(crate) fn to_json_value(&self) -> Json {
let versions = self
.versions
.iter()
.map(|v| {
obj(vec![
("type", Json::str(&v.vtype)),
("raw", Json::str(&v.raw)),
("artists", list(&v.artists)),
("modifiers", Json::Arr(v.modifiers.iter().map(|m| Json::str(m)).collect())),
("descriptor", Json::opt_str(v.descriptor.as_deref())),
("year", num_opt(v.year)),
("unknownArtist", Json::Bool(v.unknown_artist)),
("delimiter", Json::str(v.delimiter.as_str())),
])
})
.collect();
let junk = self
.junk
.iter()
.map(|j| obj(vec![("raw", Json::str(&j.raw)), ("kind", Json::str(&j.kind))]))
.collect();
obj(vec![
("input", Json::str(&self.input)),
("mode", Json::str(self.mode.as_str())),
("position", opt(&self.position)),
("timestamp", opt(&self.timestamp)),
("artists", list(&self.artists)),
("title", Json::str(&self.title)),
("fullTitle", Json::str(&self.full_title)),
("versions", Json::Arr(versions)),
("year", num_opt(self.year)),
("flags", self.flags.to_json_value()),
("junk", Json::Arr(junk)),
("warnings", Json::Arr(self.warnings.iter().map(|w| Json::str(w.as_str())).collect())),
])
}
}