uta 0.1.2

Command-line music search and downloader for QQ Music and NetEase Cloud Music, lossless first, shipped as a single static binary. For learning and research only; non-commercial use.
uta-0.1.2 is not a library.

uta

简体中文

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, 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) — without third-party endpoints you usually won't get lossless audio.

Shell completion (bash / zsh / fish / powershell / elvish):

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

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 search "晴天" -q sq              # cap quality: master|atmos|hires|sq|hq|std (default master, often 150–200MB per track)
uta get <id|link>...                 # download by song id or song link (QQ songmid / songid, NetEase id)
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

uta search "田馥甄" -s netease        # NetEase Cloud Music (-s qq|netease, "163" is an alias for netease; default qq)
uta get 'https://music.163.com/#/song?id=1481929839' <QQ link>   # links are recognized automatically and can be mixed
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

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

Aspect Choice
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, docs/netease-protocol.md. The full config template: config.example.toml.

Building

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:

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:
# 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 — non-commercial use only.