Compare commits

..

No commits in common. "main" and "musixmatch-cli/v0.3.1" have entirely different histories.

23 changed files with 24 additions and 759 deletions

View file

@ -3,17 +3,6 @@
All notable changes to this project will be documented in this file.
## [v0.4.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-inofficial/v0.3.0..musixmatch-inofficial/v0.4.0) - 2025-12-19
### 🚀 Features
- Add richsync API (word-by-word lyrics) - ([16e4440](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/16e4440a5d31cf8078c1327d8168975576d4db1f))
### 🐛 Bug Fixes
- Mark structs non-exhaustive, add weekly charts, update URLs to new MXM documentation - ([e044cfc](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/e044cfcbd231380e11c14cab31e46b651a942819))
## [v0.3.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-inofficial/v0.2.1..musixmatch-inofficial/v0.3.0) - 2025-12-08
### 🚀 Features

View file

@ -1,6 +1,6 @@
[package]
name = "musixmatch-inofficial"
version = "0.4.0"
version = "0.3.0"
rust-version = "1.70.0"
edition.workspace = true
authors.workspace = true
@ -23,7 +23,7 @@ keywords = ["music", "lyrics"]
categories = ["api-bindings", "multimedia"]
[workspace.dependencies]
musixmatch-inofficial = { version = "0.4.0", path = ".", default-features = false }
musixmatch-inofficial = { version = "0.3.0", path = ".", default-features = false }
[features]
default = ["default-tls"]

View file

@ -27,7 +27,7 @@ commercially.
You will get in trouble if you use this client to create a public lyrics site/app. If
you want to use Musixmatch data for this purpose, you will have to give them money (see
their [commercial plans](https://developer.musixmatch.com/plans)) and use their
[official API](https://docs.musixmatch.com/lyrics-api).
[official API](https://developer.musixmatch.com/documentation).
## Development info

View file

@ -3,17 +3,6 @@
All notable changes to this project will be documented in this file.
## [v0.4.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-cli/v0.3.1..musixmatch-cli/v0.4.0) - 2025-12-19
### 🚀 Features
- Add richsync support to CLI - ([4d1fbab](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/4d1fbabaa8ff3268c83bd3725412e48d81b3f7c1))
### 🐛 Bug Fixes
- Mark structs non-exhaustive, add weekly charts, update URLs to new MXM documentation - ([e044cfc](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/e044cfcbd231380e11c14cab31e46b651a942819))
## [v0.3.1](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-cli/v0.3.0..musixmatch-cli/v0.3.1) - 2025-12-08
### 🐛 Bug Fixes

View file

@ -1,6 +1,6 @@
[package]
name = "musixmatch-cli"
version = "0.4.0"
version = "0.3.1"
rust-version = "1.70.0"
edition.workspace = true
authors.workspace = true

View file

@ -50,17 +50,6 @@ enum Commands {
#[clap(long)]
lang: Option<String>,
},
/// Get richsync (word-by-word synced lyrics)
Richsync {
#[clap(flatten)]
ident: TrackIdentifiers,
/// Track length
#[clap(short, long)]
length: Option<f32>,
/// Maximum deviation from track length (Default: 1s)
#[clap(long)]
max_deviation: Option<f32>,
},
/// Get track metadata
Track {
#[clap(flatten)]
@ -329,34 +318,6 @@ async fn run(cli: Cli) -> Result<()> {
println!("{}", subtitles.subtitle_body);
}
}
Commands::Richsync {
ident,
length,
max_deviation,
} => {
let track_id = get_track_id(ident, &mxm).await?;
let richsync = mxm
.track_richsync(track_id.clone(), length, max_deviation.or(Some(1.0)))
.await?;
eprintln!("Richsync ID: {}", richsync.richsync_id);
eprintln!(
"Language: {}",
richsync.richsync_language.as_deref().unwrap_or(NA_STR)
);
eprintln!("Length: {}", richsync.richsync_length);
eprintln!(
"Copyright: {}",
richsync
.lyrics_copyright
.as_deref()
.map(|s| s.trim())
.unwrap_or(NA_STR)
);
eprintln!();
println!("{}", richsync.richsync_body);
}
Commands::Track { ident } => {
let track = get_track(ident, &mxm, false).await?;
println!("{}", serde_json::to_string_pretty(&track)?)

View file

@ -10,7 +10,7 @@ impl Musixmatch {
/// - `id`: [Album ID](crate::models::AlbumId)
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/album/album-get>
/// <https://developer.musixmatch.com/documentation/api-reference/album-get>
pub async fn album(&self, id: AlbumId<'_>) -> Result<Album> {
let mut url = self.new_url("album.get");
{
@ -35,7 +35,7 @@ impl Musixmatch {
/// - `page`: Define the page number for paginated results, starting from 1.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/artist/artist-albums-get>
/// <https://developer.musixmatch.com/documentation/api-reference/artist-albums-get>
pub async fn artist_albums(
&self,
artist_id: ArtistId<'_>,
@ -65,7 +65,7 @@ impl Musixmatch {
.collect())
}
/// This api provides you the list of the newly released albums of a given country.
/// This api provides you the list of the top albums of a given country.
///
/// # Parameters
/// - `country`: A valid country code (default: "US")

View file

@ -10,7 +10,7 @@ impl Musixmatch {
/// - `id`: [Artist ID](crate::models::ArtistId)
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/artist/artist-get>
/// <https://developer.musixmatch.com/documentation/api-reference/artist-get>
pub async fn artist(&self, id: ArtistId<'_>) -> Result<Artist> {
let mut url = self.new_url("artist.get");
{
@ -34,7 +34,7 @@ impl Musixmatch {
/// - `page`: Define the page number for paginated results, starting from 1.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/artist/artist-search>
/// <https://developer.musixmatch.com/documentation/api-reference/artist-search>
pub async fn artist_search(
&self,
q_artist: &str,
@ -67,7 +67,7 @@ impl Musixmatch {
/// - `page`: Define the page number for paginated results, starting from 1.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/charts/chart-artists-get>
/// <https://developer.musixmatch.com/documentation/api-reference/artist-chart-get>
pub async fn chart_artists(
&self,
country: &str,

View file

@ -12,7 +12,7 @@ impl Musixmatch {
/// - `q_artist`: Artist of the track
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/matcher/matcher-lyrics-get>
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-lyrics-get>
pub async fn matcher_lyrics(&self, q_track: &str, q_artist: &str) -> Result<Lyrics> {
let mut url = self.new_url("matcher.lyrics.get");
{
@ -37,7 +37,7 @@ impl Musixmatch {
/// - `id`: [Track ID](crate::models::TrackId)
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-lyrics-get>
/// <https://developer.musixmatch.com/documentation/api-reference/track-lyrics-get>
pub async fn track_lyrics(&self, id: TrackId<'_>) -> Result<Lyrics> {
let mut url = self.new_url("track.lyrics.get");
{
@ -58,9 +58,9 @@ impl Musixmatch {
/// - `id`: [Track ID](crate::models::TrackId)
/// - `selected_language`: The language of the translated lyrics
/// [(ISO 6391)](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-lyrics-translation-get>
/// None, the [public translation API](https://developer.musixmatch.com/documentation/api-reference/track-lyrics-translation-get)
/// is only available on commercial plans
pub async fn track_lyrics_translation(
&self,
id: TrackId<'_>,

View file

@ -1,7 +1,6 @@
mod album_api;
mod artist_api;
mod lyrics_api;
mod richsync_api;
mod snippet_api;
mod subtitle_api;
mod track_api;

View file

@ -1,45 +0,0 @@
use crate::error::Result;
use crate::models::richsync::{RichsyncBody, RichsyncLyrics};
use crate::models::TrackId;
use crate::Musixmatch;
impl Musixmatch {
/// Get the richsync (word-by-word synchronized lyrics) for a track specified by its ID.
///
/// # Parameters
/// - `id`: [Track ID](crate::models::TrackId)
/// - `f_richsync_length`: Optional richsync length (track duration) in seconds
/// - `f_richsync_length_max_deviation`: Optional maximum amount of seconds the richsync length
/// is allowed to deviate from the given value. The Musixmatch app sets this value to 1,
/// so this should be the recommended value.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-richsync-get>
pub async fn track_richsync(
&self,
id: TrackId<'_>,
f_richsync_length: Option<f32>,
f_richsync_length_max_deviation: Option<f32>,
) -> Result<RichsyncLyrics> {
let mut url = self.new_url("track.richsync.get");
{
let mut url_query = url.query_pairs_mut();
let id_param = id.to_param();
url_query.append_pair(id_param.0, &id_param.1);
if let Some(f_richsync_length) = f_richsync_length {
url_query.append_pair("f_richsync_length", &f_richsync_length.to_string());
}
if let Some(f_richsync_length_max_deviation) = f_richsync_length_max_deviation {
url_query.append_pair(
"f_richsync_length_max_deviation",
&f_richsync_length_max_deviation.to_string(),
);
}
url_query.finish();
}
let richsync_body = self.execute_get_request::<RichsyncBody>(&url).await?;
Ok(richsync_body.richsync)
}
}

View file

@ -14,7 +14,7 @@ impl Musixmatch {
/// - `id`: [Track ID](crate::models::TrackId)
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-snippet-get>
/// <https://developer.musixmatch.com/documentation/api-reference/track-snippet-get>
pub async fn track_snippet(&self, id: TrackId<'_>) -> Result<Snippet> {
let mut url = self.new_url("track.snippet.get");
{

View file

@ -16,7 +16,7 @@ impl Musixmatch {
/// so this should be the recommended value.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/matcher/matcher-subtitle-get>
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-subtitle-get>
pub async fn matcher_subtitle(
&self,
q_track: &str,
@ -65,7 +65,7 @@ impl Musixmatch {
/// so this should be the recommended value.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-subtitle-get>
/// <https://developer.musixmatch.com/documentation/api-reference/track-subtitle-get>
pub async fn track_subtitle(
&self,
id: TrackId<'_>,

View file

@ -18,7 +18,7 @@ impl Musixmatch {
/// instead of [ISO 6391](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/matcher/matcher-track-get>
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-track-get>
pub async fn matcher_track(
&self,
q_track: &str,
@ -77,7 +77,7 @@ impl Musixmatch {
/// instead of [ISO 6391](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-get>
/// <https://developer.musixmatch.com/documentation/api-reference/track-get>
pub async fn track(
&self,
id: TrackId<'_>,
@ -126,7 +126,7 @@ impl Musixmatch {
/// - `page`: Define the page number for paginated results, starting from 1.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/album/album-tracks-get>
/// <https://developer.musixmatch.com/documentation/api-reference/album-tracks-get>
pub async fn album_tracks(
&self,
id: AlbumId<'_>,
@ -166,7 +166,7 @@ impl Musixmatch {
/// - `page`: Define the page number for paginated results, starting from 1.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/charts/chart-tracks-get>
/// <https://developer.musixmatch.com/documentation/api-reference/track-chart-get>
pub async fn chart_tracks(
&self,
country: &str,
@ -202,7 +202,7 @@ impl Musixmatch {
/// Get the list of the music genres the Musixmatch catalogue.
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/charts/music-genres-get>
/// <https://developer.musixmatch.com/documentation/api-reference/music-genres-get>
pub async fn genres(&self) -> Result<Vec<Genre>> {
let url = self.new_url("music.genres.get");
let genres = self.execute_get_request::<Genres>(&url).await?;
@ -225,7 +225,7 @@ impl Musixmatch {
/// ```
///
/// # Reference
/// <https://docs.musixmatch.com/lyrics-api/track/track-search>
/// <https://developer.musixmatch.com/documentation/api-reference/track-search>
pub fn track_search(&self) -> TrackSearchQuery<'_> {
TrackSearchQuery {
mxm: self.clone(),

View file

@ -83,7 +83,7 @@ pub struct Album {
/// Album type
///
/// Source: <https://docs.musixmatch.com/lyrics-api/musixmatch-metadata#album-type-and-release-date>
/// Source: <https://developer.musixmatch.com/documentation/music-meta-data>
#[allow(missing_docs)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AlbumType {

View file

@ -103,7 +103,6 @@ impl SortOrder {
/// Musixmatch fully qualified ID
#[derive(Clone, Copy, PartialEq, Eq)]
#[allow(clippy::exhaustive_structs)]
pub struct Fqid {
/// Numeric Musixmatch ID
pub id: u64,

View file

@ -1,5 +1,4 @@
//! Musixmatch API models
#![warn(clippy::exhaustive_structs)]
pub(crate) mod subtitle;
pub use subtitle::Subtitle;
@ -19,11 +18,6 @@ pub use id::TrackId;
pub(crate) mod lyrics;
pub use lyrics::Lyrics;
pub(crate) mod richsync;
pub use richsync::RichsyncLine;
pub use richsync::RichsyncLyrics;
pub use richsync::RichsyncWord;
pub(crate) mod translation;
pub use translation::Translation;
pub use translation::TranslationList;

View file

@ -1,83 +0,0 @@
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::error::Error;
#[derive(Debug, Deserialize)]
pub(crate) struct RichsyncBody {
pub richsync: RichsyncLyrics,
}
/// Lyrics synced word-by-word
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RichsyncLyrics {
/// Unique Musixmatch richsync ID
pub richsync_id: u64,
/// Richsync JSON data
///
/// List of [`RichsyncLine`]
pub richsync_body: String,
/// Language code (e.g. "en")
///
/// Note that this field has a typo and is called `richssync_language` in the Musixmatch implementation.
#[serde(
default,
deserialize_with = "crate::api_model::null_if_empty",
alias = "richssync_language"
)]
pub richsync_language: Option<String>,
/// Language name (e.g. "English")
#[serde(default, deserialize_with = "crate::api_model::null_if_empty")]
pub richsync_language_description: Option<String>,
/// Duration of the synced track in seconds
pub richsync_length: u32,
/// Copyright text of the lyrics
///
/// Ends with a newline.
///
/// Example:
/// ```text
/// Writer(s): David Hodges
/// 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")]
pub lyrics_copyright: Option<String>,
/// Date and time when the lyrics were last updated
#[serde(with = "time::serde::rfc3339")]
pub updated_time: OffsetDateTime,
}
/// Line of word-by-word synchronized lyrics
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RichsyncLine {
/// Line start timestamp in seconds
pub ts: f32,
/// Line end timestamp in seconds
pub te: f32,
/// Words in the line
#[serde(default)]
pub l: Vec<RichsyncWord>,
/// Text content of the line
pub x: String,
}
/// Single word of word-by-word synchronized lyrics
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RichsyncWord {
/// Word content
pub c: String,
/// Time offset from the line timestamp in seconds
///
/// Position of the word in the track is line.ts + o.
pub o: f32,
}
impl RichsyncLyrics {
/// Get the synchronized lyrics lines
pub fn get_lines(&self) -> Result<Vec<RichsyncLine>, Error> {
serde_json::from_str(&self.richsync_body).map_err(Error::from)
}
}

View file

@ -15,7 +15,6 @@ pub(crate) struct SnippetBody {
/// Example: "There's not a thing that I would change"
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
#[non_exhaustive]
pub struct Snippet {
/// Unique Musixmatch Snippet ID
pub snippet_id: u64,

View file

@ -323,7 +323,6 @@ fn escape_xml(input: &str) -> String {
/// Single subtitle line
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(clippy::exhaustive_structs)]
pub struct SubtitleLine {
/// Subtitle line text
pub text: String,
@ -333,7 +332,6 @@ pub struct SubtitleLine {
/// Position of a subtitle line in the track
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(clippy::exhaustive_structs)]
pub struct SubtitleTime {
/// Minute component of the timestamp
pub minutes: u32,

View file

@ -160,7 +160,6 @@ pub struct TrackLyricsTranslationStatus {
/// Lyrics parts marked with the performer who is singing them
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TrackPerformerTagging {
/// Musixmatch user ID of the user who added the performer tags
///
@ -186,7 +185,6 @@ pub struct TrackPerformerTagging {
/// Performer-tagged lyrics part
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PerformerTaggingPart {
/// Part of the lyrics text
///
@ -202,7 +200,6 @@ pub struct PerformerTaggingPart {
/// Lyrics performer
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Performer {
/// artist / unknown
#[serde(rename = "type")]
@ -222,7 +219,6 @@ pub struct Performer {
/// Artists (and possibly other objects) that are referenced by the tagged parts
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
#[non_exhaustive]
pub struct PerformerTaggingResources {
/// List of artists tagged as performers
pub artists: Vec<Artist>,
@ -230,16 +226,11 @@ pub struct PerformerTaggingResources {
/// Available track charts
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChartName {
/// Editorial chart
Top,
/// Most viewed lyrics in the last 2 hours
Hot,
/// Most viewed lyrics in the last 7 days
MxmWeekly,
/// Most viewed lyrics in the last 7 days, limited to new releases only.
MxmWeeklyNew,
}
impl ChartName {
@ -247,8 +238,6 @@ impl ChartName {
match self {
ChartName::Top => "top",
ChartName::Hot => "hot",
ChartName::MxmWeekly => "mxmweekly",
ChartName::MxmWeeklyNew => "mxmweekly_new",
}
}
}

View file

@ -1,422 +0,0 @@
[
{
"ts": 3.94,
"te": 7.24,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.33
},
{
"c": "birthday",
"o": 0.66
},
{
"c": " ",
"o": 1.8
},
{
"c": "to",
"o": 1.92
},
{
"c": " ",
"o": 2.235
},
{
"c": "you",
"o": 2.55
}
],
"x": "Happy birthday to you"
},
{
"ts": 8.0399,
"te": 11.34,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.33
},
{
"c": "birthday",
"o": 0.66
},
{
"c": " ",
"o": 1.26
},
{
"c": "to",
"o": 1.86
},
{
"c": " ",
"o": 2.205
},
{
"c": "you",
"o": 2.55
}
],
"x": "Happy birthday to you"
},
{
"ts": 11.88,
"te": 15.57,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday,",
"o": 0.63
},
{
"c": " ",
"o": 1.275
},
{
"c": "happy",
"o": 1.92
},
{
"c": " ",
"o": 2.25
},
{
"c": "birthday",
"o": 2.58
}
],
"x": "Happy birthday, happy birthday"
},
{
"ts": 15.74,
"te": 19.01,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday",
"o": 0.63
},
{
"c": " ",
"o": 1.185
},
{
"c": "to",
"o": 1.74
},
{
"c": " ",
"o": 2.145
},
{
"c": "you",
"o": 2.55
}
],
"x": "Happy birthday to you"
},
{
"ts": 19.71,
"te": 23.07,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.344
},
{
"c": "birthday",
"o": 0.689
},
{
"c": " ",
"o": 1.274
},
{
"c": "to",
"o": 1.86
},
{
"c": " ",
"o": 2.22
},
{
"c": "you",
"o": 2.58
}
],
"x": "Happy birthday to you"
},
{
"ts": 23.57,
"te": 26.96,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.33
},
{
"c": "birthday",
"o": 0.66
},
{
"c": " ",
"o": 1.26
},
{
"c": "to",
"o": 1.86
},
{
"c": " ",
"o": 2.235
},
{
"c": "you",
"o": 2.61
}
],
"x": "Happy birthday to you"
},
{
"ts": 27.52,
"te": 31.21,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday,",
"o": 0.63
},
{
"c": " ",
"o": 1.29
},
{
"c": "happy",
"o": 1.95
},
{
"c": " ",
"o": 2.28
},
{
"c": "birthday",
"o": 2.61
}
],
"x": "Happy birthday, happy birthday"
},
{
"ts": 31.42,
"te": 34.42,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday",
"o": 0.63
},
{
"c": " ",
"o": 1.2
},
{
"c": "to",
"o": 1.77
},
{
"c": " ",
"o": 2.175
},
{
"c": "you",
"o": 2.58
}
],
"x": "Happy birthday to you"
},
{
"ts": 37.44,
"te": 40.59,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday",
"o": 0.63
},
{
"c": " ",
"o": 1.74
},
{
"c": "to",
"o": 1.86
},
{
"c": " ",
"o": 2.22
},
{
"c": "you",
"o": 2.58
}
],
"x": "Happy birthday to you"
},
{
"ts": 41.26,
"te": 44.38,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.3
},
{
"c": "birthday",
"o": 0.6
},
{
"c": " ",
"o": 1.2
},
{
"c": "to",
"o": 1.8
},
{
"c": " ",
"o": 2.145
},
{
"c": "you",
"o": 2.49
}
],
"x": "Happy birthday to you"
},
{
"ts": 45.17,
"te": 50.24,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday,",
"o": 0.63
},
{
"c": " ",
"o": 1.275
},
{
"c": "happy",
"o": 1.92
},
{
"c": " ",
"o": 2.264
},
{
"c": "birthday",
"o": 2.61
}
],
"x": "Happy birthday, happy birthday"
},
{
"ts": 50.99,
"te": 54.11,
"l": [
{
"c": "Happy",
"o": 0.0
},
{
"c": " ",
"o": 0.315
},
{
"c": "birthday",
"o": 0.63
},
{
"c": " ",
"o": 1.2
},
{
"c": "to",
"o": 1.77
},
{
"c": " ",
"o": 2.13
},
{
"c": "you",
"o": 2.49
}
],
"x": "Happy birthday to you"
}
]

View file

@ -647,8 +647,6 @@ mod track {
#[rstest]
#[case::top(ChartName::Top)]
#[case::hot(ChartName::Hot)]
#[case::weekly(ChartName::MxmWeekly)]
#[case::weekly_new(ChartName::MxmWeeklyNew)]
#[tokio::test]
async fn charts(#[case] chart_name: ChartName, #[future] mxm: Musixmatch) {
let tracks = mxm
@ -1073,106 +1071,6 @@ mod translation {
}
}
mod richsync {
use std::{fs::File, io::BufWriter};
use super::*;
#[rstest]
#[case::trackid(TrackId::TrackId(205688271))]
#[case::commontrack(TrackId::Commontrack(118480583))]
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba".into()))]
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
#[tokio::test]
async fn from_id(#[case] track_id: TrackId<'_>, #[future] mxm: Musixmatch) {
let richsync = mxm
.await
.track_richsync(track_id, Some(175.0), Some(1.0))
.await
.unwrap();
let lines = richsync.get_lines().unwrap();
assert_eq!(richsync.richsync_id, 8298962);
assert_eq!(richsync.richsync_language.unwrap(), "ko");
assert_eq!(richsync.richsync_language_description.unwrap(), "korean");
let copyright = richsync.lyrics_copyright.unwrap();
assert!(
copyright.contains("Kenneth Scott Chesak"),
"copyright: {copyright}",
);
assert_eq!(richsync.richsync_length, 175);
assert!(richsync.updated_time > datetime!(2022-8-27 0:00 UTC));
assert_eq!(lines.len(), 67);
for line in &lines {
assert!(!line.l.is_empty());
}
}
/// This track has no lyrics
#[rstest]
#[tokio::test]
async fn instrumental(#[future] mxm: Musixmatch) {
let err = mxm
.await
.track_richsync(TrackId::Commontrack(126454494), Some(246.0), Some(1.0))
.await
.unwrap_err();
assert!(matches!(err, Error::NotFound), "got: {err:?}");
}
/// This track has not been synced
#[rstest]
#[tokio::test]
async fn unsynced(#[future] mxm: Musixmatch) {
let err = mxm
.await
.track_richsync(
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1".into()),
Some(213.0),
Some(1.0),
)
.await
.unwrap_err();
assert!(matches!(err, Error::NotFound), "got: {err:?}");
}
/// Try to get subtitles with wrong length parameter
#[rstest]
#[tokio::test]
async fn wrong_length(#[future] mxm: Musixmatch) {
let err = mxm
.await
.track_richsync(TrackId::Commontrack(118480583), Some(200.0), Some(1.0))
.await
.unwrap_err();
assert!(matches!(err, Error::NotFound), "got: {err:?}");
}
#[rstest]
#[tokio::test]
async fn download_testdata(#[future] mxm: Musixmatch) {
let json_path = testfile("richsync.json");
if json_path.exists() {
return;
}
let richsync = mxm
.await
.track_richsync(TrackId::Commontrack(26134491), None, None)
.await
.unwrap();
let lines = richsync.get_lines().unwrap();
let json_file = File::create(json_path).unwrap();
serde_json::to_writer_pretty(BufWriter::new(json_file), &lines).unwrap();
}
}
#[track_caller]
fn assert_imgurl(url: &Option<String>, ends_with: &str) {
assert!(