# uta
[简体中文](https://docs.rs/crate/uta/latest/source/README.zh-CN.md)
A command-line music search / download tool written in Rust, supporting **QQ Music** and **NetEase Cloud Music**.
It ships as a **single static executable** — no Python, ffmpeg, or other runtime required — and prefers lossless audio.
The logic is ported from `QQMusicClient` / `NeteaseMusicClient` in [musicdl](https://github.com/CharlesPikachu/musicdl),
keeping only the "fast lossless" path.
> ⚠️ For learning and research only. The original project is licensed under PolyForm Noncommercial 1.0.0;
> as a derivative work, this project is likewise **not for commercial use**. Please obtain paid content through official channels.
## Install
- **From crates.io**: `cargo install uta`
- **Prebuilt binaries**: each release also builds Linux x86_64 (fully static musl), macOS (Apple Silicon / Intel)
and Windows x64 archives; they are attached to the project's GitHub Releases (the repository is currently private).
Then create a config file (see [Configuration](#configuration)) — without third-party endpoints you usually won't get lossless audio.
Shell completion (bash / zsh / fish / powershell / elvish):
```bash
uta completions zsh > ~/.zfunc/_uta # zsh: make sure ~/.zfunc is in $fpath before compinit
uta completions bash > ~/.local/share/bash-completion/completions/uta
uta completions fish > ~/.config/fish/completions/uta.fish
uta completions powershell >> $PROFILE # PowerShell
```
## Usage
```bash
uta search "十一月的萧邦" # search → table → arrow keys / space to multi-select → download
uta search "晴天" -n 20 --lossless # take 20 results, keep lossless only
uta search "夜曲" --json # print search results as JSON, non-interactive
uta get <id> -o ~/Music -J 2 # output directory (default: current dir) and download concurrency (default 4)
uta album "十一月的萧邦" # search albums → pick one → download to <output>/<artist> - <album>/ (track/disc numbers tagged)
uta album <album link> | --mid <id> # specify the album directly; -y skips selection (first match for keywords)
uta artist "周杰伦" # artist → album list (release date / type / track count) → multi-select → one folder per album
uta artist "周杰伦" -t studio,ep # only studio albums and EPs (studio|ep|single|live|other); --order hot sorts by popularity
uta artist <artist link> | --mid <id> # specify the artist directly; --json prints the album list; -y downloads every listed album
uta playlist <playlist link or id> # download a whole playlist to <output>/<playlist name>/ (all selected by default; -y skips selection)
```
### Sources
```bash
uta search "田馥甄" -s qq,163 # merged search: -n results from each source, interleaved;
# the same song found on both is kept once, in the better quality (--no-merge lists all)
uta doctor [-s qq,163] # check search, lyrics and every download endpoint (run this first if downloads fail)
```
`-s` works for `search`, `get`, `album`, `artist` and `playlist`; only `search` accepts multiple sources.
**Cross-source fallback**: when a track can't be resolved on its own source (e.g. a greyed-out song in a QQ album),
uta searches the other source for the same song (same title and first artist, duration within 5s) and downloads that audio,
while keeping the original title / artist / album / track number / lyrics / cover. Such rows are marked `(补)` in the table
and carry `"fallback"` in JSON. Disable with `--no-fallback`.
Output: `<output>/<title> - <artist>.flac` plus a matching `.lrc`, with title / artist / album / lyrics / cover tags embedded.
Downloads are resumable: after an interruption (Ctrl-C, network loss, failure) the partial file is kept as `.part`,
and running the same command again continues from where it stopped. Exit code is 130 on Ctrl-C and 1 if any track failed.
## Configuration
```bash
uta config init # create the config file (same content as config.example.toml)
uta config path # show the config file path
```
The config file lives at `~/.config/uta/config.toml` on Linux (the platform config directory elsewhere), or pass `--config <path>`.
Third-party endpoint URLs are **only** read from the config file and are never compiled into the binary.
- `[vkeys]` / `[tang]`: QQ Music third-party endpoints. vkeys is the primary one; tang is a rate-limited fallback when vkeys
can't provide lossless. Without them only the official endpoint is used (usually no lossless).
- `[netease.tmetu]` / `[netease.chksz]` / `[netease.jfjt]`: NetEase third-party endpoints (no keys needed). Without them only the
official endpoint is used, which gives 320k for free tracks and nothing for paid ones when not logged in.
- `[defaults]`: default `source`, `output`, `max_quality`, `jobs`, `download_jobs`.
`source = "163"` makes NetEase the default; `source = ["qq", "163"]` makes `search` use both (other commands use the first).
Priority: command line > config file > built-in defaults.
## Design
| Search | QQ official `musicu.fcg` `DoSearchForQQMusicMobile`; NetEase `cloudsearch/pc` |
| Download links | QQ: third-party (vkeys → tang) first, official `GetVkey` as fallback; NetEase: third-party (tmetu → chksz → jfjt) first, official eapi as fallback |
| Concurrency | tokio; all results are resolved in parallel |
| TLS | rustls (no OpenSSL, fully static musl builds) |
| Tags | lofty |
| Interaction | inquire |
Protocol notes (in Chinese): [`docs/qq-protocol.md`](https://docs.rs/crate/uta/latest/source/docs/qq-protocol.md), [`docs/netease-protocol.md`](https://docs.rs/crate/uta/latest/source/docs/netease-protocol.md).
The full config template: [`config.example.toml`](https://docs.rs/crate/uta/latest/source/config.example.toml).
## Building
```bash
cargo build --release
# Fully static Linux binary (x86_64-unknown-linux-musl, no dynamic dependencies)
./scripts/build-musl.sh
# output: target/x86_64-unknown-linux-musl/release/uta
```
`ring` (used by rustls) contains C code, so the musl build needs a C compiler targeting musl. The script looks for, in order:
the `CC_x86_64_unknown_linux_musl` environment variable → `x86_64-linux-musl-gcc` / `musl-gcc`
(Debian/Ubuntu: `apt install musl-tools`) → `zig`.
### NixOS / Nix
The repository ships a `flake.nix` (rust-overlay stable + rust-analyzer + musl target + musl gcc); with direnv it loads automatically:
```bash
direnv allow # first time; or run `nix develop` manually
cargo test
scripts/build-musl.sh # the devshell sets CC_x86_64_unknown_linux_musl
```
The toolchain version is pinned by `flake.lock`; upgrade with `nix flake update rust-overlay`.
### CI and releases
- `.github/workflows/ci.yml`: on pushes to main and PRs — `fmt --check`, `clippy -D warnings` (also with `--no-default-features`),
`cargo test`, `cargo package`, and a static musl build. Network tests are marked `#[ignore]` and don't run in CI.
- `.github/workflows/release.yml`: on `v*` tags — tests and builds for Linux musl, macOS (arm64 / x86_64) and Windows x64,
publishes all archives (with `.sha256`) to GitHub Releases, then publishes to crates.io
(token from the `CRATE_TOKEN` secret of the `crate` environment). Manual runs only build, without publishing.
The tag must match the version in `Cargo.toml`:
```bash
# bump `version` in Cargo.toml (e.g. 0.2.0) and commit first
git tag v0.2.0 && git push origin v0.2.0
```
## Out of scope (for now)
- Other platforms (e.g. Kugou: download links are blocked by risk control without login)
- QMCv2 decryption of encrypted VIP formats (`.mflac` / `.mgg`)
- Login cookies / VIP credentials
- The slow and unstable third-party endpoints from musicdl's lower tiers
## License
[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/) — non-commercial use only.