Compare commits
No commits in common. "main" and "musixmatch-inofficial/v0.1.0" have entirely different histories.
main
...
musixmatch
15 changed files with 363 additions and 679 deletions
|
@ -4,14 +4,6 @@ on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
Test:
|
Test:
|
||||||
runs-on: cimaster-latest
|
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:
|
steps:
|
||||||
- name: 📦 Checkout repository
|
- name: 📦 Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
@ -25,5 +17,3 @@ jobs:
|
||||||
|
|
||||||
- name: 🧪 Test
|
- name: 🧪 Test
|
||||||
run: cargo test --workspace
|
run: cargo test --workspace
|
||||||
env:
|
|
||||||
ALL_PROXY: "http://warpproxy:8124"
|
|
||||||
|
|
|
@ -23,8 +23,13 @@ jobs:
|
||||||
echo END_OF_FILE
|
echo END_OF_FILE
|
||||||
} >> "$GITHUB_ENV"
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: 📤 Publish crate on crates.io
|
- name: 📤 Publish crate on code.thetadev.de
|
||||||
run: cargo publish --token ${{ secrets.CARGO_TOKEN }} --package "${{ env.CRATE }}"
|
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
|
- name: 🎉 Publish release
|
||||||
uses: https://gitea.com/actions/release-action@main
|
uses: https://gitea.com/actions/release-action@main
|
||||||
|
|
|
@ -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 }}
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -2,31 +2,7 @@
|
||||||
|
|
||||||
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.1.0 - 2024-03-23
|
||||||
## [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
|
|
||||||
|
|
||||||
Initial release
|
Initial release
|
||||||
|
|
||||||
|
|
34
Cargo.toml
34
Cargo.toml
|
@ -1,7 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "musixmatch-inofficial"
|
name = "musixmatch-inofficial"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
rust-version = "1.70.0"
|
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
@ -9,21 +8,22 @@ repository.workspace = true
|
||||||
keywords.workspace = true
|
keywords.workspace = true
|
||||||
description = "Inofficial client for the Musixmatch API"
|
description = "Inofficial client for the Musixmatch API"
|
||||||
|
|
||||||
include = ["/src", "README.md", "CHANGELOG.md", "LICENSE"]
|
|
||||||
|
include = ["/src", "README.md", "LICENSE"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "cli"]
|
members = [".", "cli"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["ThetaDev <thetadev@magenta.de>"]
|
authors = ["ThetaDev <t.testboy@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://codeberg.org/ThetaDev/musixmatch-inofficial"
|
repository = "https://code.thetadev.de/ThetaDev/musixmatch-inofficial"
|
||||||
keywords = ["music", "lyrics"]
|
keywords = ["music", "lyrics"]
|
||||||
categories = ["api-bindings", "multimedia"]
|
categories = ["api-bindings", "multimedia"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
musixmatch-inofficial = { version = "0.1.1", path = ".", default-features = false }
|
musixmatch-inofficial = { version = "0.1.0", path = ".", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["default-tls"]
|
default = ["default-tls"]
|
||||||
|
@ -41,26 +41,30 @@ reqwest = { version = "0.12.0", default-features = false, features = [
|
||||||
"json",
|
"json",
|
||||||
"gzip",
|
"gzip",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.20.4" }
|
tokio = { version = "1.20.0" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
thiserror = "1.0.0"
|
thiserror = "1.0.36"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
time = { version = "0.3.10", features = [
|
time = { version = "0.3.15", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"formatting",
|
"formatting",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-well-known",
|
"serde-well-known",
|
||||||
] }
|
] }
|
||||||
hmac = "0.12.0"
|
hmac = "0.12.1"
|
||||||
sha1 = "0.10.0"
|
sha1 = "0.10.5"
|
||||||
rand = "0.8.0"
|
rand = "0.8.5"
|
||||||
base64 = "0.22.0"
|
base64 = "0.22.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rstest = { version = "0.23.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"
|
dotenvy = "0.15.5"
|
||||||
tokio = { version = "1.20.4", features = ["macros"] }
|
tokio = { version = "1.20.0", features = ["macros"] }
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
path_macro = "1.0.0"
|
path_macro = "1.0.0"
|
||||||
governor = "0.7.0"
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
|
12
Justfile
12
Justfile
|
@ -10,12 +10,12 @@ release crate="musixmatch-inofficial":
|
||||||
CHANGELOG="CHANGELOG.md"
|
CHANGELOG="CHANGELOG.md"
|
||||||
|
|
||||||
if [ "$CRATE" = "musixmatch-inofficial" ]; then
|
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
|
else
|
||||||
if [ ! -d "$CRATE" ]; then
|
if [ ! -d "$CRATE" ]; then
|
||||||
echo "$CRATE does not exist."; exit 1
|
echo "$CRATE does not exist."; exit 1
|
||||||
fi
|
fi
|
||||||
INCLUDES="$INCLUDES --include-path '$CRATE/**'"
|
INCLUDES="$INCLUDES --include-path $CRATE/**"
|
||||||
CHANGELOG="$CRATE/$CHANGELOG"
|
CHANGELOG="$CRATE/$CHANGELOG"
|
||||||
CRATE="musixmatch-$CRATE" # Add crate name prefix
|
CRATE="musixmatch-$CRATE" # Add crate name prefix
|
||||||
fi
|
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
|
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"
|
echo "git-cliff $CLIFF_ARGS"
|
||||||
if [ -f "$CHANGELOG" ]; then
|
if [ -f "$CHANGELOG" ]; then
|
||||||
eval "git-cliff $CLIFF_ARGS --prepend '$CHANGELOG'"
|
git-cliff $CLIFF_ARGS --prepend "$CHANGELOG"
|
||||||
else
|
else
|
||||||
eval "git-cliff $CLIFF_ARGS --output '$CHANGELOG'"
|
git-cliff $CLIFF_ARGS --output "$CHANGELOG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
editor "$CHANGELOG"
|
editor "$CHANGELOG"
|
||||||
|
|
||||||
git add .
|
git add "$CHANGELOG"
|
||||||
git commit -m "chore(release): release $CRATE v$VERSION"
|
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"
|
awk 'BEGIN{RS="(^|\n)## [^\n]+\n*"} NR==2 { print }' "$CHANGELOG" | git tag -as -F - --cleanup whitespace "$TAG"
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,8 +1,4 @@
|
||||||
# musixmatch-inofficial
|
# 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)
|
|
||||||
|
|
||||||
This is an inofficial client for the Musixmatch API that uses the key embedded in the
|
This is an inofficial client for the Musixmatch API that uses the key embedded in the
|
||||||
Musixmatch Android app.
|
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)>),
|
([LRC](<https://en.wikipedia.org/wiki/LRC_(file_format)>),
|
||||||
[DFXP](https://www.w3.org/TR/ttml1/), JSON) for almost any song.
|
[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
|
The Musixmatch API required a free account on <https://www.musixmatch.com> to be used.
|
||||||
used. However, as of 2024, this requirement was removed and the API can be used
|
However, as of 2024, this requirement was removed and the API can be used anonymously.
|
||||||
anonymously. The client still allows you to supply credentials if Musixmatch decides to
|
The client still allows you to supply credentials if Musixmatch decided to close the API
|
||||||
close the API down again.
|
down again.
|
||||||
|
|
||||||
## ⚠️ Copyright disclaimer
|
## ⚠️ Copyright disclaimer
|
||||||
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Initial release
|
|
||||||
|
|
||||||
<!-- generated by git-cliff -->
|
|
|
@ -1,7 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "musixmatch-cli"
|
name = "musixmatch-cli"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
rust-version = "1.70.0"
|
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
@ -10,7 +9,7 @@ keywords.workspace = true
|
||||||
description = "Inofficial command line interface for the Musixmatch API"
|
description = "Inofficial command line interface for the Musixmatch API"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["native-tls"]
|
default = ["rustls-tls-native-roots"]
|
||||||
|
|
||||||
# Reqwest TLS options
|
# Reqwest TLS options
|
||||||
native-tls = ["musixmatch-inofficial/native-tls"]
|
native-tls = ["musixmatch-inofficial/native-tls"]
|
||||||
|
@ -21,9 +20,11 @@ rustls-tls-native-roots = ["musixmatch-inofficial/rustls-tls-native-roots"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
musixmatch-inofficial.workspace = true
|
musixmatch-inofficial.workspace = true
|
||||||
tokio = { version = "1.20.4", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.20.0", features = ["macros", "rt-multi-thread"] }
|
||||||
clap = { version = "4.0.0", features = ["derive"] }
|
id3 = "1.3.0"
|
||||||
anyhow = "1.0.0"
|
mp3-duration = "0.1.10"
|
||||||
|
clap = { version = "4.0.10", features = ["derive"] }
|
||||||
|
anyhow = "1.0.65"
|
||||||
rpassword = "7.0.0"
|
rpassword = "7.0.0"
|
||||||
dirs = "5.0.0"
|
dirs = "5.0.0"
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.91"
|
||||||
|
|
|
@ -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
|
|
||||||
...
|
|
||||||
```
|
|
389
cli/src/main.rs
389
cli/src/main.rs
|
@ -1,6 +1,3 @@
|
||||||
#![doc = include_str!("../README.md")]
|
|
||||||
#![warn(missing_docs, clippy::todo)]
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Write},
|
io::{stdin, stdout, Write},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
@ -8,8 +5,9 @@ use std::{
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
use id3::{Tag, TagLike};
|
||||||
use musixmatch_inofficial::{
|
use musixmatch_inofficial::{
|
||||||
models::{AlbumId, ArtistId, SubtitleFormat, Track, TrackId, TranslationMap},
|
models::{SubtitleFormat, Track, TrackId, TranslationMap},
|
||||||
Musixmatch,
|
Musixmatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,50 +20,14 @@ struct Cli {
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
/// Get lyrics text
|
Get {
|
||||||
Lyrics {
|
#[command(subcommand)]
|
||||||
#[clap(flatten)]
|
command: GetCommands,
|
||||||
ident: TrackIdentifiers,
|
|
||||||
/// Language
|
|
||||||
#[clap(long)]
|
|
||||||
lang: Option<String>,
|
|
||||||
/// Bilingual
|
|
||||||
#[clap(long)]
|
|
||||||
bi: bool,
|
|
||||||
},
|
},
|
||||||
/// Get subtitles (time-synced lyrics)
|
Mp3 {
|
||||||
Subtitles {
|
#[command(subcommand)]
|
||||||
#[clap(flatten)]
|
command: FileCommands,
|
||||||
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>,
|
|
||||||
},
|
},
|
||||||
/// 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)]
|
#[group(required = true)]
|
||||||
Search {
|
Search {
|
||||||
/// Track name
|
/// Track name
|
||||||
|
@ -80,8 +42,40 @@ enum Commands {
|
||||||
/// Search query
|
/// Search query
|
||||||
query: Option<Vec<String>>,
|
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)]
|
#[derive(Args)]
|
||||||
|
@ -112,38 +106,22 @@ struct TrackIdentifiers {
|
||||||
isrc: Option<String>,
|
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)]
|
#[derive(Subcommand)]
|
||||||
enum FileCommands {
|
enum FileCommands {
|
||||||
/// Get lyrics text
|
|
||||||
Lyrics {
|
Lyrics {
|
||||||
/// Music file
|
/// Music file
|
||||||
#[clap(value_parser)]
|
#[clap(value_parser)]
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
},
|
},
|
||||||
/// Get subtitles (time-synced lyrics)
|
|
||||||
Subtitles {
|
Subtitles {
|
||||||
/// Music file
|
/// Music file
|
||||||
#[clap(value_parser)]
|
#[clap(value_parser)]
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
/// Subtitle format
|
|
||||||
#[clap(short, long, default_value = "lrc")]
|
|
||||||
format: SubtitleFormatClap,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::ValueEnum, Debug, Copy, Clone)]
|
#[derive(clap::ValueEnum, Debug, Copy, Clone)]
|
||||||
enum SubtitleFormatClap {
|
pub enum SubtitleFormatClap {
|
||||||
Lrc,
|
Lrc,
|
||||||
Ttml,
|
Ttml,
|
||||||
TtmlStructured,
|
TtmlStructured,
|
||||||
|
@ -197,148 +175,164 @@ async fn run(cli: Cli) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Lyrics { ident, lang, bi } => {
|
Commands::Get { command } => match command {
|
||||||
let track_id = get_track_id(ident, &mxm).await?;
|
GetCommands::Lyrics { ident, lang, bi } => {
|
||||||
let lyrics = mxm.track_lyrics(track_id.clone()).await?;
|
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!("Lyrics ID: {}", lyrics.lyrics_id);
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Language: {}",
|
"Language: {}",
|
||||||
lyrics.lyrics_language.as_deref().unwrap_or(NA_STR)
|
lyrics.lyrics_language.as_deref().unwrap_or(NA_STR)
|
||||||
);
|
);
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Copyright: {}",
|
"Copyright: {}",
|
||||||
lyrics
|
lyrics
|
||||||
.lyrics_copyright
|
.lyrics_copyright
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|c| c.trim())
|
.map(|c| c.trim())
|
||||||
.unwrap_or(NA_STR)
|
.unwrap_or(NA_STR)
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut lyrics_body = lyrics.lyrics_body;
|
let mut lyrics_body = lyrics.lyrics_body;
|
||||||
|
|
||||||
if let Some(lang) = lang {
|
if let Some(lang) = lang {
|
||||||
if Some(&lang) != lyrics.lyrics_language.as_ref() {
|
if Some(&lang) != lyrics.lyrics_language.as_ref() {
|
||||||
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
|
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
|
||||||
if tl.is_empty() {
|
if tl.is_empty() {
|
||||||
eprintln!("Translation not found. Returning lyrics in original language.");
|
eprintln!(
|
||||||
} else {
|
"Translation not found. Returning lyrics in original language."
|
||||||
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 {
|
} 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!();
|
let title = tag.title().ok_or(anyhow!("no title"))?;
|
||||||
println!("{}", lyrics_body);
|
let artist = tag.artist().ok_or(anyhow!("no artist"))?;
|
||||||
}
|
|
||||||
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?;
|
|
||||||
|
|
||||||
eprintln!("Subtitle ID: {}", subtitles.subtitle_id);
|
let lyrics = mxm.matcher_lyrics(title, artist).await?;
|
||||||
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 {
|
println!(
|
||||||
let mut lines = subtitles.to_lines()?;
|
"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 title = tag.title().ok_or(anyhow!("no title"))?;
|
||||||
let tl = mxm.track_lyrics_translation(track_id, &lang).await?;
|
let artist = tag.artist().ok_or(anyhow!("no artist"))?;
|
||||||
if tl.is_empty() {
|
|
||||||
bail!("Translation not found")
|
let subtitles = mxm
|
||||||
} else {
|
.matcher_subtitle(
|
||||||
eprintln!("Translated to: {}", tl.lang);
|
title,
|
||||||
let tm = TranslationMap::from(tl);
|
artist,
|
||||||
lines = tm.translate_subtitles(&lines);
|
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);
|
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 {
|
Commands::Search {
|
||||||
query,
|
query,
|
||||||
name,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,12 @@ All notable changes to this project will be documented in this file.\n
|
||||||
# template for the changelog body
|
# template for the changelog body
|
||||||
# https://keats.github.io/tera/docs/#introduction
|
# https://keats.github.io/tera/docs/#introduction
|
||||||
body = """
|
body = """
|
||||||
{% set repo_url = "https://codeberg.org/ThetaDev/musixmatch-inofficial" %}\
|
{% set repo_url = "https://code.thetadev.de/ThetaDev/rustypipe" %}\
|
||||||
{% if version %}\
|
{% if version %}\
|
||||||
{%set vname = version | split(pat="/") | last %}
|
|
||||||
{%if previous.version %}\
|
{%if previous.version %}\
|
||||||
## [{{ vname }}]({{ repo_url }}/compare/{{ previous.version }}..{{ version }})\
|
## [{{ version }}]({{ repo_url }}/compare/{{ previous.version }}..{{ version }})\
|
||||||
{% else %}\
|
{% else %}\
|
||||||
## [{{ vname }}]({{ repo_url }}/commits/tag/{{ version }})\
|
## {{ version }}\
|
||||||
{% endif %} - {{ timestamp | date(format="%Y-%m-%d") }}
|
{% endif %} - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
{% else %}\
|
{% else %}\
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
@ -74,7 +73,7 @@ commit_parsers = [
|
||||||
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
||||||
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
||||||
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
|
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
|
||||||
{ message = "^chore\\(release\\)", skip = true },
|
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||||
{ message = "^chore\\(pr\\)", skip = true },
|
{ message = "^chore\\(pr\\)", skip = true },
|
||||||
{ message = "^chore\\(pull\\)", skip = true },
|
{ message = "^chore\\(pull\\)", skip = true },
|
||||||
{ message = "^chore", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
|
{ message = "^chore", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -99,8 +99,8 @@ pub enum LoginCredential {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
// pub id: String,
|
pub id: String,
|
||||||
// pub email: String,
|
pub email: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
328
tests/tests.rs
328
tests/tests.rs
|
@ -1,12 +1,7 @@
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
num::NonZeroU32,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::LazyLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
|
|
||||||
use path_macro::path;
|
use path_macro::path;
|
||||||
use rstest::{fixture, rstest};
|
use rstest::rstest;
|
||||||
use time::macros::{date, datetime};
|
use time::macros::{date, datetime};
|
||||||
|
|
||||||
use musixmatch_inofficial::{
|
use musixmatch_inofficial::{
|
||||||
|
@ -14,18 +9,19 @@ use musixmatch_inofficial::{
|
||||||
Error, Musixmatch,
|
Error, Musixmatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn testfile<P: AsRef<Path>>(name: P) -> PathBuf {
|
#[ctor::ctor]
|
||||||
path!(env!("CARGO_MANIFEST_DIR") / "testfiles" / name)
|
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]
|
fn new_mxm() -> Musixmatch {
|
||||||
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;
|
|
||||||
|
|
||||||
let mut mxm = Musixmatch::builder();
|
let mut mxm = Musixmatch::builder();
|
||||||
|
|
||||||
if let (Ok(email), Ok(password)) = (
|
if let (Ok(email), Ok(password)) = (
|
||||||
|
@ -35,10 +31,11 @@ async fn mxm() -> Musixmatch {
|
||||||
mxm = mxm.credentials(email, password);
|
mxm = mxm.credentials(email, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mxm = mxm.build().unwrap();
|
mxm.build().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
LOGIN_LOCK.get_or_try_init(|| mxm.login()).await.unwrap();
|
fn testfile<P: AsRef<Path>>(name: P) -> PathBuf {
|
||||||
mxm
|
path!(env!("CARGO_MANIFEST_DIR") / "testfiles" / name)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod album {
|
mod album {
|
||||||
|
@ -49,8 +46,8 @@ mod album {
|
||||||
#[case::id(AlbumId::AlbumId(14248253))]
|
#[case::id(AlbumId::AlbumId(14248253))]
|
||||||
#[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))]
|
#[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn by_id(#[case] album_id: AlbumId<'_>, #[future] mxm: Musixmatch) {
|
async fn by_id(#[case] album_id: AlbumId<'_>) {
|
||||||
let album = mxm.await.album(album_id).await.unwrap();
|
let album = new_mxm().album(album_id).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(album.album_id, 14248253);
|
assert_eq!(album.album_id, 14248253);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -59,7 +56,7 @@ mod album {
|
||||||
);
|
);
|
||||||
assert_eq!(album.album_name, "Gangnam Style (강남스타일)");
|
assert_eq!(album.album_name, "Gangnam Style (강남스타일)");
|
||||||
assert!(album.album_rating > 20);
|
assert!(album.album_rating > 20);
|
||||||
assert_eq!(album.album_track_count, 0);
|
assert_eq!(album.album_track_count, 1);
|
||||||
assert_eq!(album.album_release_date.unwrap(), date!(2012 - 01 - 01));
|
assert_eq!(album.album_release_date.unwrap(), date!(2012 - 01 - 01));
|
||||||
assert_eq!(album.album_release_type, AlbumType::Single);
|
assert_eq!(album.album_release_type, AlbumType::Single);
|
||||||
assert_eq!(album.artist_id, 410698);
|
assert_eq!(album.artist_id, 410698);
|
||||||
|
@ -97,20 +94,17 @@ mod album {
|
||||||
assert_imgurl(&album.album_coverart_500x500, "/26544045_500_500.jpg");
|
assert_imgurl(&album.album_coverart_500x500, "/26544045_500_500.jpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn album_ep(#[future] mxm: Musixmatch) {
|
async fn album_ep() {
|
||||||
let album = mxm.await.album(AlbumId::AlbumId(23976123)).await.unwrap();
|
let album = new_mxm().album(AlbumId::AlbumId(23976123)).await.unwrap();
|
||||||
assert_eq!(album.album_name, "Waldbrand EP");
|
assert_eq!(album.album_name, "Waldbrand EP");
|
||||||
// assert_eq!(album.album_release_type, AlbumType::Ep);
|
// assert_eq!(album.album_release_type, AlbumType::Ep);
|
||||||
assert_eq!(album.album_release_date, Some(date!(2016 - 09 - 30)));
|
assert_eq!(album.album_release_date, Some(date!(2016 - 09 - 30)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn by_id_missing(#[future] mxm: Musixmatch) {
|
async fn by_id_missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.album(AlbumId::AlbumId(999999999999))
|
.album(AlbumId::AlbumId(999999999999))
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -118,12 +112,9 @@ mod album {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore]
|
async fn artist_albums() {
|
||||||
async fn artist_albums(#[future] mxm: Musixmatch) {
|
let albums = new_mxm()
|
||||||
let albums = mxm
|
|
||||||
.await
|
|
||||||
.artist_albums(ArtistId::ArtistId(1039), None, 10, 1)
|
.artist_albums(ArtistId::ArtistId(1039), None, 10, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -131,11 +122,9 @@ mod album {
|
||||||
assert_eq!(albums.len(), 10);
|
assert_eq!(albums.len(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn artist_albums_missing(#[future] mxm: Musixmatch) {
|
async fn artist_albums_missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1)
|
.artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -143,10 +132,9 @@ mod album {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn charts(#[future] mxm: Musixmatch) {
|
async fn charts() {
|
||||||
let albums = mxm.await.chart_albums("US", 10, 1).await.unwrap();
|
let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(albums.len(), 10);
|
assert_eq!(albums.len(), 10);
|
||||||
}
|
}
|
||||||
|
@ -159,8 +147,8 @@ mod artist {
|
||||||
#[case::id(ArtistId::ArtistId(410698))]
|
#[case::id(ArtistId::ArtistId(410698))]
|
||||||
#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))]
|
#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn by_id(#[case] artist_id: ArtistId<'_>, #[future] mxm: Musixmatch) {
|
async fn by_id(#[case] artist_id: ArtistId<'_>) {
|
||||||
let artist = mxm.await.artist(artist_id).await.unwrap();
|
let artist = new_mxm().artist(artist_id).await.unwrap();
|
||||||
|
|
||||||
// dbg!(&artist);
|
// dbg!(&artist);
|
||||||
|
|
||||||
|
@ -202,11 +190,9 @@ mod artist {
|
||||||
assert_eq!(artist.end_date, None);
|
assert_eq!(artist.end_date, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn by_id_missing(#[future] mxm: Musixmatch) {
|
async fn by_id_missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.artist(ArtistId::ArtistId(999999999999))
|
.artist(ArtistId::ArtistId(999999999999))
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -214,11 +200,9 @@ mod artist {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn related(#[future] mxm: Musixmatch) {
|
async fn related() {
|
||||||
let artists = mxm
|
let artists = new_mxm()
|
||||||
.await
|
|
||||||
.artist_related(ArtistId::ArtistId(26485840), 10, 1)
|
.artist_related(ArtistId::ArtistId(26485840), 10, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -226,11 +210,9 @@ mod artist {
|
||||||
assert_eq!(artists.len(), 10);
|
assert_eq!(artists.len(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn related_missing(#[future] mxm: Musixmatch) {
|
async fn related_missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.artist_related(ArtistId::ArtistId(999999999999), 10, 1)
|
.artist_related(ArtistId::ArtistId(999999999999), 10, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -238,25 +220,20 @@ mod artist {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn search(#[future] mxm: Musixmatch) {
|
async fn search() {
|
||||||
let artists = mxm
|
let artists = new_mxm().artist_search("psy", 5, 1).await.unwrap();
|
||||||
.await
|
|
||||||
.artist_search("Snollebollekes", 5, 1)
|
assert_eq!(artists.len(), 5);
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let artist = &artists[0];
|
let artist = &artists[0];
|
||||||
assert_eq!(artist.artist_id, 25344078);
|
assert_eq!(artist.artist_id, 410698);
|
||||||
assert_eq!(artist.artist_name, "Snollebollekes");
|
assert_eq!(artist.artist_name, "PSY");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn search_empty(#[future] mxm: Musixmatch) {
|
async fn search_empty() {
|
||||||
let artists = mxm
|
let artists = new_mxm()
|
||||||
.await
|
|
||||||
.artist_search(
|
.artist_search(
|
||||||
"Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz",
|
"Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz",
|
||||||
5,
|
5,
|
||||||
|
@ -268,18 +245,16 @@ mod artist {
|
||||||
assert_eq!(artists.len(), 0);
|
assert_eq!(artists.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn charts(#[future] mxm: Musixmatch) {
|
async fn charts() {
|
||||||
let artists = mxm.await.chart_artists("US", 10, 1).await.unwrap();
|
let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(artists.len(), 10);
|
assert_eq!(artists.len(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn charts_no_country(#[future] mxm: Musixmatch) {
|
async fn charts_no_country() {
|
||||||
let artists = mxm.await.chart_artists("XY", 10, 1).await.unwrap();
|
let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(artists.len(), 10);
|
assert_eq!(artists.len(), 10);
|
||||||
}
|
}
|
||||||
|
@ -294,13 +269,8 @@ mod track {
|
||||||
#[case::translation_2c(true, false)]
|
#[case::translation_2c(true, false)]
|
||||||
#[case::translation_3c(true, true)]
|
#[case::translation_3c(true, true)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_match(
|
async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
||||||
#[case] translation_status: bool,
|
let track = new_mxm()
|
||||||
#[case] lang_3c: bool,
|
|
||||||
#[future] mxm: Musixmatch,
|
|
||||||
) {
|
|
||||||
let track = mxm
|
|
||||||
.await
|
|
||||||
.matcher_track(
|
.matcher_track(
|
||||||
"Poker Face",
|
"Poker Face",
|
||||||
"Lady Gaga",
|
"Lady Gaga",
|
||||||
|
@ -313,12 +283,12 @@ mod track {
|
||||||
|
|
||||||
// dbg!(&track);
|
// dbg!(&track);
|
||||||
|
|
||||||
assert_eq!(track.track_id, 85213841);
|
assert_eq!(track.track_id, 15476784);
|
||||||
// assert_eq!(
|
assert_eq!(
|
||||||
// track.track_mbid.unwrap(),
|
track.track_mbid.unwrap(),
|
||||||
// "080975b0-39b1-493c-ae64-5cb3292409bb"
|
"080975b0-39b1-493c-ae64-5cb3292409bb"
|
||||||
// );
|
);
|
||||||
// assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
|
assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
|
||||||
assert!(
|
assert!(
|
||||||
track.commontrack_isrcs[0]
|
track.commontrack_isrcs[0]
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -326,7 +296,7 @@ mod track {
|
||||||
"commontrack_isrcs: {:?}",
|
"commontrack_isrcs: {:?}",
|
||||||
&track.commontrack_isrcs[0],
|
&track.commontrack_isrcs[0],
|
||||||
);
|
);
|
||||||
assert_eq!(track.track_spotify_id.unwrap(), "1QV6tiMFM6fSOKOGLMHYYg");
|
assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO");
|
||||||
assert!(
|
assert!(
|
||||||
track
|
track
|
||||||
.commontrack_spotify_ids
|
.commontrack_spotify_ids
|
||||||
|
@ -346,7 +316,7 @@ mod track {
|
||||||
assert!(track.num_favourite > 50);
|
assert!(track.num_favourite > 50);
|
||||||
assert!(track.lyrics_id.is_some());
|
assert!(track.lyrics_id.is_some());
|
||||||
assert_eq!(track.subtitle_id.unwrap(), 36450705);
|
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.album_name, "The Fame");
|
||||||
assert_eq!(track.artist_id, 378462);
|
assert_eq!(track.artist_id, 378462);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -354,10 +324,9 @@ mod track {
|
||||||
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
|
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
|
||||||
);
|
);
|
||||||
assert_eq!(track.artist_name, "Lady Gaga");
|
assert_eq!(track.artist_name, "Lady Gaga");
|
||||||
assert_imgurl(&track.album_coverart_100x100, "/32133892.jpg");
|
assert_imgurl(&track.album_coverart_100x100, "/26319636.jpg");
|
||||||
assert_imgurl(&track.album_coverart_350x350, "/32133892_350_350.jpg");
|
assert_imgurl(&track.album_coverart_350x350, "/26319636_350_350.jpg");
|
||||||
assert_imgurl(&track.album_coverart_500x500, "/32133892_500_500.jpg");
|
assert_imgurl(&track.album_coverart_500x500, "/26319636_500_500.jpg");
|
||||||
assert_imgurl(&track.album_coverart_800x800, "/32133892_800_800.jpg");
|
|
||||||
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
||||||
let first_release = track.first_release_date.unwrap();
|
let first_release = track.first_release_date.unwrap();
|
||||||
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
||||||
|
@ -405,8 +374,8 @@ mod track {
|
||||||
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
|
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
|
||||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
|
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_id(#[case] track_id: TrackId<'_>, #[future] mxm: Musixmatch) {
|
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||||
let track = mxm.await.track(track_id, true, false).await.unwrap();
|
let track = new_mxm().track(track_id, true, false).await.unwrap();
|
||||||
|
|
||||||
// dbg!(&track);
|
// dbg!(&track);
|
||||||
|
|
||||||
|
@ -446,25 +415,20 @@ mod track {
|
||||||
#[case::translation_2c(true, false)]
|
#[case::translation_2c(true, false)]
|
||||||
#[case::translation_3c(true, true)]
|
#[case::translation_3c(true, true)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_id_translations(
|
async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
||||||
#[case] translation_status: bool,
|
let track = new_mxm()
|
||||||
#[case] lang_3c: bool,
|
.track(TrackId::TrackId(15476784), translation_status, lang_3c)
|
||||||
#[future] mxm: Musixmatch,
|
|
||||||
) {
|
|
||||||
let track = mxm
|
|
||||||
.await
|
|
||||||
.track(TrackId::Commontrack(47672612), translation_status, lang_3c)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// dbg!(&track);
|
// dbg!(&track);
|
||||||
|
|
||||||
assert_eq!(track.track_id, 85213841);
|
assert_eq!(track.track_id, 15476784);
|
||||||
// assert_eq!(
|
assert_eq!(
|
||||||
// track.track_mbid.unwrap(),
|
track.track_mbid.unwrap(),
|
||||||
// "080975b0-39b1-493c-ae64-5cb3292409bb"
|
"080975b0-39b1-493c-ae64-5cb3292409bb"
|
||||||
// );
|
);
|
||||||
// assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
|
assert_eq!(track.track_isrc.unwrap(), "USUM70824409");
|
||||||
assert!(
|
assert!(
|
||||||
track.commontrack_isrcs[0]
|
track.commontrack_isrcs[0]
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -472,7 +436,7 @@ mod track {
|
||||||
"commontrack_isrcs: {:?}",
|
"commontrack_isrcs: {:?}",
|
||||||
&track.commontrack_isrcs[0],
|
&track.commontrack_isrcs[0],
|
||||||
);
|
);
|
||||||
assert_eq!(track.track_spotify_id.unwrap(), "1QV6tiMFM6fSOKOGLMHYYg");
|
assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO");
|
||||||
assert!(
|
assert!(
|
||||||
track
|
track
|
||||||
.commontrack_spotify_ids
|
.commontrack_spotify_ids
|
||||||
|
@ -492,7 +456,7 @@ mod track {
|
||||||
assert!(track.num_favourite > 50);
|
assert!(track.num_favourite > 50);
|
||||||
assert!(track.lyrics_id.is_some());
|
assert!(track.lyrics_id.is_some());
|
||||||
assert_eq!(track.subtitle_id.unwrap(), 36450705);
|
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.album_name, "The Fame");
|
||||||
assert_eq!(track.artist_id, 378462);
|
assert_eq!(track.artist_id, 378462);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -500,10 +464,9 @@ mod track {
|
||||||
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
|
"650e7db6-b795-4eb5-a702-5ea2fc46c848"
|
||||||
);
|
);
|
||||||
assert_eq!(track.artist_name, "Lady Gaga");
|
assert_eq!(track.artist_name, "Lady Gaga");
|
||||||
assert_imgurl(&track.album_coverart_100x100, "/32133892.jpg");
|
assert_imgurl(&track.album_coverart_100x100, "/26319636.jpg");
|
||||||
assert_imgurl(&track.album_coverart_350x350, "/32133892_350_350.jpg");
|
assert_imgurl(&track.album_coverart_350x350, "/26319636_350_350.jpg");
|
||||||
assert_imgurl(&track.album_coverart_500x500, "/32133892_500_500.jpg");
|
assert_imgurl(&track.album_coverart_500x500, "/26319636_500_500.jpg");
|
||||||
assert_imgurl(&track.album_coverart_800x800, "/32133892_800_800.jpg");
|
|
||||||
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1");
|
||||||
let first_release = track.first_release_date.unwrap();
|
let first_release = track.first_release_date.unwrap();
|
||||||
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
assert_eq!(first_release.date(), date!(2008 - 1 - 1));
|
||||||
|
@ -544,11 +507,9 @@ mod track {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_id_missing(#[future] mxm: Musixmatch) {
|
async fn from_id_missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.track(TrackId::TrackId(999999999999), false, false)
|
.track(TrackId::TrackId(999999999999), false, false)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -556,11 +517,9 @@ mod track {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn album_tracks(#[future] mxm: Musixmatch) {
|
async fn album_tracks() {
|
||||||
let tracks = mxm
|
let tracks = new_mxm()
|
||||||
.await
|
|
||||||
.album_tracks(AlbumId::AlbumId(17118624), true, 20, 1)
|
.album_tracks(AlbumId::AlbumId(17118624), true, 20, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -597,11 +556,9 @@ mod track {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn album_missing(#[future] mxm: Musixmatch) {
|
async fn album_missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1)
|
.album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -613,9 +570,8 @@ mod track {
|
||||||
#[case::top(ChartName::Top)]
|
#[case::top(ChartName::Top)]
|
||||||
#[case::hot(ChartName::Hot)]
|
#[case::hot(ChartName::Hot)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn charts(#[case] chart_name: ChartName, #[future] mxm: Musixmatch) {
|
async fn charts(#[case] chart_name: ChartName) {
|
||||||
let tracks = mxm
|
let tracks = new_mxm()
|
||||||
.await
|
|
||||||
.chart_tracks("US", chart_name, true, 20, 1)
|
.chart_tracks("US", chart_name, true, 20, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -623,11 +579,9 @@ mod track {
|
||||||
assert_eq!(tracks.len(), 20);
|
assert_eq!(tracks.len(), 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn search(#[future] mxm: Musixmatch) {
|
async fn search() {
|
||||||
let tracks = mxm
|
let tracks = new_mxm()
|
||||||
.await
|
|
||||||
.track_search()
|
.track_search()
|
||||||
.q_artist("Lena")
|
.q_artist("Lena")
|
||||||
.q_track("Satellite")
|
.q_track("Satellite")
|
||||||
|
@ -646,11 +600,9 @@ mod track {
|
||||||
assert_eq!(track.artist_name, "Lena");
|
assert_eq!(track.artist_name, "Lena");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn search_lyrics(#[future] mxm: Musixmatch) {
|
async fn search_lyrics() {
|
||||||
let tracks = mxm
|
let tracks = new_mxm()
|
||||||
.await
|
|
||||||
.track_search()
|
.track_search()
|
||||||
.q_lyrics("the whole world stops and stares for a while")
|
.q_lyrics("the whole world stops and stares for a while")
|
||||||
.s_track_rating(SortOrder::Desc)
|
.s_track_rating(SortOrder::Desc)
|
||||||
|
@ -658,18 +610,16 @@ mod track {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_gte(tracks.len(), 8, "tracks");
|
assert_eq!(tracks.len(), 10);
|
||||||
|
|
||||||
let track = &tracks[0];
|
let track = &tracks[0];
|
||||||
assert_eq!(track.track_name, "Just the Way You Are");
|
assert_eq!(track.track_name, "Just the Way You Are");
|
||||||
assert_eq!(track.artist_name, "Bruno Mars");
|
assert_eq!(track.artist_name, "Bruno Mars");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn search_empty(#[future] mxm: Musixmatch) {
|
async fn search_empty() {
|
||||||
let artists = mxm
|
let artists = new_mxm()
|
||||||
.await
|
|
||||||
.track_search()
|
.track_search()
|
||||||
.q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz")
|
.q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz")
|
||||||
.send(10, 1)
|
.send(10, 1)
|
||||||
|
@ -679,19 +629,16 @@ mod track {
|
||||||
assert_eq!(artists.len(), 0);
|
assert_eq!(artists.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn genres(#[future] mxm: Musixmatch) {
|
async fn genres() {
|
||||||
let genres = mxm.await.genres().await.unwrap();
|
let genres = new_mxm().genres().await.unwrap();
|
||||||
assert!(genres.len() > 360);
|
assert!(genres.len() > 360);
|
||||||
dbg!(&genres);
|
dbg!(&genres);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn snippet(#[future] mxm: Musixmatch) {
|
async fn snippet() {
|
||||||
let snippet = mxm
|
let snippet = new_mxm()
|
||||||
.await
|
|
||||||
.track_snippet(TrackId::Commontrack(8874280))
|
.track_snippet(TrackId::Commontrack(8874280))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -713,10 +660,9 @@ mod lyrics {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_match(#[future] mxm: Musixmatch) {
|
async fn from_match() {
|
||||||
let lyrics = mxm.await.matcher_lyrics("Shine", "Spektrem").await.unwrap();
|
let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap();
|
||||||
|
|
||||||
// dbg!(&lyrics);
|
// dbg!(&lyrics);
|
||||||
|
|
||||||
|
@ -744,8 +690,8 @@ mod lyrics {
|
||||||
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
|
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
|
||||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
|
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_id(#[case] track_id: TrackId<'_>, #[future] mxm: Musixmatch) {
|
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||||
let lyrics = mxm.await.track_lyrics(track_id).await.unwrap();
|
let lyrics = new_mxm().track_lyrics(track_id).await.unwrap();
|
||||||
|
|
||||||
// dbg!(&lyrics);
|
// dbg!(&lyrics);
|
||||||
|
|
||||||
|
@ -761,11 +707,9 @@ mod lyrics {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This track has no lyrics
|
/// This track has no lyrics
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn instrumental(#[future] mxm: Musixmatch) {
|
async fn instrumental() {
|
||||||
let lyrics = mxm
|
let lyrics = new_mxm()
|
||||||
.await
|
|
||||||
.matcher_lyrics("drivers license", "Bobby G")
|
.matcher_lyrics("drivers license", "Bobby G")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -781,11 +725,9 @@ mod lyrics {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This track does not exist
|
/// This track does not exist
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn missing(#[future] mxm: Musixmatch) {
|
async fn missing() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn".into()))
|
.track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn".into()))
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
@ -793,16 +735,14 @@ mod lyrics {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn download_testdata(#[future] mxm: Musixmatch) {
|
async fn download_testdata() {
|
||||||
let mxm = mxm.await;
|
|
||||||
let json_path = testfile("lyrics.json");
|
let json_path = testfile("lyrics.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lyrics = mxm
|
let lyrics = new_mxm()
|
||||||
.track_lyrics(TrackId::Commontrack(18576954))
|
.track_lyrics(TrackId::Commontrack(18576954))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -811,16 +751,14 @@ mod lyrics {
|
||||||
serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap();
|
serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn download_testdata_translation(#[future] mxm: Musixmatch) {
|
async fn download_testdata_translation() {
|
||||||
let mxm = mxm.await;
|
|
||||||
let json_path = testfile("translation.json");
|
let json_path = testfile("translation.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let translations = mxm
|
let translations = new_mxm()
|
||||||
.track_lyrics_translation(TrackId::Commontrack(18576954), "de")
|
.track_lyrics_translation(TrackId::Commontrack(18576954), "de")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -829,10 +767,9 @@ mod lyrics {
|
||||||
serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap();
|
serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn concurrency(#[future] mxm: Musixmatch) {
|
async fn concurrency() {
|
||||||
let mxm = mxm.await;
|
let mxm = new_mxm();
|
||||||
|
|
||||||
let album = mxm
|
let album = mxm
|
||||||
.album_tracks(
|
.album_tracks(
|
||||||
|
@ -867,11 +804,9 @@ mod subtitles {
|
||||||
use super::*;
|
use super::*;
|
||||||
use musixmatch_inofficial::models::SubtitleFormat;
|
use musixmatch_inofficial::models::SubtitleFormat;
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_match(#[future] mxm: Musixmatch) {
|
async fn from_match() {
|
||||||
let subtitle = mxm
|
let subtitle = new_mxm()
|
||||||
.await
|
|
||||||
.matcher_subtitle(
|
.matcher_subtitle(
|
||||||
"Shine",
|
"Shine",
|
||||||
"Spektrem",
|
"Spektrem",
|
||||||
|
@ -900,9 +835,8 @@ mod subtitles {
|
||||||
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
|
#[case::isrc(TrackId::Isrc("KRA302000590".into()))]
|
||||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
|
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51".into()))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_id(#[case] track_id: TrackId<'_>, #[future] mxm: Musixmatch) {
|
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||||
let subtitle = mxm
|
let subtitle = new_mxm()
|
||||||
.await
|
|
||||||
.track_subtitle(track_id, SubtitleFormat::Json, Some(175.0), Some(1.0))
|
.track_subtitle(track_id, SubtitleFormat::Json, Some(175.0), Some(1.0))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -922,11 +856,9 @@ mod subtitles {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This track has no lyrics
|
/// This track has no lyrics
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn instrumental(#[future] mxm: Musixmatch) {
|
async fn instrumental() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.matcher_subtitle(
|
.matcher_subtitle(
|
||||||
"drivers license",
|
"drivers license",
|
||||||
"Bobby G",
|
"Bobby G",
|
||||||
|
@ -941,11 +873,9 @@ mod subtitles {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This track has not been synced
|
/// This track has not been synced
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn unsynced(#[future] mxm: Musixmatch) {
|
async fn unsynced() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.track_subtitle(
|
.track_subtitle(
|
||||||
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1".into()),
|
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1".into()),
|
||||||
SubtitleFormat::Json,
|
SubtitleFormat::Json,
|
||||||
|
@ -959,11 +889,9 @@ mod subtitles {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to get subtitles with wrong length parameter
|
/// Try to get subtitles with wrong length parameter
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn wrong_length(#[future] mxm: Musixmatch) {
|
async fn wrong_length() {
|
||||||
let err = mxm
|
let err = new_mxm()
|
||||||
.await
|
|
||||||
.track_subtitle(
|
.track_subtitle(
|
||||||
TrackId::Commontrack(118480583),
|
TrackId::Commontrack(118480583),
|
||||||
SubtitleFormat::Json,
|
SubtitleFormat::Json,
|
||||||
|
@ -976,16 +904,14 @@ mod subtitles {
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn download_testdata(#[future] mxm: Musixmatch) {
|
async fn download_testdata() {
|
||||||
let json_path = testfile("subtitles.json");
|
let json_path = testfile("subtitles.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let subtitle = mxm
|
let subtitle = new_mxm()
|
||||||
.await
|
|
||||||
.track_subtitle(
|
.track_subtitle(
|
||||||
TrackId::Commontrack(18576954),
|
TrackId::Commontrack(18576954),
|
||||||
SubtitleFormat::Json,
|
SubtitleFormat::Json,
|
||||||
|
@ -1047,9 +973,3 @@ fn assert_imgurl(url: &Option<String>, ends_with: &str) {
|
||||||
url
|
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}");
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue