Compare commits

..

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

15 changed files with 363 additions and 669 deletions

View file

@ -4,14 +4,6 @@ on: [push, pull_request]
jobs:
Test:
runs-on: cimaster-latest
services:
warpproxy:
image: thetadev256/warpproxy
env:
WARP_DEVICE_ID: ${{ secrets.WARP_DEVICE_ID }}
WARP_ACCESS_TOKEN: ${{ secrets.WARP_ACCESS_TOKEN }}
WARP_LICENSE_KEY: ${{ secrets.WARP_LICENSE_KEY }}
WARP_PRIVATE_KEY: ${{ secrets.WARP_PRIVATE_KEY }}
steps:
- name: 📦 Checkout repository
uses: actions/checkout@v3
@ -25,5 +17,3 @@ jobs:
- name: 🧪 Test
run: cargo test --workspace
env:
ALL_PROXY: "http://warpproxy:8124"

View file

@ -23,8 +23,13 @@ jobs:
echo END_OF_FILE
} >> "$GITHUB_ENV"
- name: 📤 Publish crate on crates.io
run: cargo publish --token ${{ secrets.CARGO_TOKEN }} --package "${{ env.CRATE }}"
- name: 📤 Publish crate on code.thetadev.de
run: |
mkdir -p ~/.cargo
printf '[registries.thetadev]\nindex = "https://code.thetadev.de/ThetaDev/_cargo-index.git"\ntoken = "Bearer ${{ secrets.TOKEN_GITEA }}"\n' >> ~/.cargo/config.toml
sed -i "s/^musixmatch-.*=\s*{/\0 registry = \"thetadev\",/g" Cargo.toml
cargo publish --registry thetadev --package "${{ env.CRATE }}" --allow-dirty
git restore Cargo.toml
- name: 🎉 Publish release
uses: https://gitea.com/actions/release-action@main

View file

@ -1,63 +0,0 @@
name: renovate
on:
push:
branches: ["main"]
paths:
- ".forgejo/workflows/renovate.yaml"
- "renovate.json"
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
env:
RENOVATE_REPOSITORIES: ${{ github.repository }}
jobs:
renovate:
runs-on: docker
container:
image: renovate/renovate:latest
steps:
- name: Load renovate repo cache
uses: actions/cache/restore@v4
with:
path: |
.tmp/cache/renovate/repository
.tmp/cache/renovate/renovate-cache-sqlite
.tmp/osv
key: repo-cache-${{ github.run_id }}
restore-keys: |
repo-cache-
- name: Run renovate
run: renovate
env:
LOG_LEVEL: debug
RENOVATE_BASE_DIR: ${{ github.workspace }}/.tmp
RENOVATE_ENDPOINT: ${{ github.server_url }}
RENOVATE_PLATFORM: gitea
RENOVATE_REPOSITORY_CACHE: 'enabled'
RENOVATE_TOKEN: ${{ secrets.FORGEJO_CI_BOT_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_TOKEN }}
RENOVATE_GIT_AUTHOR: 'Renovate Bot <forgejo-renovate-action@forgejo.org>'
RENOVATE_X_SQLITE_PACKAGE_CACHE: true
GIT_AUTHOR_NAME: 'Renovate Bot'
GIT_AUTHOR_EMAIL: 'forgejo-renovate-action@forgejo.org'
GIT_COMMITTER_NAME: 'Renovate Bot'
GIT_COMMITTER_EMAIL: 'forgejo-renovate-action@forgejo.org'
OSV_OFFLINE_ROOT_DIR: ${{ github.workspace }}/.tmp/osv
- name: Save renovate repo cache
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: actions/cache/save@v4
with:
path: |
.tmp/cache/renovate/repository
.tmp/cache/renovate/renovate-cache-sqlite
.tmp/osv
key: repo-cache-${{ github.run_id }}

View file

@ -2,31 +2,7 @@
All notable changes to this project will be documented in this file.
## [v0.1.1](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-inofficial/v0.1.0..musixmatch-inofficial/v0.1.1) - 2024-08-18
### 🚀 Features
- Add msrv - ([a95f3fc](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/a95f3fcf478f1acda9fad12741604b6793e128c1))
### 📚 Documentation
- Update readme - ([348e9c5](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/348e9c5427e59c488d7e2f7cef9e7006a12864f2))
### 🧪 Testing
- Fix tests - ([d2a7aed](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/d2a7aed917bfcec75ce00bb49d380fbc31c47384))
- Fix tests - ([c120583](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/c120583bf861cc74fbce686b2bd88bc575270130))
- Fix tests - ([c9fea76](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/c9fea762ec97a1c594e60a3b1cbc72bb786d0957))
- Add rate limiter - ([3b69b36](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/3b69b36ae6c945d786534e0eaa353fb737b1fb54))
### ⚙️ Miscellaneous Tasks
- Update justfile - ([1bc5ae4](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/1bc5ae408343e6755e390909e7017647efcf59a1))
- Update dependencies - ([dcc25bf](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/dcc25bff202becdec7101c5ce1825cd75e445f99))
- Change repo to codeberg - ([30e2afd](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/30e2afd3679d2c17a49afd523c8b8bad70f291e5))
## [v0.1.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/commits/tag/musixmatch-inofficial/v0.1.0) - 2024-03-23
## v0.1.0 - 2024-03-23
Initial release

View file

