Compare commits
5 commits
musixmatch
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
c9328afa35 |
|||
|
45c4874a62 |
|||
|
4d1fbabaa8 |
|||
|
e044cfcbd2 |
|||
|
16e4440a5d |
23 changed files with 759 additions and 24 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -3,6 +3,17 @@
|
||||||
All notable changes to this project will be documented in this file.
|
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
|
## [v0.3.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-inofficial/v0.2.1..musixmatch-inofficial/v0.3.0) - 2025-12-08
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "musixmatch-inofficial"
|
name = "musixmatch-inofficial"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
rust-version = "1.70.0"
|
rust-version = "1.70.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
@ -23,7 +23,7 @@ keywords = ["music", "lyrics"]
|
||||||
categories = ["api-bindings", "multimedia"]
|
categories = ["api-bindings", "multimedia"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
musixmatch-inofficial = { version = "0.3.0", path = ".", default-features = false }
|
musixmatch-inofficial = { version = "0.4.0", path = ".", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["default-tls"]
|
default = ["default-tls"]
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ commercially.
|
||||||
You will get in trouble if you use this client to create a public lyrics site/app. If
|
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
|
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
|
their [commercial plans](https://developer.musixmatch.com/plans)) and use their
|
||||||
[official API](https://developer.musixmatch.com/documentation).
|
[official API](https://docs.musixmatch.com/lyrics-api).
|
||||||
|
|
||||||
## Development info
|
## Development info
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,17 @@
|
||||||
All notable changes to this project will be documented in this file.
|
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
|
## [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
|
### 🐛 Bug Fixes
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "musixmatch-cli"
|
name = "musixmatch-cli"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
rust-version = "1.70.0"
|
rust-version = "1.70.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,17 @@ enum Commands {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
lang: Option<String>,
|
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
|
/// Get track metadata
|
||||||
Track {
|
Track {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
|
|
@ -318,6 +329,34 @@ async fn run(cli: Cli) -> Result<()> {
|
||||||
println!("{}", subtitles.subtitle_body);
|
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 } => {
|
Commands::Track { ident } => {
|
||||||
let track = get_track(ident, &mxm, false).await?;
|
let track = get_track(ident, &mxm, false).await?;
|
||||||
println!("{}", serde_json::to_string_pretty(&track)?)
|
println!("{}", serde_json::to_string_pretty(&track)?)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ impl Musixmatch {
|
||||||
/// - `id`: [Album ID](crate::models::AlbumId)
|
/// - `id`: [Album ID](crate::models::AlbumId)
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/album-get>
|
/// <https://docs.musixmatch.com/lyrics-api/album/album-get>
|
||||||
pub async fn album(&self, id: AlbumId<'_>) -> Result<Album> {
|
pub async fn album(&self, id: AlbumId<'_>) -> Result<Album> {
|
||||||
let mut url = self.new_url("album.get");
|
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.
|
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-albums-get>
|
/// <https://docs.musixmatch.com/lyrics-api/artist/artist-albums-get>
|
||||||
pub async fn artist_albums(
|
pub async fn artist_albums(
|
||||||
&self,
|
&self,
|
||||||
artist_id: ArtistId<'_>,
|
artist_id: ArtistId<'_>,
|
||||||
|
|
@ -65,7 +65,7 @@ impl Musixmatch {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This api provides you the list of the top albums of a given country.
|
/// This api provides you the list of the newly released albums of a given country.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - `country`: A valid country code (default: "US")
|
/// - `country`: A valid country code (default: "US")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ impl Musixmatch {
|
||||||
/// - `id`: [Artist ID](crate::models::ArtistId)
|
/// - `id`: [Artist ID](crate::models::ArtistId)
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-get>
|
/// <https://docs.musixmatch.com/lyrics-api/artist/artist-get>
|
||||||
pub async fn artist(&self, id: ArtistId<'_>) -> Result<Artist> {
|
pub async fn artist(&self, id: ArtistId<'_>) -> Result<Artist> {
|
||||||
let mut url = self.new_url("artist.get");
|
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.
|
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-search>
|
/// <https://docs.musixmatch.com/lyrics-api/artist/artist-search>
|
||||||
pub async fn artist_search(
|
pub async fn artist_search(
|
||||||
&self,
|
&self,
|
||||||
q_artist: &str,
|
q_artist: &str,
|
||||||
|
|
@ -67,7 +67,7 @@ impl Musixmatch {
|
||||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-chart-get>
|
/// <https://docs.musixmatch.com/lyrics-api/charts/chart-artists-get>
|
||||||
pub async fn chart_artists(
|
pub async fn chart_artists(
|
||||||
&self,
|
&self,
|
||||||
country: &str,
|
country: &str,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl Musixmatch {
|
||||||
/// - `q_artist`: Artist of the track
|
/// - `q_artist`: Artist of the track
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-lyrics-get>
|
/// <https://docs.musixmatch.com/lyrics-api/matcher/matcher-lyrics-get>
|
||||||
pub async fn matcher_lyrics(&self, q_track: &str, q_artist: &str) -> Result<Lyrics> {
|
pub async fn matcher_lyrics(&self, q_track: &str, q_artist: &str) -> Result<Lyrics> {
|
||||||
let mut url = self.new_url("matcher.lyrics.get");
|
let mut url = self.new_url("matcher.lyrics.get");
|
||||||
{
|
{
|
||||||
|
|
@ -37,7 +37,7 @@ impl Musixmatch {
|
||||||
/// - `id`: [Track ID](crate::models::TrackId)
|
/// - `id`: [Track ID](crate::models::TrackId)
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-lyrics-get>
|
/// <https://docs.musixmatch.com/lyrics-api/track/track-lyrics-get>
|
||||||
pub async fn track_lyrics(&self, id: TrackId<'_>) -> Result<Lyrics> {
|
pub async fn track_lyrics(&self, id: TrackId<'_>) -> Result<Lyrics> {
|
||||||
let mut url = self.new_url("track.lyrics.get");
|
let mut url = self.new_url("track.lyrics.get");
|
||||||
{
|
{
|
||||||
|
|
@ -58,9 +58,9 @@ impl Musixmatch {
|
||||||
/// - `id`: [Track ID](crate::models::TrackId)
|
/// - `id`: [Track ID](crate::models::TrackId)
|
||||||
/// - `selected_language`: The language of the translated lyrics
|
/// - `selected_language`: The language of the translated lyrics
|
||||||
/// [(ISO 639‑1)](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
/// [(ISO 639‑1)](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||||
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// None, the [public translation API](https://developer.musixmatch.com/documentation/api-reference/track-lyrics-translation-get)
|
/// <https://docs.musixmatch.com/lyrics-api/track/track-lyrics-translation-get>
|
||||||
/// is only available on commercial plans
|
|
||||||
pub async fn track_lyrics_translation(
|
pub async fn track_lyrics_translation(
|
||||||
&self,
|
&self,
|
||||||
id: TrackId<'_>,
|
id: TrackId<'_>,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod album_api;
|
mod album_api;
|
||||||
mod artist_api;
|
mod artist_api;
|
||||||
mod lyrics_api;
|
mod lyrics_api;
|
||||||
|
mod richsync_api;
|
||||||
mod snippet_api;
|
mod snippet_api;
|
||||||
mod subtitle_api;
|
mod subtitle_api;
|
||||||
mod track_api;
|
mod track_api;
|
||||||
|
|
|
||||||
45
src/apis/richsync_api.rs
Normal file
45
src/apis/richsync_api.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ impl Musixmatch {
|
||||||
/// - `id`: [Track ID](crate::models::TrackId)
|
/// - `id`: [Track ID](crate::models::TrackId)
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-snippet-get>
|
/// <https://docs.musixmatch.com/lyrics-api/track/track-snippet-get>
|
||||||
pub async fn track_snippet(&self, id: TrackId<'_>) -> Result<Snippet> {
|
pub async fn track_snippet(&self, id: TrackId<'_>) -> Result<Snippet> {
|
||||||
let mut url = self.new_url("track.snippet.get");
|
let mut url = self.new_url("track.snippet.get");
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ impl Musixmatch {
|
||||||
/// so this should be the recommended value.
|
/// so this should be the recommended value.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-subtitle-get>
|
/// <https://docs.musixmatch.com/lyrics-api/matcher/matcher-subtitle-get>
|
||||||
pub async fn matcher_subtitle(
|
pub async fn matcher_subtitle(
|
||||||
&self,
|
&self,
|
||||||
q_track: &str,
|
q_track: &str,
|
||||||
|
|
@ -65,7 +65,7 @@ impl Musixmatch {
|
||||||
/// so this should be the recommended value.
|
/// so this should be the recommended value.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-subtitle-get>
|
/// <https://docs.musixmatch.com/lyrics-api/track/track-subtitle-get>
|
||||||
pub async fn track_subtitle(
|
pub async fn track_subtitle(
|
||||||
&self,
|
&self,
|
||||||
id: TrackId<'_>,
|
id: TrackId<'_>,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ impl Musixmatch {
|
||||||
/// instead of [ISO 639‑1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
|
/// instead of [ISO 639‑1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-track-get>
|
/// <https://docs.musixmatch.com/lyrics-api/matcher/matcher-track-get>
|
||||||
pub async fn matcher_track(
|
pub async fn matcher_track(
|
||||||
&self,
|
&self,
|
||||||
q_track: &str,
|
q_track: &str,
|
||||||
|
|
@ -77,7 +77,7 @@ impl Musixmatch {
|
||||||
/// instead of [ISO 639‑1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
|
/// instead of [ISO 639‑1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-get>
|
/// <https://docs.musixmatch.com/lyrics-api/track/track-get>
|
||||||
pub async fn track(
|
pub async fn track(
|
||||||
&self,
|
&self,
|
||||||
id: TrackId<'_>,
|
id: TrackId<'_>,
|
||||||
|
|
@ -126,7 +126,7 @@ impl Musixmatch {
|
||||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/album-tracks-get>
|
/// <https://docs.musixmatch.com/lyrics-api/album/album-tracks-get>
|
||||||
pub async fn album_tracks(
|
pub async fn album_tracks(
|
||||||
&self,
|
&self,
|
||||||
id: AlbumId<'_>,
|
id: AlbumId<'_>,
|
||||||
|
|
@ -166,7 +166,7 @@ impl Musixmatch {
|
||||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-chart-get>
|
/// <https://docs.musixmatch.com/lyrics-api/charts/chart-tracks-get>
|
||||||
pub async fn chart_tracks(
|
pub async fn chart_tracks(
|
||||||
&self,
|
&self,
|
||||||
country: &str,
|
country: &str,
|
||||||
|
|
@ -202,7 +202,7 @@ impl Musixmatch {
|
||||||
/// Get the list of the music genres the Musixmatch catalogue.
|
/// Get the list of the music genres the Musixmatch catalogue.
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/music-genres-get>
|
/// <https://docs.musixmatch.com/lyrics-api/charts/music-genres-get>
|
||||||
pub async fn genres(&self) -> Result<Vec<Genre>> {
|
pub async fn genres(&self) -> Result<Vec<Genre>> {
|
||||||
let url = self.new_url("music.genres.get");
|
let url = self.new_url("music.genres.get");
|
||||||
let genres = self.execute_get_request::<Genres>(&url).await?;
|
let genres = self.execute_get_request::<Genres>(&url).await?;
|
||||||
|
|
@ -225,7 +225,7 @@ impl Musixmatch {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Reference
|
/// # Reference
|
||||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-search>
|
/// <https://docs.musixmatch.com/lyrics-api/track/track-search>
|
||||||
pub fn track_search(&self) -> TrackSearchQuery<'_> {
|
pub fn track_search(&self) -> TrackSearchQuery<'_> {
|
||||||
TrackSearchQuery {
|
TrackSearchQuery {
|
||||||
mxm: self.clone(),
|
mxm: self.clone(),
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ pub struct Album {
|
||||||
|
|
||||||
/// Album type
|
/// Album type
|
||||||
///
|
///
|
||||||
/// Source: <https://developer.musixmatch.com/documentation/music-meta-data>
|
/// Source: <https://docs.musixmatch.com/lyrics-api/musixmatch-metadata#album-type-and-release-date>
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum AlbumType {
|
pub enum AlbumType {
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ impl SortOrder {
|
||||||
|
|
||||||
/// Musixmatch fully qualified ID
|
/// Musixmatch fully qualified ID
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
pub struct Fqid {
|
pub struct Fqid {
|
||||||
/// Numeric Musixmatch ID
|
/// Numeric Musixmatch ID
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//! Musixmatch API models
|
//! Musixmatch API models
|
||||||
|
#![warn(clippy::exhaustive_structs)]
|
||||||
|
|
||||||
pub(crate) mod subtitle;
|
pub(crate) mod subtitle;
|
||||||
pub use subtitle::Subtitle;
|
pub use subtitle::Subtitle;
|
||||||
|
|
@ -18,6 +19,11 @@ pub use id::TrackId;
|
||||||
pub(crate) mod lyrics;
|
pub(crate) mod lyrics;
|
||||||
pub use lyrics::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(crate) mod translation;
|
||||||
pub use translation::Translation;
|
pub use translation::Translation;
|
||||||
pub use translation::TranslationList;
|
pub use translation::TranslationList;
|
||||||
|
|
|
||||||
83
src/models/richsync.rs
Normal file
83
src/models/richsync.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ pub(crate) struct SnippetBody {
|
||||||
/// Example: "There's not a thing that I would change"
|
/// Example: "There's not a thing that I would change"
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct Snippet {
|
pub struct Snippet {
|
||||||
/// Unique Musixmatch Snippet ID
|
/// Unique Musixmatch Snippet ID
|
||||||
pub snippet_id: u64,
|
pub snippet_id: u64,
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,7 @@ fn escape_xml(input: &str) -> String {
|
||||||
|
|
||||||
/// Single subtitle line
|
/// Single subtitle line
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
pub struct SubtitleLine {
|
pub struct SubtitleLine {
|
||||||
/// Subtitle line text
|
/// Subtitle line text
|
||||||
pub text: String,
|
pub text: String,
|
||||||
|
|
@ -332,6 +333,7 @@ pub struct SubtitleLine {
|
||||||
|
|
||||||
/// Position of a subtitle line in the track
|
/// Position of a subtitle line in the track
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
pub struct SubtitleTime {
|
pub struct SubtitleTime {
|
||||||
/// Minute component of the timestamp
|
/// Minute component of the timestamp
|
||||||
pub minutes: u32,
|
pub minutes: u32,
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ pub struct TrackLyricsTranslationStatus {
|
||||||
|
|
||||||
/// Lyrics parts marked with the performer who is singing them
|
/// Lyrics parts marked with the performer who is singing them
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct TrackPerformerTagging {
|
pub struct TrackPerformerTagging {
|
||||||
/// Musixmatch user ID of the user who added the performer tags
|
/// Musixmatch user ID of the user who added the performer tags
|
||||||
///
|
///
|
||||||
|
|
@ -185,6 +186,7 @@ pub struct TrackPerformerTagging {
|
||||||
|
|
||||||
/// Performer-tagged lyrics part
|
/// Performer-tagged lyrics part
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct PerformerTaggingPart {
|
pub struct PerformerTaggingPart {
|
||||||
/// Part of the lyrics text
|
/// Part of the lyrics text
|
||||||
///
|
///
|
||||||
|
|
@ -200,6 +202,7 @@ pub struct PerformerTaggingPart {
|
||||||
|
|
||||||
/// Lyrics performer
|
/// Lyrics performer
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct Performer {
|
pub struct Performer {
|
||||||
/// artist / unknown
|
/// artist / unknown
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
|
|
@ -219,6 +222,7 @@ pub struct Performer {
|
||||||
/// Artists (and possibly other objects) that are referenced by the tagged parts
|
/// Artists (and possibly other objects) that are referenced by the tagged parts
|
||||||
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct PerformerTaggingResources {
|
pub struct PerformerTaggingResources {
|
||||||
/// List of artists tagged as performers
|
/// List of artists tagged as performers
|
||||||
pub artists: Vec<Artist>,
|
pub artists: Vec<Artist>,
|
||||||
|
|
@ -226,11 +230,16 @@ pub struct PerformerTaggingResources {
|
||||||
|
|
||||||
/// Available track charts
|
/// Available track charts
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum ChartName {
|
pub enum ChartName {
|
||||||
/// Editorial chart
|
/// Editorial chart
|
||||||
Top,
|
Top,
|
||||||
/// Most viewed lyrics in the last 2 hours
|
/// Most viewed lyrics in the last 2 hours
|
||||||
Hot,
|
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 {
|
impl ChartName {
|
||||||
|
|
@ -238,6 +247,8 @@ impl ChartName {
|
||||||
match self {
|
match self {
|
||||||
ChartName::Top => "top",
|
ChartName::Top => "top",
|
||||||
ChartName::Hot => "hot",
|
ChartName::Hot => "hot",
|
||||||
|
ChartName::MxmWeekly => "mxmweekly",
|
||||||
|
ChartName::MxmWeeklyNew => "mxmweekly_new",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
422
testfiles/richsync.json
Normal file
422
testfiles/richsync.json
Normal file
|
|
@ -0,0 +1,422 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
102
tests/tests.rs
102
tests/tests.rs
|
|
@ -647,6 +647,8 @@ mod track {
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::top(ChartName::Top)]
|
#[case::top(ChartName::Top)]
|
||||||
#[case::hot(ChartName::Hot)]
|
#[case::hot(ChartName::Hot)]
|
||||||
|
#[case::weekly(ChartName::MxmWeekly)]
|
||||||
|
#[case::weekly_new(ChartName::MxmWeeklyNew)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn charts(#[case] chart_name: ChartName, #[future] mxm: Musixmatch) {
|
async fn charts(#[case] chart_name: ChartName, #[future] mxm: Musixmatch) {
|
||||||
let tracks = mxm
|
let tracks = mxm
|
||||||
|
|
@ -1071,6 +1073,106 @@ 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]
|
#[track_caller]
|
||||||
fn assert_imgurl(url: &Option<String>, ends_with: &str) {
|
fn assert_imgurl(url: &Option<String>, ends_with: &str) {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue