coverart-cli
Album artwork in your audio tags, beside your tracks, and in a searchable report.
Fill missing artwork from iTunes, Deezer, MusicBrainz / Cover Art Archive, and optionally Last.fm. One run embeds artwork into supported audio files and writes a sidecar for players that read covers from the album folder. Existing local artwork is reused where possible.
Quick start · Workflows · Configuration · Reports · Development · Changelog
Install
Requires Python 3.11+ on Linux, macOS, or Windows 10 (1709+) / 11. Windows runs natively without WSL on local NTFS volumes. Junctions, symbolic links and other reparse points are skipped; directory-mounted volumes, UNC/network shares and non-NTFS volumes and SUBST drive mappings are unsupported. EFS-encrypted and multiply linked audio files are rejected before copying.
Install in an isolated tool environment with either pipx or uv:
pipx install coverart-cli
# Or:
uv tool install coverart-cli
coverart --version
Upgrade with pipx upgrade coverart-cli or uv tool upgrade coverart-cli.
PyPI contains the latest published release; this README also documents repository
changes that may be awaiting release.
Windows (PowerShell)
Install Python 3.15 (recommended; 3.11+ supported) and pipx, then open a new PowerShell window after ensurepath:
py -m pip install --user pipx
py -m pipx ensurepath
# Until the next PyPI release, install the Windows implementation from main
# (requires Git for Windows):
pipx install "git+https://github.com/buettgen-app/coverart-cli.git@main"
coverart --version
coverart "$env:USERPROFILE\Music" --dry-run -v
coverart "$env:USERPROFILE\Music"
coverart "$env:USERPROFILE\Music" --report-only --report-html .\report.html
Start-Process .\report.html
Quote paths containing spaces. Set a Last.fm key with $env:LASTFM_API_KEY = "YOUR_KEY".
Windows defaults are read from %APPDATA%\coverart-cli\config.toml; an absolute
XDG_CONFIG_HOME overrides that location. Use TOML literal strings for Windows
paths, for example report_html = 'C:\Reports\coverage.html'.
Close players or other applications holding files open before embedding artwork.
Windows denies a conflicting write or rename while library handles are held;
locked or read-only audio files produce an error and keep their original bytes.
Write directories must be controlled by your account, SYSTEM or local Administrators.
Directories granting other principals file creation, deletion or ACL control are
refused before staging and checked again before replacement; read-only sharing
and report generation remain available. Existing destinations are held against
in-place writes (including encryption changes) through the atomic replacement.
Audio staging files receive the source owner, group, DACL and SACL at creation, before private audio is
copied; if the complete security policy cannot be read or retained, replacement
fails safely. Embedding therefore requires an already assigned SeSecurityPrivilege
(typically available in an elevated Windows session); the tool never grants or
elevates privileges. It enables this privilege only on a temporary private thread
token while cloning security metadata, then restores the original token. Creation, access
and modification times are retained. Named NTFS streams are copied through the verified source handle. A
handle-bound atomic rename replaces the destination entry without following a
substituted symlink. Staging handles remain open through commit. Hidden, system,
indexing and compression attributes are retained; unsupported source attributes
fail safely. Failed renames leave the original intact.
Quick start
# Preview fetching and planned changes without writing files.
coverart ~/Music --dry-run -v
# Fill missing embedded artwork and album-folder sidecars.
coverart ~/Music
# Inspect coverage locally without contacting any provider.
coverart ~/Music --report-only --report-html report.html
Open report.html in your browser. The default providers require no API key.
To enable Last.fm as the first provider, set LASTFM_API_KEY in your environment;
get a key from Last.fm.
Dry runs still contact providers and download candidate images. They write
neither tags nor sidecars and cannot be combined with --report-html or
--missing-csv. Use a separate report-only run for a local coverage check.
How it works
- Discover supported audio files in album directories, including the selected root when it is itself an album folder.
- Read album artist and album tags; fall back to
Artist/Album/directory names when readable tags are absent. - Reuse a suitable local sidecar to fill missing embeds, or try enabled providers in order until valid artwork is found.
- Write the requested outputs, retaining existing artwork unless an upgrade threshold is configured.
Music/
└── Artist/
└── Album/
├── 01 - Track.mp3
├── 02 - Track.mp3
└── cover.jpg
A root with conflicting album identities, or a mix of readable and unreadable identities, is excluded from writing and reports. Compilations may have different track artists when their album identity agrees. Organize loose tracks into album directories before processing them. By default, processing skips an album only when a usable sidecar exists and every supported track has embedded artwork. Disabling an output removes that requirement from the completion check.
Common workflows
| Goal | Command |
|---|---|
| Preview a library | coverart ~/Music --dry-run -v |
| Process one album | coverart "$HOME/Music/Artist/Album" |
| Write sidecars only | coverart ~/Music --no-embed |
| Embed artwork only | coverart ~/Music --no-sidecar |
| Upgrade artwork below 30,000 bytes | coverart ~/Music --min-bytes 30000 |
| Use tags without directory fallback | coverart ~/Music --no-fallback-dirnames |
| Export misses and processing failures | coverart ~/Music --missing-csv missing.csv |
| Generate a report after processing | coverart ~/Music --report-html report.html |
| Generate a report without fetching | coverart ~/Music --report-only --report-html report.html |
| Omit report thumbnails | coverart ~/Music --report-only --report-html report.html --no-thumbs |
| Process albums serially | coverart ~/Music --workers 1 |
--min-bytes applies to embedded artwork and sidecars. Byte size is an upgrade
heuristic, not a resolution or visual-quality score. Larger existing artwork is
kept by default; --replace-smaller disables that protection, allowing a
below-threshold cover to be replaced even by a smaller downloaded image.
Four workers run by default. MusicBrainz requests are rate-limited internally.
Use -v for progress, -vv for debugging, and coverart --help for all options.
HTML report
The self-contained HTML report includes album search, source filters, coverage statistics, and light/dark themes. Viewing it requires no server. Thumbnails come from local sidecars and have a shared size budget; embedded-only albums can have coverage without a thumbnail.
Report coverage means an album has a usable sidecar or artwork embedded in every
supported track. This is broader than the default processing completion check,
which requires both. Provider provenance is not stored per album: existing
artwork is classified as covered with an unknown origin. Reports contain library,
artist and album names plus optional artwork; review their contents before sharing.
For a directly selected album directory, reports use verified tag identities and
show Unknown Artist when the tags cannot be read, avoiding disclosure of the
parent directory name. Hidden tracks are excluded from counts and coverage.
Configuration
Store defaults in ~/.config/coverart-cli/config.toml (or under
$XDG_CONFIG_HOME/coverart-cli/config.toml):
workers = 4
min_bytes = 30000
no_embed = false
no_sidecar = false
no_musicbrainz = false
# Optional: identify yourself when contacting MusicBrainz.
user_agent = "coverart-cli (mailto:you@example.com)"
Configuration is merged in this order; later values win:
Built-in defaults → user config → ./coverart.toml → --config PATH
→ LASTFM_API_KEY (key only) → explicit CLI flags
Keys match CLI option names with underscores: no_embed, min_bytes,
report_html, and so on. Unknown keys and invalid values are ignored with a
warning. Keep API keys out of committed project configuration.
Explicit flags override configured values even when equal to the built-in
default. Use --embed, --sidecar, --no-dry-run, --no-report-html, or
--no-missing-csv to clear the corresponding configured action.
Providers and formats
Providers are tried in the following order; Last.fm is skipped without a key.
| Provider | API key | Disable with |
|---|---|---|
| Last.fm | Required | --no-lastfm |
| iTunes Search | None | --no-itunes |
| Deezer | None | --no-deezer |
| MusicBrainz / Cover Art Archive | None | --no-musicbrainz |
| Audio formats | Embedded artwork |
|---|---|
| MP3 | ID3 APIC |
| M4A, M4B, MP4 | covr atom |
| FLAC | Picture block |
| Ogg Vorbis, Opus | metadata_block_picture |
Recognized local sidecars are cover.jpg, cover.png, folder.jpg, and
folder.png. Artwork is validated as JPEG or PNG before use: at most 20 MiB,
4,096 pixels per dimension, and 16,777,216 total pixels. The normal minimum
usable cover size is 2,000 bytes. Sidecar output follows the validated image
format (cover.jpg or cover.png).
File safety and privacy
Album and artist names are sent to enabled providers over HTTPS. Provider downloads and redirects are restricted to their permitted API/image hosts. Report-only mode makes no provider requests.
Library traversal skips hidden entries and does not follow symbolic links to album folders, tracks, or covers. Tag writes use verified file identities and copy-on-write replacement. Keep a backup before bulk metadata changes and avoid moving or renaming the library while a run is writing: a POSIX directory rename does not revoke an already-open descriptor.
Read SECURITY.md for trust boundaries and vulnerability reporting.
MP3 artwork writes retain existing ID3v2.3/v2.4 versions and tag values; legacy ID3v1/v2.2 tags are upgraded to v2.3 and new tags use v2.4.
Automation and Python API
CLI exit codes are 0 for a completed run (ordinary cover misses are allowed),
1 for processing or I/O errors, and 2 for invalid options or unsupported
platform capabilities. Use --missing-csv to assess missing artwork separately.
from pathlib import Path
from coverart_cli.core import RunOptions, run
from coverart_cli.providers import DeezerProvider, ITunesProvider
stats = run(
RunOptions(
root=Path.home() / "Music",
providers=[ITunesProvider(), DeezerProvider()],
dry_run=True,
)
)
print(stats.fetched_from, stats.not_found, stats.errors)
Development
Dependency updates are configured through Renovate, including Python hash locks, GitHub Actions and pre-commit hooks.
Use the checked-in hash locks and the same editable-install approach as CI:
git clone https://github.com/buettgen-app/coverart-cli.git
cd coverart-cli
python3.15 -m venv .venv
source .venv/bin/activate
python -m pip install --require-hashes -r requirements-ci.txt
python -m pip install --no-deps --no-build-isolation -e .
ruff check .
ruff format --check .
pyrefly check
pytest --cov=coverart_cli --cov=scripts --cov-report=term-missing --cov-fail-under=87 \
--cov-report=json:coverage-results/report.json \
--cov-report=xml:coverage-results/coverage.xml \
--cov-report=html:coverage-results/html
python scripts/check_coverage.py coverage-results/report.json --profile common
Install FFmpeg to enable the audio-stream preservation oracle used in CI. The Linux test matrix covers Python 3.11–3.15; additional jobs check macOS metadata preservation and native Windows processing, handle locking and junction rejection. CodeQL, Zizmor, dependency review, fuzzing, and reproducible artifact builds provide separate checks.
Test coverage measures executed code, separately from the report's album-art
coverage. Line coverage counts executed statements; branch coverage checks
both outcomes of decisions, including failures and rejected inputs. Neither proves
that a test asserts the right behavior or that the program has no vulnerabilities.
CI requires at least 90% lines and 78% branches for shared code, with a separate
native Windows measurement. Download coverage-* artifacts from a CI run and open
html/index.html to see missed lines and branches. See the
coverage policy for scopes and gates.
Run python scripts/check_repository.py to check tracked files for private
contact details and local artifacts. See repository hygiene and CI gates
for privacy-check limits and the required-check configuration.
Report bugs or request features in GitHub Issues. For releases, protected tags, PyPI Trusted Publishing, and recovery procedures, see the release and publishing guide.
License
Metadata
Release files for coverart-cli 0.7.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.7.0.tar.gz | 183.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| coverart_cli-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 243.4 kB
Release files / coverart_cli-0.7.0.tar.gz
| Download URL | coverart_cli-0.7.0.tar.gz |
|---|---|
| Size | 183.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
986bdd7ec7e35b0944a7b684332f2cdc446768f4e0b9a1cb7f68eed5def1aa67
|
|
BLAKE2b-256 checksum How to use checksums |
db6d18fb05da309aca00c1ec933e5ad0009b745482aa70bed27541c0bf3555ea
|
| 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 Oct 11, 2026.
Transparency logRelease files / coverart_cli-0.7.0-py3-none-any.whl
| Download URL | coverart_cli-0.7.0-py3-none-any.whl |
|---|---|
| Size | 60.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
22c57595af08e9811724280205815affef0de6683bd9b811e5d6758f2eb4ecf1
|
|
BLAKE2b-256 checksum How to use checksums |
efe0d63deb8c71d2917ca0ada41fbe514f88a8bd5e35cdae7c72b87748adedf1
|
| 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 Oct 11, 2026.
Transparency log