Compare commits
No commits in common. "df69f4160aabb4256ae4d94a1387c31718ee50f8" and "a31e089ff42e8e8e185a0c9782a6a9532c43d806" have entirely different histories.
df69f4160a
...
a31e089ff4
19 changed files with 1085 additions and 1487 deletions
|
@ -156,8 +156,6 @@ impl From<SubtitleFormatClap> for SubtitleFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NA_STR: &str = "n/a";
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
@ -237,14 +235,8 @@ async fn run(cli: Cli) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
eprintln!("Lyrics ID: {}", lyrics.lyrics_id);
|
eprintln!("Lyrics ID: {}", lyrics.lyrics_id);
|
||||||
eprintln!(
|
eprintln!("Language: {}", lyrics.lyrics_language);
|
||||||
"Language: {}",
|
eprintln!("Copyright: {}", lyrics.lyrics_copyright);
|
||||||
lyrics.lyrics_language.as_deref().unwrap_or(NA_STR)
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"Copyright: {}",
|
|
||||||
lyrics.lyrics_copyright.as_deref().unwrap_or(NA_STR)
|
|
||||||
);
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
|
|
||||||
println!("{}", lyrics.lyrics_body);
|
println!("{}", lyrics.lyrics_body);
|
||||||
|
@ -306,15 +298,9 @@ async fn run(cli: Cli) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
eprintln!("Subtitle ID: {}", subtitles.subtitle_id);
|
eprintln!("Subtitle ID: {}", subtitles.subtitle_id);
|
||||||
eprintln!(
|
eprintln!("Language: {}", subtitles.subtitle_language);
|
||||||
"Language: {}",
|
|
||||||
subtitles.subtitle_language.as_deref().unwrap_or(NA_STR)
|
|
||||||
);
|
|
||||||
eprintln!("Length: {}", subtitles.subtitle_length);
|
eprintln!("Length: {}", subtitles.subtitle_length);
|
||||||
eprintln!(
|
eprintln!("Copyright: {}", subtitles.lyrics_copyright);
|
||||||
"Copyright: {}",
|
|
||||||
subtitles.lyrics_copyright.as_deref().unwrap_or(NA_STR)
|
|
||||||
);
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
|
|
||||||
println!("{}", subtitles.subtitle_body);
|
println!("{}", subtitles.subtitle_body);
|
||||||
|
|
378
src/api_model.rs
378
src/api_model.rs
|
@ -1,5 +1,3 @@
|
||||||
use std::{marker::PhantomData, str::FromStr};
|
|
||||||
|
|
||||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
@ -229,269 +227,7 @@ where
|
||||||
deserializer.deserialize_any(BoolFromIntVisitor)
|
deserializer.deserialize_any(BoolFromIntVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The Musixmatch API returns zero as a default value for numeric IDs.
|
|
||||||
* These values should be deserialized as [`None`].
|
|
||||||
*/
|
|
||||||
pub fn null_if_zero<'de, D, N>(deserializer: D) -> Result<Option<N>, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
N: TryFrom<u64>,
|
|
||||||
{
|
|
||||||
struct NullIfZeroVisitor<N> {
|
|
||||||
n: PhantomData<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, N> Visitor<'de> for NullIfZeroVisitor<N>
|
|
||||||
where
|
|
||||||
N: TryFrom<u64>,
|
|
||||||
{
|
|
||||||
type Value = Option<N>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("unsigned int or None")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
if v < 1 {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(v.try_into().ok())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
self.visit_u64(v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
self.visit_u64(v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
self.visit_u64(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(NullIfZeroVisitor { n: PhantomData })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Musixmatch API returns an empty string as a default value for string fields.
|
|
||||||
/// These values should be deserialized as [`None`].
|
|
||||||
pub fn null_if_empty<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct NullIfEmptyVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for NullIfEmptyVisitor {
|
|
||||||
type Value = Option<String>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("string or None")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
if v.is_empty() {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Some(v.to_owned()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(NullIfEmptyVisitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize a numeric string into an integer.
|
|
||||||
/// Return None if the string is empty/null or if there was a parse error.
|
|
||||||
pub fn parse_int<'de, D, N>(deserializer: D) -> Result<Option<N>, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
N: FromStr + TryFrom<u64>,
|
|
||||||
{
|
|
||||||
struct ParseIntVisitor<N> {
|
|
||||||
n: PhantomData<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, N> Visitor<'de> for ParseIntVisitor<N>
|
|
||||||
where
|
|
||||||
N: FromStr + TryFrom<u64>,
|
|
||||||
{
|
|
||||||
type Value = Option<N>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("numeric string or None")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(v.try_into().ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
self.visit_u64(v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
self.visit_u64(v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
self.visit_u64(v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
if v.is_empty() {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(v.parse().ok())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(ParseIntVisitor { n: PhantomData })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod optional_date {
|
pub mod optional_date {
|
||||||
use super::*;
|
|
||||||
use serde::ser::Error as _;
|
|
||||||
use serde::Serializer;
|
|
||||||
use time::{macros::format_description, Date};
|
|
||||||
|
|
||||||
const DATE_FORMAT: &[time::format_description::FormatItem] =
|
|
||||||
format_description!("[year]-[month]-[day]");
|
|
||||||
|
|
||||||
pub fn serialize<S: Serializer>(
|
|
||||||
value: &Option<Date>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error> {
|
|
||||||
match value {
|
|
||||||
Some(date) => date
|
|
||||||
.format(&DATE_FORMAT)
|
|
||||||
.map_err(S::Error::custom)?
|
|
||||||
.serialize(serializer),
|
|
||||||
None => serializer.serialize_none(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<Option<Date>, D::Error> {
|
|
||||||
struct OptionalDateVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for OptionalDateVisitor {
|
|
||||||
type Value = Option<Date>;
|
|
||||||
|
|
||||||
fn expecting(
|
|
||||||
&self,
|
|
||||||
formatter: &mut serde::__private::fmt::Formatter,
|
|
||||||
) -> serde::__private::fmt::Result {
|
|
||||||
formatter.write_str("date or empty string")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
if v.is_empty() || v == "0000-00-00" {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Date::parse(v, &DATE_FORMAT).ok())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(OptionalDateVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod optional_datetime {
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use serde::Serializer;
|
use serde::Serializer;
|
||||||
use time::format_description::well_known::Rfc3339;
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
@ -521,20 +257,6 @@ pub mod optional_datetime {
|
||||||
formatter.write_str("timestamp or empty string")
|
formatter.write_str("timestamp or empty string")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
where
|
where
|
||||||
E: serde::de::Error,
|
E: serde::de::Error,
|
||||||
|
@ -549,14 +271,12 @@ pub mod optional_datetime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deserializer.deserialize_any(OptionalDateVisitor)
|
deserializer.deserialize_str(OptionalDateVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use time::Date;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::models::subtitle::SubtitleBody;
|
use crate::models::subtitle::SubtitleBody;
|
||||||
|
|
||||||
|
@ -618,113 +338,17 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_null_if_zero() {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct S {
|
|
||||||
#[serde(default, deserialize_with = "null_if_zero")]
|
|
||||||
n: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let json = r#"{"n": 0}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, None);
|
|
||||||
|
|
||||||
let json = r#"{"n": 1}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, Some(1));
|
|
||||||
|
|
||||||
let json = r#"{"n": null}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, None);
|
|
||||||
|
|
||||||
let json = r#"{}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_null_if_empty() {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct S {
|
|
||||||
#[serde(default, deserialize_with = "null_if_empty")]
|
|
||||||
s: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let json = r#"{"s": ""}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().s, None);
|
|
||||||
|
|
||||||
let json = r#"{"s": "a"}"#;
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::from_str::<S>(json).unwrap().s,
|
|
||||||
Some("a".to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let json = r#"{"n": null}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().s, None);
|
|
||||||
|
|
||||||
let json = r#"{}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().s, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_parse_int() {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct S {
|
|
||||||
#[serde(default, deserialize_with = "parse_int")]
|
|
||||||
n: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let json = r#"{"n": 0}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, Some(0));
|
|
||||||
|
|
||||||
let json = r#"{"n": "0"}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, Some(0));
|
|
||||||
|
|
||||||
let json = r#"{"n": null}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, None);
|
|
||||||
|
|
||||||
let json = r#"{}"#;
|
|
||||||
assert_eq!(serde_json::from_str::<S>(json).unwrap().n, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deserialize_optional_date() {
|
fn deserialize_optional_date() {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct S {
|
struct S {
|
||||||
#[serde(with = "optional_date")]
|
#[serde(with = "optional_date")]
|
||||||
date: Option<Date>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let json_null_string = r#"{"date": null}"#;
|
|
||||||
let json_empty_string = r#"{"date": ""}"#;
|
|
||||||
let json_zero_date = r#"{"date": "0000-00-00"}"#;
|
|
||||||
let json_date = r#"{"date": "2022-08-27"}"#;
|
|
||||||
|
|
||||||
let res = serde_json::from_str::<S>(json_null_string).unwrap();
|
|
||||||
assert!(res.date.is_none());
|
|
||||||
|
|
||||||
let res = serde_json::from_str::<S>(json_empty_string).unwrap();
|
|
||||||
assert!(res.date.is_none());
|
|
||||||
|
|
||||||
let res = serde_json::from_str::<S>(json_zero_date).unwrap();
|
|
||||||
assert!(res.date.is_none());
|
|
||||||
|
|
||||||
let res = serde_json::from_str::<S>(json_date).unwrap();
|
|
||||||
assert!(res.date.is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_optional_datetime() {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct S {
|
|
||||||
#[serde(with = "optional_datetime")]
|
|
||||||
date: Option<OffsetDateTime>,
|
date: Option<OffsetDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let json_null_string = r#"{"date": null}"#;
|
|
||||||
let json_empty_string = r#"{"date": ""}"#;
|
let json_empty_string = r#"{"date": ""}"#;
|
||||||
let json_date = r#"{"date": "2022-08-27T23:47:20Z"}"#;
|
let json_date = r#"{"date": "2022-08-27T23:47:20Z"}"#;
|
||||||
|
|
||||||
let res = serde_json::from_str::<S>(json_null_string).unwrap();
|
|
||||||
assert!(res.date.is_none());
|
|
||||||
|
|
||||||
let res = serde_json::from_str::<S>(json_empty_string).unwrap();
|
let res = serde_json::from_str::<S>(json_empty_string).unwrap();
|
||||||
assert!(res.date.is_none());
|
assert!(res.date.is_none());
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,7 @@ impl Musixmatch {
|
||||||
|
|
||||||
/// Create a new query builder for searching tracks in the Musixmatch database.
|
/// Create a new query builder for searching tracks in the Musixmatch database.
|
||||||
///
|
///
|
||||||
/// **Note:** The search results are unordered the by default. You probably want
|
/// **Note:** The search results are unsorted the by default. You probably want
|
||||||
/// to sort by popularity (`.s_track_rating(SortOrder::Desc)`) to get relevant results.
|
/// to sort by popularity (`.s_track_rating(SortOrder::Desc)`) to get relevant results.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::{Date, OffsetDateTime};
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::Genres;
|
use super::Genres;
|
||||||
|
|
||||||
|
@ -19,8 +19,10 @@ pub struct Album {
|
||||||
/// Unique Musixmatch Album ID
|
/// Unique Musixmatch Album ID
|
||||||
pub album_id: u64,
|
pub album_id: u64,
|
||||||
/// Musicbrainz album ID
|
/// Musicbrainz album ID
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
///
|
||||||
pub album_mbid: Option<String>,
|
/// **Note:** most albums dont have this entry set
|
||||||
|
#[serde(default)]
|
||||||
|
pub album_mbid: String,
|
||||||
/// Album name
|
/// Album name
|
||||||
pub album_name: String,
|
pub album_name: String,
|
||||||
/// Popularity of the album from 0 to 100
|
/// Popularity of the album from 0 to 100
|
||||||
|
@ -28,14 +30,14 @@ pub struct Album {
|
||||||
pub album_rating: u8,
|
pub album_rating: u8,
|
||||||
/// Number of tracks on the album
|
/// Number of tracks on the album
|
||||||
pub album_track_count: u16,
|
pub album_track_count: u16,
|
||||||
/// Album release date
|
/// Album release date (e.g. "2009-07-07")
|
||||||
#[serde(default, with = "crate::api_model::optional_date")]
|
|
||||||
pub album_release_date: Option<Date>,
|
|
||||||
/// Album type
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub album_release_type: AlbumType,
|
pub album_release_date: String,
|
||||||
|
/// Album type (Single / EP / Album)
|
||||||
|
pub album_release_type: String,
|
||||||
|
|
||||||
/// Musixmatch artist ID
|
/// Musixmatch artist ID
|
||||||
|
#[serde(default)]
|
||||||
pub artist_id: u64,
|
pub artist_id: u64,
|
||||||
/// Artist name
|
/// Artist name
|
||||||
pub artist_name: String,
|
pub artist_name: String,
|
||||||
|
@ -46,15 +48,19 @@ pub struct Album {
|
||||||
/// Secondary genres / Subgenres
|
/// Secondary genres / Subgenres
|
||||||
///
|
///
|
||||||
/// Example: primary_genres: Pop, secondary_genres: K-Pop / Mandopop
|
/// Example: primary_genres: Pop, secondary_genres: K-Pop / Mandopop
|
||||||
|
///
|
||||||
|
/// Note that this schema is not applied to all tracks on Musixmatch.
|
||||||
|
/// There are for example many K-Pop tracks with both Pop and K-Pop
|
||||||
|
/// tagged as primary genre and this field empty.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub secondary_genres: Genres,
|
pub secondary_genres: Genres,
|
||||||
|
|
||||||
/// Album copyright text
|
/// Album copyright text
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_copyright: Option<String>,
|
pub album_copyright: String,
|
||||||
/// Album label / recording company
|
/// Album label / recording company
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_label: Option<String>,
|
pub album_label: String,
|
||||||
/// Human-readable URL-safe Album ID
|
/// Human-readable URL-safe Album ID
|
||||||
///
|
///
|
||||||
/// Example: `LMFAO/Party-Rock-5`
|
/// Example: `LMFAO/Party-Rock-5`
|
||||||
|
@ -65,31 +71,17 @@ pub struct Album {
|
||||||
pub updated_time: OffsetDateTime,
|
pub updated_time: OffsetDateTime,
|
||||||
|
|
||||||
/// Album cover URL (100x100px)
|
/// Album cover URL (100x100px)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_100x100: Option<String>,
|
pub album_coverart_100x100: String,
|
||||||
/// Album cover URL (350x350px)
|
/// Album cover URL (350x350px)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_350x350: Option<String>,
|
pub album_coverart_350x350: String,
|
||||||
/// Album cover URL (500x500px)
|
/// Album cover URL (500x500px)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_500x500: Option<String>,
|
pub album_coverart_500x500: String,
|
||||||
/// Album cover URL (800x800px)
|
/// Album cover URL (800x800px)
|
||||||
///
|
///
|
||||||
/// **Note:** not present on a lot of albums
|
/// **Note:** not present on a lot of albums
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_800x800: Option<String>,
|
pub album_coverart_800x800: String,
|
||||||
}
|
|
||||||
|
|
||||||
/// Album type
|
|
||||||
///
|
|
||||||
/// Source: <https://developer.musixmatch.com/documentation/music-meta-data>
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum AlbumType {
|
|
||||||
#[default]
|
|
||||||
Album,
|
|
||||||
Single,
|
|
||||||
Compilation,
|
|
||||||
Remix,
|
|
||||||
Live,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::{Date, OffsetDateTime};
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::Genres;
|
use super::Genres;
|
||||||
|
|
||||||
|
@ -19,8 +19,10 @@ pub struct Artist {
|
||||||
/// Musixmatch Artist ID
|
/// Musixmatch Artist ID
|
||||||
pub artist_id: u64,
|
pub artist_id: u64,
|
||||||
/// Musicbrainz Artist ID
|
/// Musicbrainz Artist ID
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
///
|
||||||
pub artist_mbid: Option<String>,
|
/// **Note:** most tracks dont have this entry set
|
||||||
|
#[serde(default)]
|
||||||
|
pub artist_mbid: String,
|
||||||
|
|
||||||
/// Artist name
|
/// Artist name
|
||||||
pub artist_name: String,
|
pub artist_name: String,
|
||||||
|
@ -28,8 +30,8 @@ pub struct Artist {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub artist_name_translation_list: Vec<ArtistNameTranslation>,
|
pub artist_name_translation_list: Vec<ArtistNameTranslation>,
|
||||||
/// Artist origin as a 2-letter country code (e.g. "US")
|
/// Artist origin as a 2-letter country code (e.g. "US")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub artist_country: Option<String>,
|
pub artist_country: String,
|
||||||
/// Alternative names for the artist (e.g. in different languages)
|
/// Alternative names for the artist (e.g. in different languages)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub artist_alias_list: Vec<ArtistAlias>,
|
pub artist_alias_list: Vec<ArtistAlias>,
|
||||||
|
@ -51,20 +53,20 @@ pub struct Artist {
|
||||||
pub secondary_genres: Genres,
|
pub secondary_genres: Genres,
|
||||||
|
|
||||||
/// URL of the artist's Twitter profile
|
/// URL of the artist's Twitter profile
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub artist_twitter_url: Option<String>,
|
pub artist_twitter_url: String,
|
||||||
/// URL of the artist's website
|
/// URL of the artist's website
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub artist_website_url: Option<String>,
|
pub artist_website_url: String,
|
||||||
/// URL of the artist's Instagram profile
|
/// URL of the artist's Instagram profile
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub artist_instagram_url: Option<String>,
|
pub artist_instagram_url: String,
|
||||||
/// URL of the artist's TikTok profile
|
/// URL of the artist's TikTok profile
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub artist_tiktok_url: Option<String>,
|
pub artist_tiktok_url: String,
|
||||||
/// URL of the artist's Facebook profile
|
/// URL of the artist's Facebook profile
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub artist_facebook_url: Option<String>,
|
pub artist_facebook_url: String,
|
||||||
|
|
||||||
/// URL-safe human-readable artist ID
|
/// URL-safe human-readable artist ID
|
||||||
///
|
///
|
||||||
|
@ -73,18 +75,22 @@ pub struct Artist {
|
||||||
/// Date and time when the artist was last updated
|
/// Date and time when the artist was last updated
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
pub updated_time: OffsetDateTime,
|
pub updated_time: OffsetDateTime,
|
||||||
/// Start year of the artist's presence
|
/// Year of the start of the artist's presence
|
||||||
#[serde(default, deserialize_with = "crate::api_model::parse_int")]
|
#[serde(default)]
|
||||||
pub begin_date_year: Option<u16>,
|
pub begin_date_year: String,
|
||||||
/// Start date of the artist's presence
|
/// Start date of the artist's presence in YYYY-MM-DD format
|
||||||
#[serde(default, with = "crate::api_model::optional_date")]
|
///
|
||||||
pub begin_date: Option<Date>,
|
/// **Info:** the default value is `"0000-00-00"`
|
||||||
/// End year of the artist's presence
|
#[serde(default)]
|
||||||
#[serde(default, deserialize_with = "crate::api_model::parse_int")]
|
pub begin_date: String,
|
||||||
pub end_date_year: Option<u16>,
|
/// Year of the end of the artist's presence
|
||||||
/// End date of the artist's presence
|
#[serde(default)]
|
||||||
#[serde(default, with = "crate::api_model::optional_date")]
|
pub end_date_year: String,
|
||||||
pub end_date: Option<Date>,
|
/// End date of the artist's presence in YYYY-MM-DD format
|
||||||
|
///
|
||||||
|
/// **Info:** the default value is `"0000-00-00"`
|
||||||
|
#[serde(default)]
|
||||||
|
pub end_date: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alternative artist name (e.g. different languages)
|
/// Alternative artist name (e.g. different languages)
|
||||||
|
|
|
@ -21,11 +21,11 @@ pub struct Lyrics {
|
||||||
/// Lyrics text
|
/// Lyrics text
|
||||||
pub lyrics_body: String,
|
pub lyrics_body: String,
|
||||||
/// Language code (e.g. "en")
|
/// Language code (e.g. "en")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub lyrics_language: Option<String>,
|
pub lyrics_language: String,
|
||||||
/// Language name (e.g. "English")
|
/// Language name (e.g. "English")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub lyrics_language_description: Option<String>,
|
pub lyrics_language_description: String,
|
||||||
/// Copyright text of the lyrics
|
/// Copyright text of the lyrics
|
||||||
///
|
///
|
||||||
/// Ends with a newline.
|
/// Ends with a newline.
|
||||||
|
@ -35,8 +35,8 @@ pub struct Lyrics {
|
||||||
/// Writer(s): David Hodges
|
/// Writer(s): David Hodges
|
||||||
/// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p.
|
/// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p.
|
||||||
/// ```
|
/// ```
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub lyrics_copyright: Option<String>,
|
pub lyrics_copyright: String,
|
||||||
/// Date and time when the lyrics were last updated
|
/// Date and time when the lyrics were last updated
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
pub updated_time: OffsetDateTime,
|
pub updated_time: OffsetDateTime,
|
||||||
|
|
|
@ -39,7 +39,6 @@ pub use artist::ArtistNameTranslationInner;
|
||||||
|
|
||||||
pub(crate) mod album;
|
pub(crate) mod album;
|
||||||
pub use album::Album;
|
pub use album::Album;
|
||||||
pub use album::AlbumType;
|
|
||||||
|
|
||||||
pub(crate) mod snippet;
|
pub(crate) mod snippet;
|
||||||
pub use snippet::Snippet;
|
pub use snippet::Snippet;
|
||||||
|
|
|
@ -18,8 +18,8 @@ pub struct Snippet {
|
||||||
/// Unique Musixmatch Snippet ID
|
/// Unique Musixmatch Snippet ID
|
||||||
pub snippet_id: u64,
|
pub snippet_id: u64,
|
||||||
/// Snippet language code (e.g. "en")
|
/// Snippet language code (e.g. "en")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub snippet_language: Option<String>,
|
pub snippet_language: String,
|
||||||
/// True if the track is instrumental
|
/// True if the track is instrumental
|
||||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||||
pub instrumental: bool,
|
pub instrumental: bool,
|
||||||
|
|
|
@ -196,11 +196,11 @@ pub struct Subtitle {
|
||||||
/// Subtitle / synchronized lyrics in the requested format
|
/// Subtitle / synchronized lyrics in the requested format
|
||||||
pub subtitle_body: String,
|
pub subtitle_body: String,
|
||||||
/// Language code (e.g. "en")
|
/// Language code (e.g. "en")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub subtitle_language: Option<String>,
|
pub subtitle_language: String,
|
||||||
/// Language name (e.g. "English")
|
/// Language name (e.g. "English")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub subtitle_language_description: Option<String>,
|
pub subtitle_language_description: String,
|
||||||
/// Copyright text of the lyrics
|
/// Copyright text of the lyrics
|
||||||
///
|
///
|
||||||
/// Ends with a newline.
|
/// Ends with a newline.
|
||||||
|
@ -210,8 +210,8 @@ pub struct Subtitle {
|
||||||
/// Writer(s): David Hodges
|
/// Writer(s): David Hodges
|
||||||
/// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p.
|
/// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p.
|
||||||
/// ```
|
/// ```
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub lyrics_copyright: Option<String>,
|
pub lyrics_copyright: String,
|
||||||
/// Duration of the subtitle in seconds
|
/// Duration of the subtitle in seconds
|
||||||
pub subtitle_length: u32,
|
pub subtitle_length: u32,
|
||||||
/// Date and time when the subtitle was last updated
|
/// Date and time when the subtitle was last updated
|
||||||
|
@ -239,7 +239,7 @@ pub struct SubtitleLines {
|
||||||
/// List of subtitle lines
|
/// List of subtitle lines
|
||||||
pub lines: Vec<SubtitleLine>,
|
pub lines: Vec<SubtitleLine>,
|
||||||
/// Language code (e.g. "en")
|
/// Language code (e.g. "en")
|
||||||
pub lang: Option<String>,
|
pub lang: String,
|
||||||
/// Duration of the subtitle in seconds
|
/// Duration of the subtitle in seconds
|
||||||
pub length: u32,
|
pub length: u32,
|
||||||
}
|
}
|
||||||
|
@ -268,10 +268,10 @@ impl SubtitleLines {
|
||||||
pub fn to_ttml(&self) -> String {
|
pub fn to_ttml(&self) -> String {
|
||||||
let mut ttml = format!(
|
let mut ttml = format!(
|
||||||
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:tts="http://www.w3.org/ns/ttml" xml:lang="{0}">
|
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:tts="http://www.w3.org/ns/ttml" xml:lang="{}">
|
||||||
<body>
|
<body>
|
||||||
<div xml:lang="{0}">"#,
|
<div xml:lang="{}">"#,
|
||||||
self.lang.as_deref().unwrap_or_default(),
|
self.lang, self.lang
|
||||||
);
|
);
|
||||||
|
|
||||||
for i in 0..self.lines.len() {
|
for i in 0..self.lines.len() {
|
||||||
|
|
|
@ -20,26 +20,28 @@ pub struct Track {
|
||||||
/// Unique Musixmatch Track ID
|
/// Unique Musixmatch Track ID
|
||||||
pub track_id: u64,
|
pub track_id: u64,
|
||||||
/// Track Musicbrainz ID
|
/// Track Musicbrainz ID
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
///
|
||||||
pub track_mbid: Option<String>,
|
/// **Note:** most tracks dont have this entry set
|
||||||
|
#[serde(default)]
|
||||||
|
pub track_mbid: String,
|
||||||
/// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code)
|
/// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub track_isrc: Option<String>,
|
pub track_isrc: String,
|
||||||
/// [ISRCs](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) of equivalent tracks (e.g. on different albums)
|
/// [ISRCs](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) of equivalent tracks (e.g. on different albums)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub commontrack_isrcs: Vec<Vec<String>>,
|
pub commontrack_isrcs: Vec<Vec<String>>,
|
||||||
/// Track ID on Spotify
|
/// Track ID on Spotify
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub track_spotify_id: Option<String>,
|
pub track_spotify_id: String,
|
||||||
/// Spotify IDs of equivalent tracks (e.g. on different albums)
|
/// Spotify IDs of equivalent tracks (e.g. on different albums)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub commontrack_spotify_ids: Vec<String>,
|
pub commontrack_spotify_ids: Vec<String>,
|
||||||
/// Track ID on Soundcloud
|
/// Track ID on Soundcloud
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_zero")]
|
#[serde(default)]
|
||||||
pub track_soundcloud_id: Option<u64>,
|
pub track_soundcloud_id: u64,
|
||||||
/// Track ID on XBox Music
|
/// Track ID on XBox Music
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub track_xboxmusic_id: Option<String>,
|
pub track_xboxmusic_id: String,
|
||||||
|
|
||||||
/// Title of the track
|
/// Title of the track
|
||||||
pub track_name: String,
|
pub track_name: String,
|
||||||
|
@ -76,39 +78,44 @@ pub struct Track {
|
||||||
pub num_favourite: u32,
|
pub num_favourite: u32,
|
||||||
|
|
||||||
/// Musixmatch lyrics ID
|
/// Musixmatch lyrics ID
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_zero")]
|
#[serde(default)]
|
||||||
pub lyrics_id: Option<u64>,
|
pub lyrics_id: u64,
|
||||||
/// Musixmatch subtitle ID
|
/// Musixmatch subtitle ID
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_zero")]
|
#[serde(default)]
|
||||||
pub subtitle_id: Option<u64>,
|
pub subtitle_id: u64,
|
||||||
|
|
||||||
/// Musixmatch album ID
|
/// Musixmatch album ID
|
||||||
|
#[serde(default)]
|
||||||
pub album_id: u64,
|
pub album_id: u64,
|
||||||
/// Album name
|
/// Album name
|
||||||
|
#[serde(default)]
|
||||||
pub album_name: String,
|
pub album_name: String,
|
||||||
|
|
||||||
/// Musixmatch artist ID
|
/// Musixmatch artist ID
|
||||||
pub artist_id: u64,
|
pub artist_id: u64,
|
||||||
/// Artist name
|
|
||||||
pub artist_name: String,
|
|
||||||
/// Musicbrainz artist ID
|
/// Musicbrainz artist ID
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
///
|
||||||
pub artist_mbid: Option<String>,
|
/// **Note:** most tracks dont have this entry set
|
||||||
|
#[serde(default)]
|
||||||
|
pub artist_mbid: String,
|
||||||
|
/// Artist name
|
||||||
|
#[serde(default)]
|
||||||
|
pub artist_name: String,
|
||||||
|
|
||||||
/// Album cover URL (100x100px)
|
/// Album cover URL (100x100px)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_100x100: Option<String>,
|
pub album_coverart_100x100: String,
|
||||||
/// Album cover URL (350x350px)
|
/// Album cover URL (350x350px)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_350x350: Option<String>,
|
pub album_coverart_350x350: String,
|
||||||
/// Album cover URL (500x500px)
|
/// Album cover URL (500x500px)
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_500x500: Option<String>,
|
pub album_coverart_500x500: String,
|
||||||
/// Album cover URL (800x800px)
|
/// Album cover URL (800x800px)
|
||||||
///
|
///
|
||||||
/// **Note:** not present on a lot of albums
|
/// **Note:** not present on a lot of albums
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub album_coverart_800x800: Option<String>,
|
pub album_coverart_800x800: String,
|
||||||
|
|
||||||
/// Human-readable Musixmatch ID
|
/// Human-readable Musixmatch ID
|
||||||
///
|
///
|
||||||
|
@ -118,7 +125,7 @@ pub struct Track {
|
||||||
pub commontrack_vanity_id: String,
|
pub commontrack_vanity_id: String,
|
||||||
|
|
||||||
/// Track release date
|
/// Track release date
|
||||||
#[serde(default, with = "crate::api_model::optional_datetime")]
|
#[serde(default, with = "crate::api_model::optional_date")]
|
||||||
pub first_release_date: Option<OffsetDateTime>,
|
pub first_release_date: Option<OffsetDateTime>,
|
||||||
/// Date and time when the track was last updated
|
/// Date and time when the track was last updated
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
|
|
|
@ -19,9 +19,9 @@ pub(crate) struct TranslationBody {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Translation {
|
pub struct Translation {
|
||||||
/// source language code (e.g. "en")
|
/// 2-character source language code (e.g. "en")
|
||||||
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
|
#[serde(default)]
|
||||||
pub language_from: Option<String>,
|
pub language_from: String,
|
||||||
/// Translated line
|
/// Translated line
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
@ -102,7 +102,7 @@ impl TranslationMap {
|
||||||
time: line.time,
|
time: line.time,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
lang: Some(self.lang.to_owned()),
|
lang: self.lang.to_owned(),
|
||||||
length: subtitles.length,
|
length: subtitles.length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
tests/_fixtures.rs
Normal file
21
tests/_fixtures.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use musixmatch_inofficial::{storage::FileStorage, Musixmatch};
|
||||||
|
|
||||||
|
#[ctor::ctor]
|
||||||
|
fn init() {
|
||||||
|
let _ = dotenvy::dotenv();
|
||||||
|
env_logger::init();
|
||||||
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(new_mxm().login())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_mxm() -> Musixmatch {
|
||||||
|
Musixmatch::new(
|
||||||
|
&std::env::var("MUSIXMATCH_EMAIL").unwrap(),
|
||||||
|
&std::env::var("MUSIXMATCH_PASSWORD").unwrap(),
|
||||||
|
Some(Box::new(FileStorage::default())),
|
||||||
|
)
|
||||||
|
}
|
104
tests/album_test.rs
Normal file
104
tests/album_test.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
mod _fixtures;
|
||||||
|
use _fixtures::*;
|
||||||
|
|
||||||
|
use musixmatch_inofficial::{
|
||||||
|
models::{AlbumId, ArtistId},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use rstest::rstest;
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::id(AlbumId::AlbumId(14248253))]
|
||||||
|
#[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn by_id(#[case] album_id: AlbumId<'_>) {
|
||||||
|
let album = new_mxm().album(album_id).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(album.album_id, 14248253);
|
||||||
|
assert_eq!(album.album_mbid, "6c3cf9d8-88a8-43ed-850b-55813f01e451");
|
||||||
|
assert_eq!(album.album_name, "Gangnam Style (강남스타일)");
|
||||||
|
assert!(album.album_rating > 25);
|
||||||
|
assert_eq!(album.album_track_count, 1);
|
||||||
|
assert_eq!(album.album_release_date, "2012-01-01");
|
||||||
|
assert_eq!(album.album_release_type, "Single");
|
||||||
|
assert_eq!(album.artist_id, 410698);
|
||||||
|
assert_eq!(album.artist_name, "PSY");
|
||||||
|
|
||||||
|
let first_pri_genre = &album.primary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_pri_genre.music_genre_id, 14);
|
||||||
|
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
||||||
|
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
||||||
|
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||||
|
|
||||||
|
let first_sec_genre = &album.secondary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_sec_genre.music_genre_id, 17);
|
||||||
|
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
||||||
|
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
||||||
|
assert_eq!(
|
||||||
|
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
||||||
|
"Dance"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
album.album_copyright,
|
||||||
|
"© 2012 Schoolboy/Universal Republic Records, a division of UMG Recordings, Inc."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
album.album_label,
|
||||||
|
"Silent Records/Universal Republic Records"
|
||||||
|
);
|
||||||
|
assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single");
|
||||||
|
assert!(album.updated_time > datetime!(2022-6-3 0:00 UTC));
|
||||||
|
assert_eq!(
|
||||||
|
album.album_coverart_100x100,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
album.album_coverart_350x350,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_350_350.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
album.album_coverart_500x500,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_500_500.jpg"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn by_id_missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.album(AlbumId::AlbumId(999999999999))
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn artist_albums() {
|
||||||
|
let albums = new_mxm()
|
||||||
|
.artist_albums(ArtistId::ArtistId(1039), None, 10, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(albums.len(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn artist_albums_missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn charts() {
|
||||||
|
let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(albums.len(), 10);
|
||||||
|
}
|
116
tests/artist_test.rs
Normal file
116
tests/artist_test.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
mod _fixtures;
|
||||||
|
use _fixtures::*;
|
||||||
|
|
||||||
|
use musixmatch_inofficial::{models::ArtistId, Error};
|
||||||
|
use rstest::rstest;
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::id(ArtistId::ArtistId(410698))]
|
||||||
|
#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn by_id(#[case] artist_id: ArtistId<'_>) {
|
||||||
|
let artist = new_mxm().artist(artist_id).await.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&artist);
|
||||||
|
|
||||||
|
assert_eq!(artist.artist_id, 410698);
|
||||||
|
assert_eq!(artist.artist_mbid, "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a");
|
||||||
|
assert_eq!(artist.artist_name, "PSY");
|
||||||
|
assert!(
|
||||||
|
artist.artist_name_translation_list.iter().any(|tl| {
|
||||||
|
tl.artist_name_translation.language == "KO"
|
||||||
|
&& tl.artist_name_translation.translation == "싸이"
|
||||||
|
}),
|
||||||
|
"missing Korean translation in: {:?}",
|
||||||
|
artist.artist_name_translation_list
|
||||||
|
);
|
||||||
|
assert_eq!(artist.artist_country, "KR");
|
||||||
|
assert!(artist.artist_rating > 50);
|
||||||
|
let first_genre = &artist.primary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_genre.music_genre_id, 14);
|
||||||
|
assert_eq!(first_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_genre.music_genre_name, "Pop");
|
||||||
|
assert_eq!(first_genre.music_genre_name_extended, "Pop");
|
||||||
|
assert_eq!(first_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||||
|
assert_eq!(artist.artist_twitter_url, "https://twitter.com/psy_oppa");
|
||||||
|
assert_eq!(
|
||||||
|
artist.artist_facebook_url,
|
||||||
|
"https://www.facebook.com/officialpsy"
|
||||||
|
);
|
||||||
|
assert_eq!(artist.artist_vanity_id, "410698");
|
||||||
|
assert!(artist.updated_time > datetime!(2016-6-30 0:00 UTC));
|
||||||
|
assert_eq!(artist.begin_date_year, "1977");
|
||||||
|
assert_eq!(artist.begin_date, "1977-12-31");
|
||||||
|
assert_eq!(artist.end_date_year, "");
|
||||||
|
assert_eq!(artist.end_date, "0000-00-00");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn by_id_missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.artist(ArtistId::ArtistId(999999999999))
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn related() {
|
||||||
|
let artists = new_mxm()
|
||||||
|
.artist_related(ArtistId::ArtistId(26485840), 10, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artists.len(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn related_missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.artist_related(ArtistId::ArtistId(999999999999), 10, 1)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn search() {
|
||||||
|
let artists = new_mxm().artist_search("psy", 5, 1).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artists.len(), 5);
|
||||||
|
|
||||||
|
let artist = &artists[0];
|
||||||
|
assert_eq!(artist.artist_id, 410698);
|
||||||
|
assert_eq!(artist.artist_name, "PSY");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn search_empty() {
|
||||||
|
let artists = new_mxm()
|
||||||
|
.artist_search(
|
||||||
|
"Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz",
|
||||||
|
5,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artists.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn charts() {
|
||||||
|
let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artists.len(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn charts_no_country() {
|
||||||
|
let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artists.len(), 10);
|
||||||
|
}
|
145
tests/lyrics_test.rs
Normal file
145
tests/lyrics_test.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
mod _fixtures;
|
||||||
|
use _fixtures::*;
|
||||||
|
|
||||||
|
use std::{fs::File, io::BufWriter, path::Path};
|
||||||
|
|
||||||
|
use futures::stream::{self, StreamExt};
|
||||||
|
use musixmatch_inofficial::{models::TrackId, Error};
|
||||||
|
use rstest::rstest;
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_match() {
|
||||||
|
let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&lyrics);
|
||||||
|
|
||||||
|
assert_eq!(lyrics.lyrics_id, 25947036);
|
||||||
|
assert!(!lyrics.instrumental);
|
||||||
|
assert!(!lyrics.explicit);
|
||||||
|
assert!(lyrics
|
||||||
|
.lyrics_body
|
||||||
|
.starts_with("Eyes, in the sky, gazing far into the night\n"));
|
||||||
|
assert_eq!(lyrics.lyrics_language, "en");
|
||||||
|
assert_eq!(lyrics.lyrics_language_description, "English");
|
||||||
|
assert!(
|
||||||
|
lyrics.lyrics_copyright.contains("Kim Jeffeson"),
|
||||||
|
"copyright: {}",
|
||||||
|
lyrics.lyrics_copyright,
|
||||||
|
);
|
||||||
|
assert!(lyrics.updated_time > datetime!(2021-6-3 0:00 UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::trackid(TrackId::TrackId(205688271))]
|
||||||
|
#[case::commontrack(TrackId::Commontrack(118480583))]
|
||||||
|
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
||||||
|
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
||||||
|
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||||
|
let lyrics = new_mxm().track_lyrics(track_id).await.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&lyrics);
|
||||||
|
|
||||||
|
assert_eq!(lyrics.lyrics_id, 30126001);
|
||||||
|
assert_eq!(lyrics.lyrics_language, "ko");
|
||||||
|
assert_eq!(lyrics.lyrics_language_description, "Korean");
|
||||||
|
assert!(
|
||||||
|
lyrics.lyrics_copyright.contains("Kenneth Scott Chesak"),
|
||||||
|
"copyright: {}",
|
||||||
|
lyrics.lyrics_copyright,
|
||||||
|
);
|
||||||
|
assert!(lyrics.updated_time > datetime!(2022-8-27 0:00 UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This track has no lyrics
|
||||||
|
#[tokio::test]
|
||||||
|
async fn instrumental() {
|
||||||
|
let lyrics = new_mxm()
|
||||||
|
.matcher_lyrics("drivers license", "Bobby G")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(lyrics.lyrics_id, 25891609);
|
||||||
|
assert!(lyrics.instrumental);
|
||||||
|
assert!(!lyrics.explicit);
|
||||||
|
assert_eq!(lyrics.lyrics_body, "");
|
||||||
|
assert_eq!(lyrics.lyrics_language, "");
|
||||||
|
assert_eq!(lyrics.lyrics_language_description, "");
|
||||||
|
assert_eq!(lyrics.lyrics_copyright, "");
|
||||||
|
assert!(lyrics.updated_time > datetime!(2021-6-21 0:00 UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This track does not exist
|
||||||
|
#[tokio::test]
|
||||||
|
async fn missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn"))
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn download_testdata() {
|
||||||
|
let json_path = Path::new("testfiles/lyrics.json");
|
||||||
|
if json_path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lyrics = new_mxm()
|
||||||
|
.track_lyrics(TrackId::Commontrack(18576954))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let json_file = File::create(json_path).unwrap();
|
||||||
|
serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn download_testdata_translation() {
|
||||||
|
let json_path = Path::new("testfiles/translation.json");
|
||||||
|
if json_path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let translations = new_mxm()
|
||||||
|
.track_lyrics_translation(TrackId::Commontrack(18576954), "de")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let json_file = File::create(json_path).unwrap();
|
||||||
|
serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn concurrency() {
|
||||||
|
let mxm = new_mxm();
|
||||||
|
|
||||||
|
let album = mxm
|
||||||
|
.album_tracks(
|
||||||
|
musixmatch_inofficial::models::AlbumId::AlbumId(17118624),
|
||||||
|
true,
|
||||||
|
20,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let x = stream::iter(album)
|
||||||
|
.map(|track| {
|
||||||
|
mxm.track_lyrics(musixmatch_inofficial::models::TrackId::TrackId(
|
||||||
|
track.track_id,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.buffered(8)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
dbg!(x);
|
||||||
|
}
|
135
tests/subtitle_test.rs
Normal file
135
tests/subtitle_test.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
mod _fixtures;
|
||||||
|
use _fixtures::*;
|
||||||
|
|
||||||
|
use std::{fs::File, io::BufWriter, path::Path};
|
||||||
|
|
||||||
|
use musixmatch_inofficial::{
|
||||||
|
models::{SubtitleFormat, TrackId},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use rstest::rstest;
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_match() {
|
||||||
|
let subtitle = new_mxm()
|
||||||
|
.matcher_subtitle(
|
||||||
|
"Shine",
|
||||||
|
"Spektrem",
|
||||||
|
SubtitleFormat::Json,
|
||||||
|
Some(315.0),
|
||||||
|
Some(1.0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&subtitle);
|
||||||
|
|
||||||
|
assert_eq!(subtitle.subtitle_id, 36913312);
|
||||||
|
assert_eq!(subtitle.subtitle_language, "en");
|
||||||
|
assert_eq!(subtitle.subtitle_language_description, "English");
|
||||||
|
assert!(
|
||||||
|
subtitle.lyrics_copyright.contains("Kim Jeffeson"),
|
||||||
|
"copyright: {}",
|
||||||
|
subtitle.lyrics_copyright,
|
||||||
|
);
|
||||||
|
assert_eq!(subtitle.subtitle_length, 315);
|
||||||
|
assert!(subtitle.updated_time > datetime!(2021-6-30 0:00 UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::trackid(TrackId::TrackId(205688271))]
|
||||||
|
#[case::commontrack(TrackId::Commontrack(118480583))]
|
||||||
|
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
||||||
|
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
||||||
|
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||||
|
let subtitle = new_mxm()
|
||||||
|
.track_subtitle(track_id, SubtitleFormat::Json, Some(175.0), Some(1.0))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&subtitle);
|
||||||
|
|
||||||
|
assert_eq!(subtitle.subtitle_id, 36476905);
|
||||||
|
assert_eq!(subtitle.subtitle_language, "ko");
|
||||||
|
assert_eq!(subtitle.subtitle_language_description, "Korean");
|
||||||
|
assert!(
|
||||||
|
subtitle.lyrics_copyright.contains("Kenneth Scott Chesak"),
|
||||||
|
"copyright: {}",
|
||||||
|
subtitle.lyrics_copyright,
|
||||||
|
);
|
||||||
|
assert_eq!(subtitle.subtitle_length, 175);
|
||||||
|
assert!(subtitle.updated_time > datetime!(2022-8-27 0:00 UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This track has no lyrics
|
||||||
|
#[tokio::test]
|
||||||
|
async fn instrumental() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.matcher_subtitle(
|
||||||
|
"drivers license",
|
||||||
|
"Bobby G",
|
||||||
|
SubtitleFormat::Json,
|
||||||
|
Some(246.0),
|
||||||
|
Some(1.0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This track has not been synced
|
||||||
|
#[tokio::test]
|
||||||
|
async fn unsynced() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.track_subtitle(
|
||||||
|
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1"),
|
||||||
|
SubtitleFormat::Json,
|
||||||
|
Some(213.0),
|
||||||
|
Some(1.0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get subtitles with wrong length parameter
|
||||||
|
#[tokio::test]
|
||||||
|
async fn wrong_length() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.track_subtitle(
|
||||||
|
TrackId::Commontrack(118480583),
|
||||||
|
SubtitleFormat::Json,
|
||||||
|
Some(200.0),
|
||||||
|
Some(1.0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn download_testdata() {
|
||||||
|
let json_path = Path::new("testfiles/subtitles.json");
|
||||||
|
if json_path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtitle = new_mxm()
|
||||||
|
.track_subtitle(
|
||||||
|
TrackId::Commontrack(18576954),
|
||||||
|
SubtitleFormat::Json,
|
||||||
|
Some(259.0),
|
||||||
|
Some(1.0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let json_file = File::create(json_path).unwrap();
|
||||||
|
serde_json::to_writer_pretty(BufWriter::new(json_file), &subtitle).unwrap();
|
||||||
|
}
|
980
tests/tests.rs
980
tests/tests.rs
|
@ -1,980 +0,0 @@
|
||||||
use rstest::rstest;
|
|
||||||
use time::macros::{date, datetime};
|
|
||||||
|
|
||||||
use musixmatch_inofficial::{
|
|
||||||
models::{AlbumId, ArtistId, TrackId},
|
|
||||||
storage::FileStorage,
|
|
||||||
Error, Musixmatch,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[ctor::ctor]
|
|
||||||
fn init() {
|
|
||||||
let _ = dotenvy::dotenv();
|
|
||||||
env_logger::init();
|
|
||||||
tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.block_on(new_mxm().login())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_mxm() -> Musixmatch {
|
|
||||||
Musixmatch::new(
|
|
||||||
&std::env::var("MUSIXMATCH_EMAIL").unwrap(),
|
|
||||||
&std::env::var("MUSIXMATCH_PASSWORD").unwrap(),
|
|
||||||
Some(Box::<FileStorage>::default()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod album {
|
|
||||||
use super::*;
|
|
||||||
use musixmatch_inofficial::models::AlbumType;
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::id(AlbumId::AlbumId(14248253))]
|
|
||||||
#[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn by_id(#[case] album_id: AlbumId<'_>) {
|
|
||||||
let album = new_mxm().album(album_id).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(album.album_id, 14248253);
|
|
||||||
assert_eq!(
|
|
||||||
album.album_mbid.unwrap(),
|
|
||||||
"6c3cf9d8-88a8-43ed-850b-55813f01e451"
|
|
||||||
);
|
|
||||||
assert_eq!(album.album_name, "Gangnam Style (강남스타일)");
|
|
||||||
assert!(album.album_rating > 25);
|
|
||||||
assert_eq!(album.album_track_count, 1);
|
|
||||||
assert_eq!(album.album_release_date.unwrap(), date!(2012 - 01 - 01));
|
|
||||||
assert_eq!(album.album_release_type, AlbumType::Single);
|
|
||||||
assert_eq!(album.artist_id, 410698);
|
|
||||||
assert_eq!(album.artist_name, "PSY");
|
|
||||||
|
|
||||||
let first_pri_genre = &album.primary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_pri_genre.music_genre_id, 14);
|
|
||||||
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
|
||||||
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
|
||||||
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
|
||||||
|
|
||||||
let first_sec_genre = &album.secondary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_sec_genre.music_genre_id, 17);
|
|
||||||
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
|
||||||
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
|
||||||
assert_eq!(
|
|
||||||
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
|
||||||
"Dance"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
album.album_copyright.unwrap(),
|
|
||||||
"© 2012 Schoolboy/Universal Republic Records, a division of UMG Recordings, Inc."
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
album.album_label.unwrap(),
|
|
||||||
"Silent Records/Universal Republic Records"
|
|
||||||
);
|
|
||||||
assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single");
|
|
||||||
assert!(album.updated_time > datetime!(2022-6-3 0:00 UTC));
|
|
||||||
assert_eq!(
|
|
||||||
album.album_coverart_100x100.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
album.album_coverart_350x350.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_350_350.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
album.album_coverart_500x500.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_500_500.jpg"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn album_tst() {
|
|
||||||
let album = new_mxm().album(AlbumId::AlbumId(23976123)).await.unwrap();
|
|
||||||
println!("type: {:?}", album.album_release_type);
|
|
||||||
println!("date: {:?}", album.album_release_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn by_id_missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.album(AlbumId::AlbumId(999999999999))
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn artist_albums() {
|
|
||||||
let albums = new_mxm()
|
|
||||||
.artist_albums(ArtistId::ArtistId(1039), None, 10, 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(albums.len(), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn artist_albums_missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn charts() {
|
|
||||||
let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(albums.len(), 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod artist {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::id(ArtistId::ArtistId(410698))]
|
|
||||||
#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn by_id(#[case] artist_id: ArtistId<'_>) {
|
|
||||||
let artist = new_mxm().artist(artist_id).await.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&artist);
|
|
||||||
|
|
||||||
assert_eq!(artist.artist_id, 410698);
|
|
||||||
assert_eq!(
|
|
||||||
artist.artist_mbid.unwrap(),
|
|
||||||
"f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"
|
|
||||||
);
|
|
||||||
assert_eq!(artist.artist_name, "PSY");
|
|
||||||
assert!(
|
|
||||||
artist.artist_name_translation_list.iter().any(|tl| {
|
|
||||||
tl.artist_name_translation.language == "KO"
|
|
||||||
&& tl.artist_name_translation.translation == "싸이"
|
|
||||||
}),
|
|
||||||
"missing Korean translation in: {:?}",
|
|
||||||
artist.artist_name_translation_list
|
|
||||||
);
|
|
||||||
assert_eq!(artist.artist_country.unwrap(), "KR");
|
|
||||||
assert!(artist.artist_rating > 50);
|
|
||||||
let first_genre = &artist.primary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_genre.music_genre_id, 14);
|
|
||||||
assert_eq!(first_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_genre.music_genre_name, "Pop");
|
|
||||||
assert_eq!(first_genre.music_genre_name_extended, "Pop");
|
|
||||||
assert_eq!(first_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
|
||||||
assert_eq!(
|
|
||||||
artist.artist_twitter_url.unwrap(),
|
|
||||||
"https://twitter.com/psy_oppa"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
artist.artist_facebook_url.unwrap(),
|
|
||||||
"https://www.facebook.com/officialpsy"
|
|
||||||
);
|
|
||||||
assert_eq!(artist.artist_vanity_id, "410698");
|
|
||||||
assert!(artist.updated_time > datetime!(2016-6-30 0:00 UTC));
|
|
||||||
assert_eq!(artist.begin_date_year.unwrap(), 1977);
|
|
||||||
assert_eq!(artist.begin_date.unwrap(), date!(1977 - 12 - 31));
|
|
||||||
assert_eq!(artist.end_date_year, None);
|
|
||||||
assert_eq!(artist.end_date, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn by_id_missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.artist(ArtistId::ArtistId(999999999999))
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn related() {
|
|
||||||
let artists = new_mxm()
|
|
||||||
.artist_related(ArtistId::ArtistId(26485840), 10, 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(artists.len(), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn related_missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.artist_related(ArtistId::ArtistId(999999999999), 10, 1)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn search() {
|
|
||||||
let artists = new_mxm().artist_search("psy", 5, 1).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(artists.len(), 5);
|
|
||||||
|
|
||||||
let artist = &artists[0];
|
|
||||||
assert_eq!(artist.artist_id, 410698);
|
|
||||||
assert_eq!(artist.artist_name, "PSY");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn search_empty() {
|
|
||||||
let artists = new_mxm()
|
|
||||||
.artist_search(
|
|
||||||
"Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz",
|
|
||||||
5,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(artists.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn charts() {
|
|
||||||
let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(artists.len(), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn charts_no_country() {
|
|
||||||
let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(artists.len(), 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod track {
|
|
||||||
use super::*;
|
|
||||||
use musixmatch_inofficial::models::{ChartName, SortOrder};
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::no_translation(false, false)]
|
|
||||||
#[case::translation_2c(true, false)]
|
|
||||||
#[case::translation_3c(true, true)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
|
||||||
let track = new_mxm()
|
|
||||||
.matcher_track(
|
|
||||||
"Poker Face",
|
|
||||||
"Lady Gaga",
|
|
||||||
"The Fame",
|
|
||||||
translation_status,
|
|
||||||
lang_3c,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&track);
|
|
||||||
|
|
||||||
assert_eq!(track.track_id, 15476784);
|
|
||||||
assert_eq!(
|
|
||||||
track.track_mbid.unwrap(),
|
|
||||||
"080975b0-39b1-493c-ae64-5cb3292409bb"
|
|
||||||
);
|
|
||||||
assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
|
|
||||||
assert!(
|
|
||||||
track.commontrack_isrcs[0]
|
|
||||||
.iter()
|
|
||||||
.any(|isrc| isrc == "USUM70824409"),
|
|
||||||
"commontrack_isrcs: {:?}",
|
|
||||||
&track.commontrack_isrcs[0],
|
|
||||||
);
|
|
||||||
assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO");
|
|
||||||
assert!(
|
|
||||||
track
|
|
||||||
.commontrack_spotify_ids
|
|
||||||
.iter()
|
|
||||||
.any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"),
|
|
||||||
"commontrack_spotify_ids: {:?}",
|
|
||||||
track.commontrack_spotify_ids,
|
|
||||||
);
|
|
||||||
assert_eq!(track.track_name, "Poker Face");
|
|
||||||
assert!(track.track_rating > 50);
|
|
||||||
assert_eq!(track.commontrack_id, 47672612);
|
|
||||||
assert!(!track.instrumental);
|
|
||||||
assert!(track.explicit);
|
|
||||||
assert!(track.has_subtitles);
|
|
||||||
assert!(track.has_richsync);
|
|
||||||
assert!(track.has_track_structure);
|
|
||||||
assert!(track.num_favourite > 50);
|
|
||||||
assert_eq!(track.lyrics_id.unwrap(), 30678771);
|
|
||||||
assert_eq!(track.subtitle_id.unwrap(), 36450705);
|
|
||||||
assert_eq!(track.album_id, 13810402);
|
|
||||||
assert_eq!(track.album_name, "The Fame");
|
|
||||||
assert_eq!(track.artist_id, 378462);
|
|
||||||
assert_eq!(
|
|
||||||
track.artist_mbid.unwrap(),
|
|
||||||
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
|
|
||||||
);
|
|
||||||
assert_eq!(track.artist_name, "Lady Gaga");
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_100x100.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_350x350.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_500x500.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
|
||||||
let first_release = track.first_release_date.unwrap();
|
|
||||||
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
|
||||||
assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC));
|
|
||||||
|
|
||||||
let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_pri_genre.music_genre_id, 14);
|
|
||||||
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
|
||||||
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
|
||||||
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
|
||||||
|
|
||||||
let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_sec_genre.music_genre_id, 17);
|
|
||||||
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
|
||||||
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
|
||||||
assert_eq!(
|
|
||||||
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
|
||||||
"Dance"
|
|
||||||
);
|
|
||||||
|
|
||||||
if translation_status {
|
|
||||||
assert!(
|
|
||||||
track.track_lyrics_translation_status.iter().all(|tl| {
|
|
||||||
(if lang_3c {
|
|
||||||
tl.from == "eng"
|
|
||||||
} else {
|
|
||||||
tl.from == "en"
|
|
||||||
}) && tl.perc >= 0.0
|
|
||||||
&& tl.perc <= 1.0
|
|
||||||
}),
|
|
||||||
"translation: {:?}",
|
|
||||||
track.track_lyrics_translation_status
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert!(track.track_lyrics_translation_status.is_empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::trackid(TrackId::TrackId(205688271))]
|
|
||||||
#[case::commontrack(TrackId::Commontrack(118480583))]
|
|
||||||
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
|
||||||
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
|
||||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_id(#[case] track_id: TrackId<'_>) {
|
|
||||||
let track = new_mxm().track(track_id, true, false).await.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&track);
|
|
||||||
|
|
||||||
assert_eq!(track.track_id, 205688271);
|
|
||||||
assert_eq!(track.track_isrc.unwrap(), "KRA302000590");
|
|
||||||
assert_eq!(track.track_spotify_id.unwrap(), "1t2qYCAjUAoGfeFeoBlK51");
|
|
||||||
assert_eq!(track.track_name, "Black Mamba");
|
|
||||||
assert!(track.track_rating > 50);
|
|
||||||
assert_eq!(track.track_length, 175);
|
|
||||||
assert!(!track.explicit);
|
|
||||||
assert!(track.has_lyrics);
|
|
||||||
assert!(track.has_subtitles);
|
|
||||||
assert!(track.has_richsync);
|
|
||||||
assert!(track.num_favourite > 200);
|
|
||||||
assert_eq!(track.lyrics_id.unwrap(), 30126001);
|
|
||||||
assert_eq!(track.subtitle_id.unwrap(), 36476905);
|
|
||||||
assert_eq!(track.album_id, 41035954);
|
|
||||||
assert_eq!(track.album_name, "Black Mamba - Single");
|
|
||||||
assert_eq!(track.artist_id, 46970441);
|
|
||||||
assert_eq!(track.artist_name, "aespa");
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_100x100.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_350x350.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_500x500.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba");
|
|
||||||
|
|
||||||
let release_date = track.first_release_date.unwrap();
|
|
||||||
assert_eq!(release_date.date(), date!(2020 - 11 - 17));
|
|
||||||
assert!(track.updated_time > datetime!(2022-8-27 0:00 UTC));
|
|
||||||
|
|
||||||
let first_tstatus = &track.track_lyrics_translation_status[0];
|
|
||||||
assert_eq!(first_tstatus.from, "ko");
|
|
||||||
assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::no_translation(false, false)]
|
|
||||||
#[case::translation_2c(true, false)]
|
|
||||||
#[case::translation_3c(true, true)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
|
||||||
let track = new_mxm()
|
|
||||||
.track(TrackId::TrackId(15476784), translation_status, lang_3c)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&track);
|
|
||||||
|
|
||||||
assert_eq!(track.track_id, 15476784);
|
|
||||||
assert_eq!(
|
|
||||||
track.track_mbid.unwrap(),
|
|
||||||
"080975b0-39b1-493c-ae64-5cb3292409bb"
|
|
||||||
);
|
|
||||||
assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
|
|
||||||
assert!(
|
|
||||||
track.commontrack_isrcs[0]
|
|
||||||
.iter()
|
|
||||||
.any(|isrc| isrc == "USUM70824409"),
|
|
||||||
"commontrack_isrcs: {:?}",
|
|
||||||
&track.commontrack_isrcs[0],
|
|
||||||
);
|
|
||||||
assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO");
|
|
||||||
assert!(
|
|
||||||
track
|
|
||||||
.commontrack_spotify_ids
|
|
||||||
.iter()
|
|
||||||
.any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"),
|
|
||||||
"commontrack_spotify_ids: {:?}",
|
|
||||||
track.commontrack_spotify_ids,
|
|
||||||
);
|
|
||||||
assert_eq!(track.track_name, "Poker Face");
|
|
||||||
assert!(track.track_rating > 50);
|
|
||||||
assert_eq!(track.commontrack_id, 47672612);
|
|
||||||
assert!(!track.instrumental);
|
|
||||||
assert!(track.explicit);
|
|
||||||
assert!(track.has_subtitles);
|
|
||||||
assert!(track.has_richsync);
|
|
||||||
assert!(track.has_track_structure);
|
|
||||||
assert!(track.num_favourite > 50);
|
|
||||||
assert_eq!(track.lyrics_id.unwrap(), 30678771);
|
|
||||||
assert_eq!(track.subtitle_id.unwrap(), 36450705);
|
|
||||||
assert_eq!(track.album_id, 13810402);
|
|
||||||
assert_eq!(track.album_name, "The Fame");
|
|
||||||
assert_eq!(track.artist_id, 378462);
|
|
||||||
assert_eq!(
|
|
||||||
track.artist_mbid.unwrap(),
|
|
||||||
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
|
|
||||||
);
|
|
||||||
assert_eq!(track.artist_name, "Lady Gaga");
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_100x100.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_350x350.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
track.album_coverart_500x500.unwrap(),
|
|
||||||
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
|
||||||
let first_release = track.first_release_date.unwrap();
|
|
||||||
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
|
||||||
assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC));
|
|
||||||
|
|
||||||
let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_pri_genre.music_genre_id, 14);
|
|
||||||
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
|
||||||
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
|
||||||
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
|
||||||
|
|
||||||
let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre;
|
|
||||||
assert_eq!(first_sec_genre.music_genre_id, 17);
|
|
||||||
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
|
||||||
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
|
||||||
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
|
||||||
assert_eq!(
|
|
||||||
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
|
||||||
"Dance"
|
|
||||||
);
|
|
||||||
|
|
||||||
if translation_status {
|
|
||||||
assert!(
|
|
||||||
track.track_lyrics_translation_status.iter().all(|tl| {
|
|
||||||
(if lang_3c {
|
|
||||||
tl.from == "eng"
|
|
||||||
} else {
|
|
||||||
tl.from == "en"
|
|
||||||
}) && tl.perc >= 0.0
|
|
||||||
&& tl.perc <= 1.0
|
|
||||||
}),
|
|
||||||
"translation: {:?}",
|
|
||||||
track.track_lyrics_translation_status
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert!(track.track_lyrics_translation_status.is_empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_id_missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.track(TrackId::TrackId(999999999999), false, false)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn album_tracks() {
|
|
||||||
let tracks = new_mxm()
|
|
||||||
.album_tracks(AlbumId::AlbumId(17118624), true, 20, 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&tracks);
|
|
||||||
|
|
||||||
let track_names = tracks
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.track_name.to_owned())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
track_names,
|
|
||||||
vec![
|
|
||||||
"Gäa",
|
|
||||||
"Vergiss mein nicht",
|
|
||||||
"Orome",
|
|
||||||
"Falke flieg",
|
|
||||||
"Minne",
|
|
||||||
"Das Lied der Ahnen",
|
|
||||||
"Hörst du den Wind",
|
|
||||||
"Nan Úye",
|
|
||||||
"Faolan",
|
|
||||||
"Hymne der Nacht",
|
|
||||||
"Avalon",
|
|
||||||
"Tolo Nan",
|
|
||||||
"Oonagh",
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
tracks.iter().for_each(|t| {
|
|
||||||
assert!(t.has_lyrics);
|
|
||||||
assert!(t.has_subtitles);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn album_missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::top(ChartName::Top)]
|
|
||||||
#[case::hot(ChartName::Hot)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn charts(#[case] chart_name: ChartName) {
|
|
||||||
let tracks = new_mxm()
|
|
||||||
.chart_tracks("US", chart_name, true, 20, 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(tracks.len(), 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn search() {
|
|
||||||
let tracks = new_mxm()
|
|
||||||
.track_search()
|
|
||||||
.q_artist("Lena")
|
|
||||||
.q_track("Satellite")
|
|
||||||
.s_track_rating(SortOrder::Desc)
|
|
||||||
.send(1, 0)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&tracks);
|
|
||||||
|
|
||||||
assert_eq!(tracks.len(), 1);
|
|
||||||
|
|
||||||
let track = &tracks[0];
|
|
||||||
assert_eq!(track.commontrack_id, 12426476);
|
|
||||||
assert_eq!(track.track_name, "Satellite");
|
|
||||||
assert_eq!(track.artist_name, "Lena");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn search_lyrics() {
|
|
||||||
let tracks = new_mxm()
|
|
||||||
.track_search()
|
|
||||||
.q_lyrics("the whole world stops and stares for a while")
|
|
||||||
.s_track_rating(SortOrder::Desc)
|
|
||||||
.send(10, 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(tracks.len(), 10);
|
|
||||||
|
|
||||||
let track = &tracks[0];
|
|
||||||
assert_eq!(track.track_name, "Just the Way You Are");
|
|
||||||
assert_eq!(track.artist_name, "Bruno Mars");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn search_empty() {
|
|
||||||
let artists = new_mxm()
|
|
||||||
.track_search()
|
|
||||||
.q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz")
|
|
||||||
.send(10, 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(artists.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn genres() {
|
|
||||||
let genres = new_mxm().genres().await.unwrap();
|
|
||||||
assert!(genres.len() > 360);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn snippet() {
|
|
||||||
let snippet = new_mxm()
|
|
||||||
.track_snippet(TrackId::Commontrack(8874280))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(snippet.snippet_id, 23036767);
|
|
||||||
assert_eq!(snippet.snippet_language.unwrap(), "en");
|
|
||||||
assert!(!snippet.instrumental);
|
|
||||||
assert!(snippet.updated_time > datetime!(2022-8-29 0:00 UTC));
|
|
||||||
assert_eq!(
|
|
||||||
snippet.snippet_body,
|
|
||||||
"There's not a thing that I would change"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod lyrics {
|
|
||||||
use futures::stream::{self, StreamExt};
|
|
||||||
use std::{fs::File, io::BufWriter, path::Path};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_match() {
|
|
||||||
let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&lyrics);
|
|
||||||
|
|
||||||
assert_eq!(lyrics.lyrics_id, 25947036);
|
|
||||||
assert!(!lyrics.instrumental);
|
|
||||||
assert!(!lyrics.explicit);
|
|
||||||
assert!(lyrics
|
|
||||||
.lyrics_body
|
|
||||||
.starts_with("Eyes, in the sky, gazing far into the night\n"));
|
|
||||||
assert_eq!(lyrics.lyrics_language.unwrap(), "en");
|
|
||||||
assert_eq!(lyrics.lyrics_language_description.unwrap(), "English");
|
|
||||||
let copyright = lyrics.lyrics_copyright.unwrap();
|
|
||||||
assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",);
|
|
||||||
assert!(lyrics.updated_time > datetime!(2021-6-3 0:00 UTC));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::trackid(TrackId::TrackId(205688271))]
|
|
||||||
#[case::commontrack(TrackId::Commontrack(118480583))]
|
|
||||||
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
|
||||||
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
|
||||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_id(#[case] track_id: TrackId<'_>) {
|
|
||||||
let lyrics = new_mxm().track_lyrics(track_id).await.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&lyrics);
|
|
||||||
|
|
||||||
assert_eq!(lyrics.lyrics_id, 30126001);
|
|
||||||
assert_eq!(lyrics.lyrics_language.unwrap(), "ko");
|
|
||||||
assert_eq!(lyrics.lyrics_language_description.unwrap(), "Korean");
|
|
||||||
let copyright = lyrics.lyrics_copyright.unwrap();
|
|
||||||
assert!(
|
|
||||||
copyright.contains("Kenneth Scott Chesak"),
|
|
||||||
"copyright: {copyright}",
|
|
||||||
);
|
|
||||||
assert!(lyrics.updated_time > datetime!(2022-8-27 0:00 UTC));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This track has no lyrics
|
|
||||||
#[tokio::test]
|
|
||||||
async fn instrumental() {
|
|
||||||
let lyrics = new_mxm()
|
|
||||||
.matcher_lyrics("drivers license", "Bobby G")
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(lyrics.lyrics_id, 25891609);
|
|
||||||
assert!(lyrics.instrumental);
|
|
||||||
assert!(!lyrics.explicit);
|
|
||||||
assert_eq!(lyrics.lyrics_body, "");
|
|
||||||
assert_eq!(lyrics.lyrics_language, None);
|
|
||||||
assert_eq!(lyrics.lyrics_language_description, None);
|
|
||||||
assert_eq!(lyrics.lyrics_copyright, None);
|
|
||||||
assert!(lyrics.updated_time > datetime!(2021-6-21 0:00 UTC));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This track does not exist
|
|
||||||
#[tokio::test]
|
|
||||||
async fn missing() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn"))
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn download_testdata() {
|
|
||||||
let json_path = Path::new("testfiles/lyrics.json");
|
|
||||||
if json_path.exists() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lyrics = new_mxm()
|
|
||||||
.track_lyrics(TrackId::Commontrack(18576954))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let json_file = File::create(json_path).unwrap();
|
|
||||||
serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn download_testdata_translation() {
|
|
||||||
let json_path = Path::new("testfiles/translation.json");
|
|
||||||
if json_path.exists() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let translations = new_mxm()
|
|
||||||
.track_lyrics_translation(TrackId::Commontrack(18576954), "de")
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let json_file = File::create(json_path).unwrap();
|
|
||||||
serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn concurrency() {
|
|
||||||
let mxm = new_mxm();
|
|
||||||
|
|
||||||
let album = mxm
|
|
||||||
.album_tracks(
|
|
||||||
musixmatch_inofficial::models::AlbumId::AlbumId(17118624),
|
|
||||||
true,
|
|
||||||
20,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let x = stream::iter(album)
|
|
||||||
.map(|track| {
|
|
||||||
mxm.track_lyrics(musixmatch_inofficial::models::TrackId::TrackId(
|
|
||||||
track.track_id,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.buffered(8)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(Result::unwrap)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
dbg!(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod subtitles {
|
|
||||||
use std::{fs::File, io::BufWriter, path::Path};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use musixmatch_inofficial::models::SubtitleFormat;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_match() {
|
|
||||||
let subtitle = new_mxm()
|
|
||||||
.matcher_subtitle(
|
|
||||||
"Shine",
|
|
||||||
"Spektrem",
|
|
||||||
SubtitleFormat::Json,
|
|
||||||
Some(315.0),
|
|
||||||
Some(1.0),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&subtitle);
|
|
||||||
|
|
||||||
assert_eq!(subtitle.subtitle_id, 36913312);
|
|
||||||
assert_eq!(subtitle.subtitle_language.unwrap(), "en");
|
|
||||||
assert_eq!(subtitle.subtitle_language_description.unwrap(), "English");
|
|
||||||
let copyright = subtitle.lyrics_copyright.unwrap();
|
|
||||||
assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",);
|
|
||||||
assert_eq!(subtitle.subtitle_length, 315);
|
|
||||||
assert!(subtitle.updated_time > datetime!(2021-6-30 0:00 UTC));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::trackid(TrackId::TrackId(205688271))]
|
|
||||||
#[case::commontrack(TrackId::Commontrack(118480583))]
|
|
||||||
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
|
||||||
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
|
||||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn from_id(#[case] track_id: TrackId<'_>) {
|
|
||||||
let subtitle = new_mxm()
|
|
||||||
.track_subtitle(track_id, SubtitleFormat::Json, Some(175.0), Some(1.0))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&subtitle);
|
|
||||||
|
|
||||||
assert_eq!(subtitle.subtitle_id, 36476905);
|
|
||||||
assert_eq!(subtitle.subtitle_language.unwrap(), "ko");
|
|
||||||
assert_eq!(subtitle.subtitle_language_description.unwrap(), "Korean");
|
|
||||||
let copyright = subtitle.lyrics_copyright.unwrap();
|
|
||||||
assert!(
|
|
||||||
copyright.contains("Kenneth Scott Chesak"),
|
|
||||||
"copyright: {copyright}",
|
|
||||||
);
|
|
||||||
assert_eq!(subtitle.subtitle_length, 175);
|
|
||||||
assert!(subtitle.updated_time > datetime!(2022-8-27 0:00 UTC));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This track has no lyrics
|
|
||||||
#[tokio::test]
|
|
||||||
async fn instrumental() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.matcher_subtitle(
|
|
||||||
"drivers license",
|
|
||||||
"Bobby G",
|
|
||||||
SubtitleFormat::Json,
|
|
||||||
Some(246.0),
|
|
||||||
Some(1.0),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This track has not been synced
|
|
||||||
#[tokio::test]
|
|
||||||
async fn unsynced() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.track_subtitle(
|
|
||||||
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1"),
|
|
||||||
SubtitleFormat::Json,
|
|
||||||
Some(213.0),
|
|
||||||
Some(1.0),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to get subtitles with wrong length parameter
|
|
||||||
#[tokio::test]
|
|
||||||
async fn wrong_length() {
|
|
||||||
let err = new_mxm()
|
|
||||||
.track_subtitle(
|
|
||||||
TrackId::Commontrack(118480583),
|
|
||||||
SubtitleFormat::Json,
|
|
||||||
Some(200.0),
|
|
||||||
Some(1.0),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, Error::NotFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn download_testdata() {
|
|
||||||
let json_path = Path::new("testfiles/subtitles.json");
|
|
||||||
if json_path.exists() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let subtitle = new_mxm()
|
|
||||||
.track_subtitle(
|
|
||||||
TrackId::Commontrack(18576954),
|
|
||||||
SubtitleFormat::Json,
|
|
||||||
Some(259.0),
|
|
||||||
Some(1.0),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let json_file = File::create(json_path).unwrap();
|
|
||||||
serde_json::to_writer_pretty(BufWriter::new(json_file), &subtitle).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod translation {
|
|
||||||
use std::{fs::File, io::BufReader, path::Path};
|
|
||||||
|
|
||||||
use musixmatch_inofficial::models::{Lyrics, Subtitle, TranslationList, TranslationMap};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn translation_test() {
|
|
||||||
let lyrics_path = Path::new("testfiles/lyrics.json");
|
|
||||||
let subtitles_path = Path::new("testfiles/subtitles.json");
|
|
||||||
let translation_path = Path::new("testfiles/translation.json");
|
|
||||||
|
|
||||||
let lyrics: Lyrics =
|
|
||||||
serde_json::from_reader(BufReader::new(File::open(lyrics_path).unwrap())).unwrap();
|
|
||||||
let subtitle: Subtitle =
|
|
||||||
serde_json::from_reader(BufReader::new(File::open(subtitles_path).unwrap())).unwrap();
|
|
||||||
let translations: TranslationList =
|
|
||||||
serde_json::from_reader(BufReader::new(File::open(translation_path).unwrap())).unwrap();
|
|
||||||
|
|
||||||
let t_map = TranslationMap::from(translations);
|
|
||||||
|
|
||||||
let lyrics_trans = t_map.translate_lyrics(&lyrics.lyrics_body);
|
|
||||||
let expected_lyrics = std::fs::read_to_string("testfiles/translated_lyrics.txt").unwrap();
|
|
||||||
assert_eq!(lyrics_trans.trim(), expected_lyrics.trim());
|
|
||||||
|
|
||||||
let subtitles_trans = t_map.translate_subtitles(&subtitle.to_lines().unwrap());
|
|
||||||
|
|
||||||
let expected_lrc = std::fs::read_to_string("testfiles/translated_subtitles.lrc").unwrap();
|
|
||||||
let expected_ttml = std::fs::read_to_string("testfiles/translated_subtitles.xml").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(subtitles_trans.to_lrc().trim(), expected_lrc.trim());
|
|
||||||
assert_eq!(subtitles_trans.to_ttml().trim(), expected_ttml.trim());
|
|
||||||
}
|
|
||||||
}
|
|
412
tests/track_test.rs
Normal file
412
tests/track_test.rs
Normal file
|
@ -0,0 +1,412 @@
|
||||||
|
mod _fixtures;
|
||||||
|
use _fixtures::*;
|
||||||
|
|
||||||
|
use musixmatch_inofficial::{
|
||||||
|
models::{AlbumId, ChartName, SortOrder, TrackId},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use rstest::rstest;
|
||||||
|
use time::macros::{date, datetime};
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::no_translation(false, false)]
|
||||||
|
#[case::translation_2c(true, false)]
|
||||||
|
#[case::translation_3c(true, true)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
||||||
|
let track = new_mxm()
|
||||||
|
.matcher_track(
|
||||||
|
"Poker Face",
|
||||||
|
"Lady Gaga",
|
||||||
|
"The Fame",
|
||||||
|
translation_status,
|
||||||
|
lang_3c,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&track);
|
||||||
|
|
||||||
|
assert_eq!(track.track_id, 15476784);
|
||||||
|
assert_eq!(track.track_mbid, "080975b0-39b1-493c-ae64-5cb3292409bb");
|
||||||
|
assert_eq!(track.track_isrc, "USUM70824409");
|
||||||
|
assert!(
|
||||||
|
track.commontrack_isrcs[0]
|
||||||
|
.iter()
|
||||||
|
.any(|isrc| isrc == "USUM70824409"),
|
||||||
|
"commontrack_isrcs: {:?}",
|
||||||
|
&track.commontrack_isrcs[0],
|
||||||
|
);
|
||||||
|
assert_eq!(track.track_spotify_id, "5R8dQOPq8haW94K7mgERlO");
|
||||||
|
assert!(
|
||||||
|
track
|
||||||
|
.commontrack_spotify_ids
|
||||||
|
.iter()
|
||||||
|
.any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"),
|
||||||
|
"commontrack_spotify_ids: {:?}",
|
||||||
|
track.commontrack_spotify_ids,
|
||||||
|
);
|
||||||
|
assert_eq!(track.track_name, "Poker Face");
|
||||||
|
assert!(track.track_rating > 50);
|
||||||
|
assert_eq!(track.commontrack_id, 47672612);
|
||||||
|
assert!(!track.instrumental);
|
||||||
|
assert!(track.explicit);
|
||||||
|
assert!(track.has_subtitles);
|
||||||
|
assert!(track.has_richsync);
|
||||||
|
assert!(track.has_track_structure);
|
||||||
|
assert!(track.num_favourite > 50);
|
||||||
|
assert_eq!(track.lyrics_id, 30678771);
|
||||||
|
assert_eq!(track.subtitle_id, 36450705);
|
||||||
|
assert_eq!(track.album_id, 13810402);
|
||||||
|
assert_eq!(track.album_name, "The Fame");
|
||||||
|
assert_eq!(track.artist_id, 378462);
|
||||||
|
assert_eq!(track.artist_mbid, "650e7db6-b795-4eb5-a702-5ea2fc46c848");
|
||||||
|
assert_eq!(track.artist_name, "Lady Gaga");
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_100x100,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_350x350,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_500x500,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
||||||
|
let first_release = track.first_release_date.unwrap();
|
||||||
|
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
||||||
|
assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC));
|
||||||
|
|
||||||
|
let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_pri_genre.music_genre_id, 14);
|
||||||
|
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
||||||
|
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
||||||
|
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||||
|
|
||||||
|
let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_sec_genre.music_genre_id, 17);
|
||||||
|
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
||||||
|
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
||||||
|
assert_eq!(
|
||||||
|
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
||||||
|
"Dance"
|
||||||
|
);
|
||||||
|
|
||||||
|
if translation_status {
|
||||||
|
assert!(
|
||||||
|
track.track_lyrics_translation_status.iter().all(|tl| {
|
||||||
|
(if lang_3c {
|
||||||
|
tl.from == "eng"
|
||||||
|
} else {
|
||||||
|
tl.from == "en"
|
||||||
|
}) && tl.perc >= 0.0
|
||||||
|
&& tl.perc <= 1.0
|
||||||
|
}),
|
||||||
|
"translation: {:?}",
|
||||||
|
track.track_lyrics_translation_status
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(track.track_lyrics_translation_status.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::trackid(TrackId::TrackId(205688271))]
|
||||||
|
#[case::commontrack(TrackId::Commontrack(118480583))]
|
||||||
|
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
||||||
|
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
||||||
|
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||||
|
let track = new_mxm().track(track_id, true, false).await.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&track);
|
||||||
|
|
||||||
|
assert_eq!(track.track_id, 205688271);
|
||||||
|
assert_eq!(track.track_isrc, "KRA302000590");
|
||||||
|
assert_eq!(track.track_spotify_id, "1t2qYCAjUAoGfeFeoBlK51");
|
||||||
|
assert_eq!(track.track_name, "Black Mamba");
|
||||||
|
assert!(track.track_rating > 50);
|
||||||
|
assert_eq!(track.track_length, 175);
|
||||||
|
assert!(!track.explicit);
|
||||||
|
assert!(track.has_lyrics);
|
||||||
|
assert!(track.has_subtitles);
|
||||||
|
assert!(track.has_richsync);
|
||||||
|
assert!(track.num_favourite > 200);
|
||||||
|
assert_eq!(track.lyrics_id, 30126001);
|
||||||
|
assert_eq!(track.subtitle_id, 36476905);
|
||||||
|
assert_eq!(track.album_id, 41035954);
|
||||||
|
assert_eq!(track.album_name, "Black Mamba - Single");
|
||||||
|
assert_eq!(track.artist_id, 46970441);
|
||||||
|
assert_eq!(track.artist_name, "aespa");
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_100x100,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_350x350,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_500x500,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba");
|
||||||
|
|
||||||
|
let release_date = track.first_release_date.unwrap();
|
||||||
|
assert_eq!(release_date.date(), date!(2020 - 11 - 17));
|
||||||
|
assert!(track.updated_time > datetime!(2022-8-27 0:00 UTC));
|
||||||
|
|
||||||
|
let first_tstatus = &track.track_lyrics_translation_status[0];
|
||||||
|
assert_eq!(first_tstatus.from, "ko");
|
||||||
|
assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::no_translation(false, false)]
|
||||||
|
#[case::translation_2c(true, false)]
|
||||||
|
#[case::translation_3c(true, true)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
||||||
|
let track = new_mxm()
|
||||||
|
.track(TrackId::TrackId(15476784), translation_status, lang_3c)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&track);
|
||||||
|
|
||||||
|
assert_eq!(track.track_id, 15476784);
|
||||||
|
assert_eq!(track.track_mbid, "080975b0-39b1-493c-ae64-5cb3292409bb");
|
||||||
|
assert_eq!(track.track_isrc, "USUM70824409");
|
||||||
|
assert!(
|
||||||
|
track.commontrack_isrcs[0]
|
||||||
|
.iter()
|
||||||
|
.any(|isrc| isrc == "USUM70824409"),
|
||||||
|
"commontrack_isrcs: {:?}",
|
||||||
|
&track.commontrack_isrcs[0],
|
||||||
|
);
|
||||||
|
assert_eq!(track.track_spotify_id, "5R8dQOPq8haW94K7mgERlO");
|
||||||
|
assert!(
|
||||||
|
track
|
||||||
|
.commontrack_spotify_ids
|
||||||
|
.iter()
|
||||||
|
.any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"),
|
||||||
|
"commontrack_spotify_ids: {:?}",
|
||||||
|
track.commontrack_spotify_ids,
|
||||||
|
);
|
||||||
|
assert_eq!(track.track_name, "Poker Face");
|
||||||
|
assert!(track.track_rating > 50);
|
||||||
|
assert_eq!(track.commontrack_id, 47672612);
|
||||||
|
assert!(!track.instrumental);
|
||||||
|
assert!(track.explicit);
|
||||||
|
assert!(track.has_subtitles);
|
||||||
|
assert!(track.has_richsync);
|
||||||
|
assert!(track.has_track_structure);
|
||||||
|
assert!(track.num_favourite > 50);
|
||||||
|
assert_eq!(track.lyrics_id, 30678771);
|
||||||
|
assert_eq!(track.subtitle_id, 36450705);
|
||||||
|
assert_eq!(track.album_id, 13810402);
|
||||||
|
assert_eq!(track.album_name, "The Fame");
|
||||||
|
assert_eq!(track.artist_id, 378462);
|
||||||
|
assert_eq!(track.artist_mbid, "650e7db6-b795-4eb5-a702-5ea2fc46c848");
|
||||||
|
assert_eq!(track.artist_name, "Lady Gaga");
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_100x100,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_350x350,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
track.album_coverart_500x500,
|
||||||
|
"https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg"
|
||||||
|
);
|
||||||
|
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
||||||
|
let first_release = track.first_release_date.unwrap();
|
||||||
|
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
||||||
|
assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC));
|
||||||
|
|
||||||
|
let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_pri_genre.music_genre_id, 14);
|
||||||
|
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
||||||
|
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
||||||
|
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||||
|
|
||||||
|
let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre;
|
||||||
|
assert_eq!(first_sec_genre.music_genre_id, 17);
|
||||||
|
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
||||||
|
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
||||||
|
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
||||||
|
assert_eq!(
|
||||||
|
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
||||||
|
"Dance"
|
||||||
|
);
|
||||||
|
|
||||||
|
if translation_status {
|
||||||
|
assert!(
|
||||||
|
track.track_lyrics_translation_status.iter().all(|tl| {
|
||||||
|
(if lang_3c {
|
||||||
|
tl.from == "eng"
|
||||||
|
} else {
|
||||||
|
tl.from == "en"
|
||||||
|
}) && tl.perc >= 0.0
|
||||||
|
&& tl.perc <= 1.0
|
||||||
|
}),
|
||||||
|
"translation: {:?}",
|
||||||
|
track.track_lyrics_translation_status
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(track.track_lyrics_translation_status.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn from_id_missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.track(TrackId::TrackId(999999999999), false, false)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn album_tracks() {
|
||||||
|
let tracks = new_mxm()
|
||||||
|
.album_tracks(AlbumId::AlbumId(17118624), true, 20, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&tracks);
|
||||||
|
|
||||||
|
let track_names = tracks
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.track_name.to_owned())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
track_names,
|
||||||
|
vec![
|
||||||
|
"Gäa",
|
||||||
|
"Vergiss mein nicht",
|
||||||
|
"Orome",
|
||||||
|
"Falke flieg",
|
||||||
|
"Minne",
|
||||||
|
"Das Lied der Ahnen",
|
||||||
|
"Hörst du den Wind",
|
||||||
|
"Nan Úye",
|
||||||
|
"Faolan",
|
||||||
|
"Hymne der Nacht",
|
||||||
|
"Avalon",
|
||||||
|
"Tolo Nan",
|
||||||
|
"Oonagh",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
tracks.iter().for_each(|t| {
|
||||||
|
assert!(t.has_lyrics);
|
||||||
|
assert!(t.has_subtitles);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn album_missing() {
|
||||||
|
let err = new_mxm()
|
||||||
|
.album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::top(ChartName::Top)]
|
||||||
|
#[case::hot(ChartName::Hot)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn charts(#[case] chart_name: ChartName) {
|
||||||
|
let tracks = new_mxm()
|
||||||
|
.chart_tracks("US", chart_name, true, 20, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(tracks.len(), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn search() {
|
||||||
|
let tracks = new_mxm()
|
||||||
|
.track_search()
|
||||||
|
.q_artist("Lena")
|
||||||
|
.q_track("Satellite")
|
||||||
|
.s_track_rating(SortOrder::Desc)
|
||||||
|
.send(1, 0)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// dbg!(&tracks);
|
||||||
|
|
||||||
|
assert_eq!(tracks.len(), 1);
|
||||||
|
|
||||||
|
let track = &tracks[0];
|
||||||
|
assert_eq!(track.commontrack_id, 12426476);
|
||||||
|
assert_eq!(track.track_name, "Satellite");
|
||||||
|
assert_eq!(track.artist_name, "Lena");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn search_lyrics() {
|
||||||
|
let tracks = new_mxm()
|
||||||
|
.track_search()
|
||||||
|
.q_lyrics("the whole world stops and stares for a while")
|
||||||
|
.s_track_rating(SortOrder::Desc)
|
||||||
|
.send(10, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(tracks.len(), 10);
|
||||||
|
|
||||||
|
let track = &tracks[0];
|
||||||
|
assert_eq!(track.track_name, "Just the Way You Are");
|
||||||
|
assert_eq!(track.artist_name, "Bruno Mars");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn search_empty() {
|
||||||
|
let artists = new_mxm()
|
||||||
|
.track_search()
|
||||||
|
.q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz")
|
||||||
|
.send(10, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artists.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn genres() {
|
||||||
|
let genres = new_mxm().genres().await.unwrap();
|
||||||
|
assert!(genres.len() > 360);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn snippet() {
|
||||||
|
let snippet = new_mxm()
|
||||||
|
.track_snippet(TrackId::Commontrack(8874280))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(snippet.snippet_id, 23036767);
|
||||||
|
assert_eq!(snippet.snippet_language, "en");
|
||||||
|
assert!(!snippet.instrumental);
|
||||||
|
assert!(snippet.updated_time > datetime!(2022-8-29 0:00 UTC));
|
||||||
|
assert_eq!(
|
||||||
|
snippet.snippet_body,
|
||||||
|
"There's not a thing that I would change"
|
||||||
|
);
|
||||||
|
}
|
31
tests/translation_test.rs
Normal file
31
tests/translation_test.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use std::{fs::File, io::BufReader, path::Path};
|
||||||
|
|
||||||
|
use musixmatch_inofficial::models::{Lyrics, Subtitle, TranslationList, TranslationMap};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translation_test() {
|
||||||
|
let lyrics_path = Path::new("testfiles/lyrics.json");
|
||||||
|
let subtitles_path = Path::new("testfiles/subtitles.json");
|
||||||
|
let translation_path = Path::new("testfiles/translation.json");
|
||||||
|
|
||||||
|
let lyrics: Lyrics =
|
||||||
|
serde_json::from_reader(BufReader::new(File::open(lyrics_path).unwrap())).unwrap();
|
||||||
|
let subtitle: Subtitle =
|
||||||
|
serde_json::from_reader(BufReader::new(File::open(subtitles_path).unwrap())).unwrap();
|
||||||
|
let translations: TranslationList =
|
||||||
|
serde_json::from_reader(BufReader::new(File::open(translation_path).unwrap())).unwrap();
|
||||||
|
|
||||||
|
let t_map = TranslationMap::from(translations);
|
||||||
|
|
||||||
|
let lyrics_trans = t_map.translate_lyrics(&lyrics.lyrics_body);
|
||||||
|
let expected_lyrics = std::fs::read_to_string("testfiles/translated_lyrics.txt").unwrap();
|
||||||
|
assert_eq!(lyrics_trans.trim(), expected_lyrics.trim());
|
||||||
|
|
||||||
|
let subtitles_trans = t_map.translate_subtitles(&subtitle.to_lines().unwrap());
|
||||||
|
|
||||||
|
let expected_lrc = std::fs::read_to_string("testfiles/translated_subtitles.lrc").unwrap();
|
||||||
|
let expected_ttml = std::fs::read_to_string("testfiles/translated_subtitles.xml").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(subtitles_trans.to_lrc().trim(), expected_lrc.trim());
|
||||||
|
assert_eq!(subtitles_trans.to_ttml().trim(), expected_ttml.trim());
|
||||||
|
}
|
Loading…
Reference in a new issue