@ -1,7 +1,6 @@
[package]
name = "musixmatch-inofficial"
version = "0.1.1"
rust-version = "1.70.0"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
@ -9,21 +8,22 @@ repository.workspace = true
keywords.workspace = true
description = "Inofficial client for the Musixmatch API"
include = ["/src", "README.md", "CHANGELOG.md", "LICENSE"]
include = ["/src", "README.md", "LICENSE"]
[workspace]
members = [".", "cli"]
[workspace.package]
edition = "2021"
authors = ["ThetaDev <thetadev@magenta.de>"]
authors = ["ThetaDev <t.testboy@gmail.com>"]
license = "MIT"
repository = "https://codeberg.org/ThetaDev/musixmatch-inofficial"
repository = "https://code.thetadev.de/ThetaDev/musixmatch-inofficial"
keywords = ["music", "lyrics"]
categories = ["api-bindings", "multimedia"]
[workspace.dependencies]
musixmatch-inofficial = { version = "0.1.1", path = ".", default-features = false }
musixmatch-inofficial = { version = "0.1.0", path = ".", default-features = false }
[features]
default = ["default-tls"]
@ -41,26 +41,30 @@ reqwest = { version = "0.12.0", default-features = false, features = [
"json",
"gzip",
] }
tokio = { version = "1.20.4" }
tokio = { version = "1.20.0" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85"
thiserror = "1.0.0"
thiserror = "1.0.36"
log = "0.4.17"
time = { version = "0.3.10", features = [
time = { version = "0.3.15", features = [
"macros",
"formatting",
"serde",
"serde-well-known",
] }
hmac = "0.12.0"
sha1 = "0.10.0"
rand = "0.8.0"
hmac = "0.12.1"
sha1 = "0.10.5"
rand = "0.8.5"
base64 = "0.22.0"
[dev-dependencies]
rstest = { version = "0.22.0", default-features = false }
ctor = "0.2.0"
rstest = { version = "0.18.0", default-features = false }
env_logger = "0.11.0"
dotenvy = "0.15.5"
tokio = { version = "1.20.4", features = ["macros"] }
tokio = { version = "1.20.0", features = ["macros"] }
futures = "0.3.21"
path_macro = "1.0.0"
governor = "0.6.3"
[profile.release]
strip = true

View file

@ -10,12 +10,12 @@ release crate="musixmatch-inofficial":
CHANGELOG="CHANGELOG.md"
if [ "$CRATE" = "musixmatch-inofficial" ]; then
INCLUDES="$INCLUDES --include-path 'src/**' --include-path 'tests/**' --include-path 'testfiles/**'"
INCLUDES="$INCLUDES --include-path src/** --include-path tests/** --include-path testfiles/**"
else
if [ ! -d "$CRATE" ]; then
echo "$CRATE does not exist."; exit 1
fi
INCLUDES="$INCLUDES --include-path '$CRATE/**'"
INCLUDES="$INCLUDES --include-path $CRATE/**"
CHANGELOG="$CRATE/$CHANGELOG"
CRATE="musixmatch-$CRATE" # Add crate name prefix
fi
@ -26,17 +26,17 @@ release crate="musixmatch-inofficial":
if git rev-parse "$TAG" >/dev/null 2>&1; then echo "version tag $TAG already exists"; exit 1; fi
CLIFF_ARGS="--tag '${TAG}' --tag-pattern '${CRATE}/v*' --unreleased $INCLUDES"
CLIFF_ARGS="--tag v${VERSION} --tag-pattern ${CRATE}/* --unreleased $INCLUDES"
echo "git-cliff $CLIFF_ARGS"
if [ -f "$CHANGELOG" ]; then
eval "git-cliff $CLIFF_ARGS --prepend '$CHANGELOG'"
git-cliff $CLIFF_ARGS --prepend "$CHANGELOG"
else
eval "git-cliff $CLIFF_ARGS --output '$CHANGELOG'"
git-cliff $CLIFF_ARGS --output "$CHANGELOG"
fi
editor "$CHANGELOG"
git add .
git add "$CHANGELOG"
git commit -m "chore(release): release $CRATE v$VERSION"
awk 'BEGIN{RS="(^|\n)## [^\n]+\n*"} NR==2 { print }' "$CHANGELOG" | git tag -as -F - --cleanup whitespace "$TAG"

View file

@ -1,8 +1,4 @@
# musixmatch-inofficial
[![Current crates.io version](https://img.shields.io/crates/v/musixmatch-inofficial.svg)](https://crates.io/crates/musixmatch-inofficial)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
[![CI status](https://codeberg.org/ThetaDev/musixmatch-inofficial/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://codeberg.org/ThetaDev/musixmatch-inofficial/actions/?workflow=ci.yaml)
# Musixmatch-Inofficial
This is an inofficial client for the Musixmatch API that uses the key embedded in the
Musixmatch Android app.
@ -11,10 +7,10 @@ It allows you to obtain synchronized lyrics in different formats
([LRC](<https://en.wikipedia.org/wiki/LRC_(file_format)>),
[DFXP](https://www.w3.org/TR/ttml1/), JSON) for almost any song.
The Musixmatch API used to require a free account on <https://www.musixmatch.com> to be
used. However, as of 2024, this requirement was removed and the API can be used
anonymously. The client still allows you to supply credentials if Musixmatch decides to
close the API down again.
The Musixmatch API required a free account on <https://www.musixmatch.com> to be used.
However, as of 2024, this requirement was removed and the API can be used anonymously.
The client still allows you to supply credentials if Musixmatch decided to close the API
down again.
## ⚠️ Copyright disclaimer

View file

@ -2,37 +2,7 @@
All notable changes to this project will be documented in this file.
## [v0.2.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/compare/musixmatch-cli/v0.1.0..musixmatch-cli/v0.2.0) - 2024-08-18
### 🚀 Features
- Add format option to mp3 subtitles cmd - ([19e209e](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/19e209e34f4d129a4223930bfd41e1ccf117f231))
- Add get album, get artist, search artist - ([c4bfbe5](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/c4bfbe563a00d399b3645dd68f03c1215ee51fdb))
- [**breaking**] Remove MP3 feature, refactor cmd structure - ([54235e6](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/54235e6fb61084823a6583aaa7d59b1799deb07f))
- Add msrv - ([a95f3fc](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/a95f3fcf478f1acda9fad12741604b6793e128c1))
### 🐛 Bug Fixes
- Use native TLS for CLI - ([dc1bea1](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/dc1bea13cc2a37eae7f3727dc72f865a01430a2e))
### 📚 Documentation
- Update readme - ([348e9c5](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/348e9c5427e59c488d7e2f7cef9e7006a12864f2))
### 🧪 Testing
- Fix tests - ([d2a7aed](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/d2a7aed917bfcec75ce00bb49d380fbc31c47384))
- Add rate limiter - ([3b69b36](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/3b69b36ae6c945d786534e0eaa353fb737b1fb54))
### ⚙️ Miscellaneous Tasks
- Fix changelogs - ([e72d2b4](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/e72d2b4363a3a9a48dec8f2be9389f6cc239035c))
- Update justfile - ([1bc5ae4](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/1bc5ae408343e6755e390909e7017647efcf59a1))
- Update dependencies - ([dcc25bf](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/dcc25bff202becdec7101c5ce1825cd75e445f99))
- Change repo to codeberg - ([30e2afd](https://codeberg.org/ThetaDev/musixmatch-inofficial/commit/30e2afd3679d2c17a49afd523c8b8bad70f291e5))
## [v0.1.0](https://codeberg.org/ThetaDev/musixmatch-inofficial/commits/tag/musixmatch-cli/v0.1.0) - 2024-03-23
## v0.1.0 - 2024-03-23
Initial release

View file

@ -1,7 +1,6 @@
[package]
name = "musixmatch-cli"
version = "0.2.0"
rust-version = "1.70.0"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
@ -10,7 +9,7 @@ keywords.workspace = true
description = "Inofficial command line interface for the Musixmatch API"
[features]
default = ["native-tls"]
default = ["rustls-tls-native-roots"]
# Reqwest TLS options
native-tls = ["musixmatch-inofficial/native-tls"]
@ -21,9 +20,11 @@ rustls-tls-native-roots = ["musixmatch-inofficial/rustls-tls-native-roots"]
[dependencies]
musixmatch-inofficial.workspace = true
tokio = { version = "1.20.4", features = ["macros", "rt-multi-thread"] }
clap = { version = "4.0.0", features = ["derive"] }
anyhow = "1.0.0"
tokio = { version = "1.20.0", features = ["macros", "rt-multi-thread"] }
id3 = "1.3.0"
mp3-duration = "0.1.10"
clap = { version = "4.0.10", features = ["derive"] }
anyhow = "1.0.65"
rpassword = "7.0.0"
dirs = "5.0.0"
serde_json = "1.0.85"
serde_json = "1.0.91"

View file

@ -1,77 +0,0 @@
# musixmatch-cli
[![Current crates.io version](https://img.shields.io/crates/v/musixmatch-cli.svg)](https://crates.io/crates/musixmatch-cli)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
[![CI status](https://codeberg.org/ThetaDev/musixmatch-inofficial/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://codeberg.org/ThetaDev/musixmatch-inofficial/actions/?workflow=ci.yaml)
The Musixmatch CLI allows you to fetch lyrics, subtitles and track metadata from the
command line using the Musixmatch API.
The Musixmatch API used to require a free account on <https://www.musixmatch.com> to be
used. However, as of 2024, this requirement was removed and the API can be used
anonymously. The CLI still allows you to supply credentials if Musixmatch decides to
close the API down again.
### Get lyrics
```txt
musixmatch-cli lyrics -n shine -a spektrem
Lyrics ID: 34583240
Language: en
Copyright: Writer(s): Jesse Warren
Copyright: Ncs Music
Eyes in the sky gazing far into the night
I raise my hand to the fire, but it's no use
'Cause you can't stop it from shining through
It's true
...
```
### Get translated lyrics
Musixmatch also offers translated lyrics. You have to select a language using the
`--lang` flag. You can also set the `--bi` flag to output both the original and
translated lines.
```txt
musixmatch-cli lyrics -n shine -a spektrem --lang de --bi
Lyrics ID: 34583240
Language: en
Copyright: Writer(s): Jesse Warren
Copyright: Ncs Music
Translated to: de
Eyes in the sky gazing far into the night
> Augen starren in die weite Nacht
I raise my hand to the fire, but it's no use
> Ich hebe meine Hand in das Feuer, doch ihr geschieht nichts
'Cause you can't stop it from shining through
> Denn du kannst es nicht daran hindern, hindurch zu scheinen
It's true
> Es ist wahr
...
```
### Get subtitles (synchronized lyrics)
For most lyrics Musixmatch provides timestamps for the individual lines so you can
display them in sync during playback.
Musixmatch offers multiple subtitle formats you can select using the `--format` flag.
The available formats are: `lrc`, `ttml`, `ttml-structured`, `json`, `ebu-stl`
```txt
musixmatch-cli subtitles -n shine -a spektrem
Subtitle ID: 35340319
Language: en
Length: 316
Copyright: Writer(s): Jesse Warren
Copyright: Ncs Music
[00:59.84] Eyes in the sky gazing far into the night
[01:06.55] I raise my hand to the fire, but it's no use
[01:11.97] 'Cause you can't stop it from shining through
[01:16.07] It's true
...
```

View file

@ -1,6 +1,3 @@
#![doc = include_str!("../README.md")]
#![warn(missing_docs, clippy::todo)]
use std::{
io::{stdin, stdout, Write},
path::PathBuf,
@ -8,8 +5,9 @@ use std::{
use anyhow::{anyhow, bail, Result};
use clap::{Args, Parser, Subcommand};
use id3::{Tag, TagLike};
use musixmatch_inofficial::{
models::{AlbumId, ArtistId, SubtitleFormat, Track, TrackId, TranslationMap},
models::{SubtitleFormat, Track, TrackId, TranslationMap},
Musixmatch,
};
@ -22,50 +20,14 @@ struct Cli {
#[derive(Subcommand)]
enum Commands {
/// Get lyrics text
Lyrics {
#[clap(flatten)]
ident: TrackIdentifiers,
/// Language
#[clap(long)]
lang: Option<String>,
/// Bilingual
#[clap(long)]
bi: bool,
Get {
#[command(subcommand)]
command: GetCommands,
},
/// Get subtitles (time-synced lyrics)
Subtitles {
#[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>,
/// Subtitle format
#[clap(short, long, default_value = "lrc")]
format: SubtitleFormatClap,
/// Language
#[clap(long)]
lang: Option<String>,
Mp3 {
#[command(subcommand)]
command: FileCommands,
},
/// Get track metadata
Track {
#[clap(flatten)]
ident: TrackIdentifiers,
},
/// Get album metadata
Album {
#[clap(flatten)]
ident: AlbumArtistIdentifiers,
},
/// Get artist metadata
Artist {
#[clap(flatten)]
ident: AlbumArtistIdentifiers,
},
/// Search for Musixmatch tracks
#[group(required = true)]
Search {
/// Track name
@ -80,8 +42,40 @@ enum Commands {
/// Search query
query: Option<Vec<String>>,
},
/// Search for Musixmatch artists
SearchArtist { query: Vec<String> },
}
#[derive(Subcommand)]
enum GetCommands {
Lyrics {
#[clap(flatten)]
ident: TrackIdentifiers,
/// Language
#[clap(long)]
lang: Option<String>,
/// Bilingual
#[clap(long)]
bi: bool,
},
Subtitles {
#[clap(flatten)]
ident: TrackIdentifiers,
/// Track length
#[clap(long, short)]
length: Option<f32>,
/// Maximum deviation from track length (Default: 1s)
#[clap(long)]
max_deviation: Option<f32>,
/// Subtitle format
#[clap(long, default_value = "lrc")]
format: SubtitleFormatClap,
/// Language
#[clap(long)]
lang: Option<String>,
},
Track {
#[clap(flatten)]
ident: TrackIdentifiers,
},
}
#[derive(Args)]
@ -112,38 +106,22 @@ struct TrackIdentifiers {
isrc: Option<String>,
}
#[derive(Args)]
#[group(multiple = false)]
struct AlbumArtistIdentifiers {
/// Musixmatch-ID
#[clap(long)]
mxm_id: Option<u64>,
/// Musicbrainz-ID
#[clap(long)]
musicbrainz: Option<String>,
}
#[derive(Subcommand)]
enum FileCommands {
/// Get lyrics text
Lyrics {
/// Music file
#[clap(value_parser)]
file: PathBuf,
},
/// Get subtitles (time-synced lyrics)
Subtitles {
/// Music file
#[clap(value_parser)]
file: PathBuf,
/// Subtitle format
#[clap(short, long, default_value = "lrc")]
format: SubtitleFormatClap,
},
}
#[derive(clap::ValueEnum, Debug, Copy, Clone)]
enum SubtitleFormatClap {
pub enum SubtitleFormatClap {
Lrc,
Ttml,
TtmlStructured,
@ -197,148 +175,164 @@ async fn run(cli: Cli) -> Result<()> {
};
match cli.command {
Commands::Lyrics { ident, lang, bi } => {
let track_id = get_track_id(ident, &mxm).await?;
let lyrics = mxm.track_lyrics(track_id.clone()).await?;
Commands::Get { command } => match command {
GetCommands::Lyrics { ident, lang, bi } => {
let track_id = get_track_id(ident, &mxm).await?;
let lyrics = mxm.track_lyrics(track_id.clone()).await?;
eprintln!("Lyrics ID: {}", lyrics.lyrics_id);
eprintln!(
"Language: {}",
lyrics.lyrics_language.as_deref().unwrap_or(NA_STR)
);
eprintln!(
"Copyright: {}",
lyrics
.lyrics_copyright
.as_deref()
.map(|c| c.trim())
.unwrap_or(NA_STR)
);
eprintln!("Lyrics ID: {}", lyrics.lyrics_id);
eprintln!(
"Language: {}",
lyrics.lyrics_language.as_deref().unwrap_or(NA_STR)
);
eprintln!(
"Copyright: {}",
lyrics
.lyrics_copyright
.as_deref()
.map(|c| c.trim())
.unwrap_or(NA_STR)
);
let mut lyrics_body = lyrics.lyrics_body;
let mut lyrics_body = lyrics.lyrics_body;
if let Some(lang) = lang {
if Some(&lang) != lyrics.lyrics_language.as_ref() {
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
if tl.is_empty() {
eprintln!("Translation not found. Returning lyrics in original language.");
} else {
eprintln!("Translated to: {}", tl.lang);
let tm = TranslationMap::from(tl);
let translated = tm.translate_lyrics(&lyrics_body);
lyrics_body = if bi {
lyrics_body
.lines()
.zip(translated.lines())
.map(|(a, b)| {
if a == b {
a.to_string() + "\n"
} else {
format!("{a}\n> {b}\n")
}
})
.collect()
if let Some(lang) = lang {
if Some(&lang) != lyrics.lyrics_language.as_ref() {
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
if tl.is_empty() {
eprintln!(
"Translation not found. Returning lyrics in original language."
);
} else {
translated
};
eprintln!("Translated to: {}", tl.lang);
let tm = TranslationMap::from(tl);
let translated = tm.translate_lyrics(&lyrics_body);
lyrics_body = if bi {
lyrics_body
.lines()
.zip(translated.lines())
.map(|(a, b)| {
if a == b {
a.to_string() + "\n"
} else {
format!("{a}\n> {b}\n")
}
})
.collect()
} else {
translated
};
}
}
}
eprintln!();
println!("{}", lyrics_body);
}
GetCommands::Subtitles {
ident,
length,
max_deviation,
format,
lang,
} => {
let track_id = get_track_id(ident, &mxm).await?;
let subtitles = mxm
.track_subtitle(
track_id.clone(),
if lang.is_some() {
SubtitleFormat::Json
} else {
format.into()
},
length,
max_deviation.or(Some(1.0)),
)
.await?;
eprintln!("Subtitle ID: {}", subtitles.subtitle_id);
eprintln!(
"Language: {}",
subtitles.subtitle_language.as_deref().unwrap_or(NA_STR)
);
eprintln!("Length: {}", subtitles.subtitle_length);
eprintln!(
"Copyright: {}",
subtitles
.lyrics_copyright
.as_deref()
.map(|s| s.trim())
.unwrap_or(NA_STR)
);
if let Some(lang) = lang {
let mut lines = subtitles.to_lines()?;
if Some(&lang) != subtitles.subtitle_language.as_ref() {
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
if tl.is_empty() {
bail!("Translation not found")
} else {
eprintln!("Translated to: {}", tl.lang);
let tm = TranslationMap::from(tl);
lines = tm.translate_subtitles(&lines);
}
}
eprintln!();
let res = match format {
SubtitleFormatClap::Lrc => lines.to_lrc(),
SubtitleFormatClap::Ttml => lines.to_ttml(),
SubtitleFormatClap::Json => lines.to_json()?,
SubtitleFormatClap::TtmlStructured | SubtitleFormatClap::EbuStl => {
bail!("subtitle format {format:?} cant be translated")
}
};
println!("{}", res);
} else {
eprintln!();
println!("{}", subtitles.subtitle_body);
}
}
GetCommands::Track { ident } => {
let track = get_track(ident, &mxm).await?;
println!("{}", serde_json::to_string_pretty(&track)?)
}
},
Commands::Mp3 { command } => match command {
FileCommands::Lyrics { file } => {
let tag = Tag::read_from_path(&file)?;
eprintln!();
println!("{}", lyrics_body);
}
Commands::Subtitles {
ident,
length,
max_deviation,
format,
lang,
} => {
let track_id = get_track_id(ident, &mxm).await?;
let subtitles = mxm
.track_subtitle(
track_id.clone(),
if lang.is_some() {
SubtitleFormat::Json
} else {
format.into()
},
length,
max_deviation.or(Some(1.0)),
)
.await?;
let title = tag.title().ok_or(anyhow!("no title"))?;
let artist = tag.artist().ok_or(anyhow!("no artist"))?;
eprintln!("Subtitle ID: {}", subtitles.subtitle_id);
eprintln!(
"Language: {}",
subtitles.subtitle_language.as_deref().unwrap_or(NA_STR)
);
eprintln!("Length: {}", subtitles.subtitle_length);
eprintln!(
"Copyright: {}",
subtitles
.lyrics_copyright
.as_deref()
.map(|s| s.trim())
.unwrap_or(NA_STR)
);
let lyrics = mxm.matcher_lyrics(title, artist).await?;
if let Some(lang) = lang {
let mut lines = subtitles.to_lines()?;
println!(
"Lyrics for {} by {}:\n\n{}",
title, artist, lyrics.lyrics_body
);
}
FileCommands::Subtitles { file } => {
let tag = Tag::read_from_path(&file)?;
let duration = mp3_duration::from_path(&file)?;
if Some(&lang) != subtitles.subtitle_language.as_ref() {
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
if tl.is_empty() {
bail!("Translation not found")
} else {
eprintln!("Translated to: {}", tl.lang);
let tm = TranslationMap::from(tl);
lines = tm.translate_subtitles(&lines);
}
}
let title = tag.title().ok_or(anyhow!("no title"))?;
let artist = tag.artist().ok_or(anyhow!("no artist"))?;
let subtitles = mxm
.matcher_subtitle(
title,
artist,
SubtitleFormat::Lrc,
Some(duration.as_secs_f32()),
Some(1.0),
)
.await?;
eprintln!();
let res = match format {
SubtitleFormatClap::Lrc => lines.to_lrc(),
SubtitleFormatClap::Ttml => lines.to_ttml(),
SubtitleFormatClap::Json => lines.to_json()?,
SubtitleFormatClap::TtmlStructured | SubtitleFormatClap::EbuStl => {
bail!("subtitle format {format:?} cant be translated")
}
};
println!("{}", res);
} else {
eprintln!();
println!("{}", subtitles.subtitle_body);
}
}
Commands::Track { ident } => {
let track = get_track(ident, &mxm).await?;
println!("{}", serde_json::to_string_pretty(&track)?)
}
Commands::Album { ident } => {
let id = if let Some(id) = ident.mxm_id {
AlbumId::AlbumId(id)
} else if let Some(mb) = &ident.musicbrainz {
AlbumId::Musicbrainz(mb)
} else {
bail!("no album ID specified")
};
let album = mxm.album(id).await?;
println!("{}", serde_json::to_string_pretty(&album)?)
}
Commands::Artist { ident } => {
let id = if let Some(id) = ident.mxm_id {
ArtistId::ArtistId(id)
} else if let Some(mb) = &ident.musicbrainz {
ArtistId::Musicbrainz(mb)
} else {
bail!("no artist ID specified")
};
let album = mxm.artist(id).await?;
println!("{}", serde_json::to_string_pretty(&album)?)
}
},
Commands::Search {
query,
name,
@ -375,15 +369,6 @@ async fn run(cli: Cli) -> Result<()> {
);
}
}
Commands::SearchArtist { query } => {
let artists = mxm.artist_search(&query.join(" "), 20, 0).await?;
for a in artists {
println!(
"{} <https://musixmatch.com/artist/{}>",
a.artist_name, a.artist_vanity_id
);
}
}
};
Ok(())
}

View file

@ -14,13 +14,12 @@ All notable changes to this project will be documented in this file.\n
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{% set repo_url = "https://codeberg.org/ThetaDev/musixmatch-inofficial" %}\
{% set repo_url = "https://code.thetadev.de/ThetaDev/rustypipe" %}\
{% if version %}\
{%set vname = version | split(pat="/") | last %}
{%if previous.version %}\
## [{{ vname }}]({{ repo_url }}/compare/{{ previous.version }}..{{ version }})\
## [{{ version }}]({{ repo_url }}/compare/{{ previous.version }}..{{ version }})\
{% else %}\
## [{{ vname }}]({{ repo_url }}/commits/tag/{{ version }})\
## {{ version }}\
{% endif %} - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
@ -74,7 +73,7 @@ commit_parsers = [
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\)", skip = true },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },

View file

@ -1,13 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices"
],
"semanticCommits": "enabled",
"automerge": true,
"automergeStrategy": "squash",
"osvVulnerabilityAlerts": true,
"labels": ["dependency-upgrade"],
"enabledManagers": ["cargo"],
"prHourlyLimit": 5
}

View file

@ -99,8 +99,8 @@ pub enum LoginCredential {
#[derive(Debug, Deserialize)]
pub struct Account {
// pub id: String,
// pub email: String,
pub id: String,
pub email: String,
pub name: String,
}

View file

@ -1,12 +1,7 @@
use std::{
num::NonZeroU32,
path::{Path, PathBuf},
sync::LazyLock,
};
use std::path::{Path, PathBuf};
use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
use path_macro::path;
use rstest::{fixture, rstest};
use rstest::rstest;
use time::macros::{date, datetime};
use musixmatch_inofficial::{
@ -14,18 +9,19 @@ use musixmatch_inofficial::{
Error, Musixmatch,
};
fn testfile<P: AsRef<Path>>(name: P) -> PathBuf {
path!(env!("CARGO_MANIFEST_DIR") / "testfiles" / name)
#[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();
}
#[fixture]
async fn mxm() -> Musixmatch {
static LOGIN_LOCK: tokio::sync::OnceCell<()> = tokio::sync::OnceCell::const_new();
static MXM_LIMITER: LazyLock<DefaultDirectRateLimiter> =
LazyLock::new(|| RateLimiter::direct(Quota::per_second(NonZeroU32::new(1).unwrap())));
MXM_LIMITER.until_ready().await;
fn new_mxm() -> Musixmatch {
let mut mxm = Musixmatch::builder();
if let (Ok(email), Ok(password)) = (
@ -35,10 +31,11 @@ async fn mxm() -> Musixmatch {
mxm = mxm.credentials(email, password);
}
let mxm = mxm.build().unwrap();
mxm.build().unwrap()
}
LOGIN_LOCK.get_or_try_init(|| mxm.login()).await.unwrap();
mxm
fn testfile<P: AsRef<Path>>(name: P) -> PathBuf {
path!(env!("CARGO_MANIFEST_DIR") / "testfiles" / name)
}
mod album {
@ -49,8 +46,8 @@ mod album {
#[case::id(AlbumId::AlbumId(14248253))]
#[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))]
#[tokio::test]
async fn by_id(#[case] album_id: AlbumId<'_>, #[future] mxm: Musixmatch) {
let album = mxm.await.album(album_id).await.unwrap();
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!(
@ -97,20 +94,17 @@ mod album {
assert_imgurl(&album.album_coverart_500x500, "/26544045_500_500.jpg");
}
#[rstest]
#[tokio::test]
async fn album_ep(#[future] mxm: Musixmatch) {
let album = mxm.await.album(AlbumId::AlbumId(23976123)).await.unwrap();
async fn album_ep() {
let album = new_mxm().album(AlbumId::AlbumId(23976123)).await.unwrap();
assert_eq!(album.album_name, "Waldbrand EP");
// assert_eq!(album.album_release_type, AlbumType::Ep);
assert_eq!(album.album_release_date, Some(date!(2016 - 09 - 30)));
}
#[rstest]
#[tokio::test]
async fn by_id_missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn by_id_missing() {
let err = new_mxm()
.album(AlbumId::AlbumId(999999999999))
.await
.unwrap_err();
@ -118,11 +112,9 @@ mod album {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn artist_albums(#[future] mxm: Musixmatch) {
let albums = mxm
.await
async fn artist_albums() {
let albums = new_mxm()
.artist_albums(ArtistId::ArtistId(1039), None, 10, 1)
.await
.unwrap();
@ -130,11 +122,9 @@ mod album {
assert_eq!(albums.len(), 10);
}
#[rstest]
#[tokio::test]
async fn artist_albums_missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn artist_albums_missing() {
let err = new_mxm()
.artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1)
.await
.unwrap_err();
@ -142,10 +132,9 @@ mod album {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn charts(#[future] mxm: Musixmatch) {
let albums = mxm.await.chart_albums("US", 10, 1).await.unwrap();
async fn charts() {
let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap();
assert_eq!(albums.len(), 10);
}
@ -158,8 +147,8 @@ mod artist {
#[case::id(ArtistId::ArtistId(410698))]
#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))]
#[tokio::test]
async fn by_id(#[case] artist_id: ArtistId<'_>, #[future] mxm: Musixmatch) {
let artist = mxm.await.artist(artist_id).await.unwrap();
async fn by_id(#[case] artist_id: ArtistId<'_>) {
let artist = new_mxm().artist(artist_id).await.unwrap();
// dbg!(&artist);
@ -201,11 +190,9 @@ mod artist {
assert_eq!(artist.end_date, None);
}
#[rstest]
#[tokio::test]
async fn by_id_missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn by_id_missing() {
let err = new_mxm()
.artist(ArtistId::ArtistId(999999999999))
.await
.unwrap_err();
@ -213,11 +200,9 @@ mod artist {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn related(#[future] mxm: Musixmatch) {
let artists = mxm
.await
async fn related() {
let artists = new_mxm()
.artist_related(ArtistId::ArtistId(26485840), 10, 1)
.await
.unwrap();
@ -225,11 +210,9 @@ mod artist {
assert_eq!(artists.len(), 10);
}
#[rstest]
#[tokio::test]
async fn related_missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn related_missing() {
let err = new_mxm()
.artist_related(ArtistId::ArtistId(999999999999), 10, 1)
.await
.unwrap_err();
@ -237,25 +220,20 @@ mod artist {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn search(#[future] mxm: Musixmatch) {
let artists = mxm
.await
.artist_search("Snollebollekes", 5, 1)
.await
.unwrap();
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, 25344078);
assert_eq!(artist.artist_name, "Snollebollekes");
assert_eq!(artist.artist_id, 410698);
assert_eq!(artist.artist_name, "PSY");
}
#[rstest]
#[tokio::test]
async fn search_empty(#[future] mxm: Musixmatch) {
let artists = mxm
.await
async fn search_empty() {
let artists = new_mxm()
.artist_search(
"Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz",
5,
@ -267,18 +245,16 @@ mod artist {
assert_eq!(artists.len(), 0);
}
#[rstest]
#[tokio::test]
async fn charts(#[future] mxm: Musixmatch) {
let artists = mxm.await.chart_artists("US", 10, 1).await.unwrap();
async fn charts() {
let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap();
assert_eq!(artists.len(), 10);
}
#[rstest]
#[tokio::test]
async fn charts_no_country(#[future] mxm: Musixmatch) {
let artists = mxm.await.chart_artists("XY", 10, 1).await.unwrap();
async fn charts_no_country() {
let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap();
assert_eq!(artists.len(), 10);
}
@ -293,13 +269,8 @@ mod track {
#[case::translation_2c(true, false)]
#[case::translation_3c(true, true)]
#[tokio::test]
async fn from_match(
#[case] translation_status: bool,
#[case] lang_3c: bool,
#[future] mxm: Musixmatch,
) {
let track = mxm
.await
async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) {
let track = new_mxm()
.matcher_track(
"Poker Face",
"Lady Gaga",
@ -312,12 +283,12 @@ mod track {
// dbg!(&track);
assert_eq!(track.track_id, 85213841);
// assert_eq!(
// track.track_mbid.unwrap(),
// "080975b0-39b1-493c-ae64-5cb3292409bb"
// );
// assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
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()
@ -325,7 +296,7 @@ mod track {
"commontrack_isrcs: {:?}",
&track.commontrack_isrcs[0],
);
assert_eq!(track.track_spotify_id.unwrap(), "1QV6tiMFM6fSOKOGLMHYYg");
assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO");
assert!(
track
.commontrack_spotify_ids
@ -345,7 +316,7 @@ mod track {
assert!(track.num_favourite > 50);
assert!(track.lyrics_id.is_some());
assert_eq!(track.subtitle_id.unwrap(), 36450705);
assert_eq!(track.album_id, 20960801);
assert_eq!(track.album_id, 13810402);
assert_eq!(track.album_name, "The Fame");
assert_eq!(track.artist_id, 378462);
assert_eq!(
@ -353,10 +324,9 @@ mod track {
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
);
assert_eq!(track.artist_name, "Lady Gaga");
assert_imgurl(&track.album_coverart_100x100, "/32133892.jpg");
assert_imgurl(&track.album_coverart_350x350, "/32133892_350_350.jpg");
assert_imgurl(&track.album_coverart_500x500, "/32133892_500_500.jpg");
assert_imgurl(&track.album_coverart_800x800, "/32133892_800_800.jpg");
assert_imgurl(&track.album_coverart_100x100, "/26319636.jpg");
assert_imgurl(&track.album_coverart_350x350, "/26319636_350_350.jpg");
assert_imgurl(&track.album_coverart_500x500, "/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));
@ -404,8 +374,8 @@ mod track {
#[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 track = mxm.await.track(track_id, true, false).await.unwrap();
async fn from_id(#[case] track_id: TrackId<'_>) {
let track = new_mxm().track(track_id, true, false).await.unwrap();
// dbg!(&track);
@ -445,25 +415,20 @@ mod track {
#[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,
#[future] mxm: Musixmatch,
) {
let track = mxm
.await
.track(TrackId::Commontrack(47672612), translation_status, lang_3c)
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, 85213841);
// assert_eq!(
// track.track_mbid.unwrap(),
// "080975b0-39b1-493c-ae64-5cb3292409bb"
// );
// assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
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()
@ -471,7 +436,7 @@ mod track {
"commontrack_isrcs: {:?}",
&track.commontrack_isrcs[0],
);
assert_eq!(track.track_spotify_id.unwrap(), "1QV6tiMFM6fSOKOGLMHYYg");
assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO");
assert!(
track
.commontrack_spotify_ids
@ -491,7 +456,7 @@ mod track {
assert!(track.num_favourite > 50);
assert!(track.lyrics_id.is_some());
assert_eq!(track.subtitle_id.unwrap(), 36450705);
assert_eq!(track.album_id, 20960801);
assert_eq!(track.album_id, 13810402);
assert_eq!(track.album_name, "The Fame");
assert_eq!(track.artist_id, 378462);
assert_eq!(
@ -499,10 +464,9 @@ mod track {
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
);
assert_eq!(track.artist_name, "Lady Gaga");
assert_imgurl(&track.album_coverart_100x100, "/32133892.jpg");
assert_imgurl(&track.album_coverart_350x350, "/32133892_350_350.jpg");
assert_imgurl(&track.album_coverart_500x500, "/32133892_500_500.jpg");
assert_imgurl(&track.album_coverart_800x800, "/32133892_800_800.jpg");
assert_imgurl(&track.album_coverart_100x100, "/26319636.jpg");
assert_imgurl(&track.album_coverart_350x350, "/26319636_350_350.jpg");
assert_imgurl(&track.album_coverart_500x500, "/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));
@ -543,11 +507,9 @@ mod track {
}
}
#[rstest]
#[tokio::test]
async fn from_id_missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn from_id_missing() {
let err = new_mxm()
.track(TrackId::TrackId(999999999999), false, false)
.await
.unwrap_err();
@ -555,11 +517,9 @@ mod track {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn album_tracks(#[future] mxm: Musixmatch) {
let tracks = mxm
.await
async fn album_tracks() {
let tracks = new_mxm()
.album_tracks(AlbumId::AlbumId(17118624), true, 20, 1)
.await
.unwrap();
@ -596,11 +556,9 @@ mod track {
});
}
#[rstest]
#[tokio::test]
async fn album_missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn album_missing() {
let err = new_mxm()
.album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1)
.await
.unwrap_err();
@ -612,9 +570,8 @@ mod track {
#[case::top(ChartName::Top)]
#[case::hot(ChartName::Hot)]
#[tokio::test]
async fn charts(#[case] chart_name: ChartName, #[future] mxm: Musixmatch) {
let tracks = mxm
.await
async fn charts(#[case] chart_name: ChartName) {
let tracks = new_mxm()
.chart_tracks("US", chart_name, true, 20, 1)
.await
.unwrap();
@ -622,11 +579,9 @@ mod track {
assert_eq!(tracks.len(), 20);
}
#[rstest]
#[tokio::test]
async fn search(#[future] mxm: Musixmatch) {
let tracks = mxm
.await
async fn search() {
let tracks = new_mxm()
.track_search()
.q_artist("Lena")
.q_track("Satellite")
@ -645,11 +600,9 @@ mod track {
assert_eq!(track.artist_name, "Lena");
}
#[rstest]
#[tokio::test]
async fn search_lyrics(#[future] mxm: Musixmatch) {
let tracks = mxm
.await
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)
@ -657,18 +610,16 @@ mod track {
.await
.unwrap();
assert_gte(tracks.len(), 8, "tracks");
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");
}
#[rstest]
#[tokio::test]
async fn search_empty(#[future] mxm: Musixmatch) {
let artists = mxm
.await
async fn search_empty() {
let artists = new_mxm()
.track_search()
.q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz")
.send(10, 1)
@ -678,19 +629,16 @@ mod track {
assert_eq!(artists.len(), 0);
}
#[rstest]
#[tokio::test]
async fn genres(#[future] mxm: Musixmatch) {
let genres = mxm.await.genres().await.unwrap();
async fn genres() {
let genres = new_mxm().genres().await.unwrap();
assert!(genres.len() > 360);
dbg!(&genres);
}
#[rstest]
#[tokio::test]
async fn snippet(#[future] mxm: Musixmatch) {
let snippet = mxm
.await
async fn snippet() {
let snippet = new_mxm()
.track_snippet(TrackId::Commontrack(8874280))
.await
.unwrap();
@ -712,10 +660,9 @@ mod lyrics {
use super::*;
#[rstest]
#[tokio::test]
async fn from_match(#[future] mxm: Musixmatch) {
let lyrics = mxm.await.matcher_lyrics("Shine", "Spektrem").await.unwrap();
async fn from_match() {
let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap();
// dbg!(&lyrics);
@ -743,8 +690,8 @@ mod lyrics {
#[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 lyrics = mxm.await.track_lyrics(track_id).await.unwrap();
async fn from_id(#[case] track_id: TrackId<'_>) {
let lyrics = new_mxm().track_lyrics(track_id).await.unwrap();
// dbg!(&lyrics);
@ -760,11 +707,9 @@ mod lyrics {
}
/// This track has no lyrics
#[rstest]
#[tokio::test]
async fn instrumental(#[future] mxm: Musixmatch) {
let lyrics = mxm
.await
async fn instrumental() {
let lyrics = new_mxm()
.matcher_lyrics("drivers license", "Bobby G")
.await
.unwrap();
@ -780,11 +725,9 @@ mod lyrics {
}
/// This track does not exist
#[rstest]
#[tokio::test]
async fn missing(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn missing() {
let err = new_mxm()
.track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn".into()))
.await
.unwrap_err();
@ -792,16 +735,14 @@ mod lyrics {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn download_testdata(#[future] mxm: Musixmatch) {
let mxm = mxm.await;
async fn download_testdata() {
let json_path = testfile("lyrics.json");
if json_path.exists() {
return;
}
let lyrics = mxm
let lyrics = new_mxm()
.track_lyrics(TrackId::Commontrack(18576954))
.await
.unwrap();
@ -810,16 +751,14 @@ mod lyrics {
serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap();
}
#[rstest]
#[tokio::test]
async fn download_testdata_translation(#[future] mxm: Musixmatch) {
let mxm = mxm.await;
async fn download_testdata_translation() {
let json_path = testfile("translation.json");
if json_path.exists() {
return;
}
let translations = mxm
let translations = new_mxm()
.track_lyrics_translation(TrackId::Commontrack(18576954), "de")
.await
.unwrap();
@ -828,10 +767,9 @@ mod lyrics {
serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap();
}
#[rstest]
#[tokio::test]
async fn concurrency(#[future] mxm: Musixmatch) {
let mxm = mxm.await;
async fn concurrency() {
let mxm = new_mxm();
let album = mxm
.album_tracks(
@ -866,11 +804,9 @@ mod subtitles {
use super::*;
use musixmatch_inofficial::models::SubtitleFormat;
#[rstest]
#[tokio::test]
async fn from_match(#[future] mxm: Musixmatch) {
let subtitle = mxm
.await
async fn from_match() {
let subtitle = new_mxm()
.matcher_subtitle(
"Shine",
"Spektrem",
@ -899,9 +835,8 @@ mod subtitles {
#[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 subtitle = mxm
.await
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();
@ -921,11 +856,9 @@ mod subtitles {
}
/// This track has no lyrics
#[rstest]
#[tokio::test]
async fn instrumental(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn instrumental() {
let err = new_mxm()
.matcher_subtitle(
"drivers license",
"Bobby G",
@ -940,11 +873,9 @@ mod subtitles {
}
/// This track has not been synced
#[rstest]
#[tokio::test]
async fn unsynced(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn unsynced() {
let err = new_mxm()
.track_subtitle(
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1".into()),
SubtitleFormat::Json,
@ -958,11 +889,9 @@ mod subtitles {
}
/// Try to get subtitles with wrong length parameter
#[rstest]
#[tokio::test]
async fn wrong_length(#[future] mxm: Musixmatch) {
let err = mxm
.await
async fn wrong_length() {
let err = new_mxm()
.track_subtitle(
TrackId::Commontrack(118480583),
SubtitleFormat::Json,
@ -975,16 +904,14 @@ mod subtitles {
assert!(matches!(err, Error::NotFound));
}
#[rstest]
#[tokio::test]
async fn download_testdata(#[future] mxm: Musixmatch) {
async fn download_testdata() {
let json_path = testfile("subtitles.json");
if json_path.exists() {
return;
}
let subtitle = mxm
.await
let subtitle = new_mxm()
.track_subtitle(
TrackId::Commontrack(18576954),
SubtitleFormat::Json,
@ -1046,9 +973,3 @@ fn assert_imgurl(url: &Option<String>, ends_with: &str) {
url
);
}
/// Assert that number A is greater than or equal to number B
#[track_caller]
fn assert_gte<T: PartialOrd + std::fmt::Display>(a: T, b: T, msg: &str) {
assert!(a >= b, "expected >= {b} {msg}, got {a}");
}