coverart-cli
Fill the missing covers in your music library — embed and sidecar in one pass.
What it does
Most cover-art tools only embed or only sidecar. That breaks across players —
Subsonic apps (Amperfy, Symfonium) read tags, Plex / Jellyfin prefer cover.jpg,
Apple Music wants embedded. coverart-cli does both in one pass and ships an
HTML coverage report so you can see what's still missing.
Install
pipx install coverart-cli
Requires Python 3.11 or newer. Upgrade an existing installation with
pipx upgrade coverart-cli.
Quick start
# 1. Preview the exact library first; this performs no writes
coverart ~/Music --dry-run -v
# 2. Fetch + embed + sidecar (free providers, no key needed)
coverart ~/Music
# Optional: add Last.fm as the first provider
LASTFM_API_KEY=your-key coverart ~/Music
# Just generate the coverage report
coverart ~/Music --report-only --report-html report.html
# Only write cover.jpg files; leave audio tags unchanged
coverart ~/Music --no-embed
Run coverart --help for the full flag list.
By default, an album is complete only when it has a usable sidecar and every supported audio file has embedded artwork. If a local sidecar already exists, it is reused to fill missing embeds without a network request. Symbolic links to album directories, audio files, or sidecars are ignored, so writes stay inside the selected library.
Before the first non-dry run, keep a backup of irreplaceable music files. Tag writes are handled by Mutagen, but any bulk metadata operation deserves a recovery path.
Config file
Save your defaults so you don't have to repeat flags:
# ~/.config/coverart-cli/config.toml
lastfm_key = "your-key"
min_bytes = 30000
replace_smaller = true
no_musicbrainz = false
Lookup order (later wins): built-in defaults →
~/.config/coverart-cli/config.toml → ./coverart.toml → --config PATH →
CLI flags. LASTFM_API_KEY overrides the configured key unless
--lastfm-key is supplied. Run coverart ~/Music afterwards with no repeated
flags.
Sources
Tried in order until a cover is found:
- Last.fm —
album.getinfo(needs a free API key) - iTunes — Apple Music's public search, no key
- Deezer — public API, no key
- MusicBrainz + Cover Art Archive — fallback for niche releases
Album and artist names are sent to the enabled providers over HTTPS. Download URLs and redirects are restricted to the providers' API and image hosts; arbitrary hosts and non-HTTPS URLs are rejected.
Supported formats
MP3 (ID3 APIC), M4A/M4B/MP4 (covr atom), FLAC (Picture block), Ogg Vorbis / Opus (metadata_block_picture).
Common workflows
| Goal | Command |
|---|---|
| Preview all changes | coverart ~/Music --dry-run -v |
| Upgrade small artwork | coverart ~/Music --min-bytes 30000 --replace-smaller |
| Embed only | coverart ~/Music --no-sidecar |
| Sidecars only | coverart ~/Music --no-embed |
| Disable directory-name fallback | coverart ~/Music --no-fallback-dirnames |
| Export misses | coverart ~/Music --missing-csv missing.csv |
| Build an HTML report | coverart ~/Music --report-only --report-html report.html |
Use --workers 1 for deterministic serial processing or when a provider is
rate-limiting heavily. MusicBrainz requests are always rate-limited internally.
Programmatic use
from pathlib import Path
from coverart_cli.core import RunOptions, run
from coverart_cli.providers import ITunesProvider, DeezerProvider
stats = run(RunOptions(
root=Path("~/Music").expanduser(),
providers=[ITunesProvider(), DeezerProvider()],
))
print(stats.fetched_from, stats.not_found)
Alternatives
| Tool | When to pick it |
|---|---|
| sacad | Best match rate; Rust binary, more sources |
| get-cover-art | Battle-tested Python API |
beets fetchart |
Already using beets for everything else |
coverart-cli (this) |
You want an HTML report plus embed/sidecar dual output |
Development
git clone https://github.com/buettgen-app/coverart-cli && cd coverart-cli
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check . && pyrefly check && pytest
Releases
Releases are prepared by release-please
and published from an immutable Git tag via PyPI Trusted Publishing.
Commits to main follow Conventional Commits:
| Commit prefix | Effect on next release |
|---|---|
feat: … |
minor bump (0.3.0 → 0.4.0) |
fix: … |
patch bump (0.3.0 → 0.3.1) |
feat!: … / BREAKING CHANGE: |
major bump (0.3.0 → 1.0.0) |
docs:, refactor:, perf: |
changelog entry, no bump |
chore:, ci:, test: |
hidden in changelog |
The Prepare release workflow uses the repository-scoped GITHUB_TOKEN to
open and update one rolling Release PR. Merging that PR creates the version tag
and publishes the GitHub Release. Because GitHub intentionally suppresses the
release event created by its workflow token, the same trusted run sends an
authenticated repository dispatch to the isolated Publish release workflow.
That workflow verifies the source workflow run, release tag, exact commit,
package metadata, and changelog. It builds one wheel and source distribution,
attaches those exact files to the GitHub Release, and publishes them to PyPI
with OIDC attestations.
No release App, private key, or personal access token is required. In
Settings → Actions → General → Workflow permissions, enable Allow GitHub
Actions to create and approve pull requests. GitHub may hold checks on a
Release Please PR until a maintainer selects Approve workflows to run; this
approval remains part of the manual release gate.
The PyPI Trusted Publisher must be configured for GitHub owner buettgen-app,
repository coverart-cli, workflow release.yml, and environment pypi. That filename is the stable
publish identity even though Release Please itself runs in
prepare-release.yml. Do not create release tags or upload distributions by
hand. A failed publish can be rerun from the same GitHub Actions run without
introducing a second release path. Historical releases can be recovered by
manually running the same workflow from main; it accepts only an existing
published release tag and applies every normal validation and test gate. Pull
requests and Release PRs require an explicit merge after branch protection
passes. Verified low-risk Dependabot patch updates, development-only minor
updates, and GitHub Actions minor updates use GitHub's native auto-merge; major
and production minor updates remain manual.
GitHub Actions changes are gated by the repository's Zizmor security lint;
third-party AI review remains advisory so availability limits cannot block
security updates.
License
Metadata
Release files for coverart-cli 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| coverart_cli-0.6.0.tar.gz | 2.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| coverart_cli-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.5 MB
Release files / coverart_cli-0.6.0.tar.gz
| Download URL | coverart_cli-0.6.0.tar.gz |
|---|---|
| Size | 2.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c67df1166f712709f2381ebddea123181e94285f16de136f3bb7044a5c4405a6
|
|
BLAKE2b-256 checksum How to use checksums |
03f44cf35016818588a180ae179c7d132f0ce1200d6c469a1ebf435da8fbeb45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.
Transparency logRelease files / coverart_cli-0.6.0-py3-none-any.whl
| Download URL | coverart_cli-0.6.0-py3-none-any.whl |
|---|---|
| Size | 33.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d4b38bded41452786076793a2dccc0cb5a34aa5cfc3869428f2ca1c40ae41cf
|
|
BLAKE2b-256 checksum How to use checksums |
a12ba40157082deada565dc5fa9ab7ecb7ba20e9641ee2f746c6415abbd13199
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.
Transparency log