metaedit

Browse a Jellyfin music library, read artist / album / track metadata from the Last.fm API, keep a persistent local archive of every Last.fm response, review a field-level diff, and apply the accepted changes back to Jellyfin.

It is a self-hosted tool for one person with a music library and a taste for tidier metadata — not a sync service and not a plugin.

Library → candidates → diff → apply → undo.

What it does

Four properties hold on every write, and each is tested:

  1. The payload is always the complete writable field set. POST /Items/{id} is a full overwrite, so a field omitted from the body is nulled. Anything you did not select is carried through at its current value instead.
  2. A snapshot is written before the item is. A failed write leaves a harmless orphaned snapshot; the reverse order would leave an unrecoverable edit.
  3. A stale Etag is refused with 409 and nothing is written, so a reviewed change cannot silently undo an edit made in Jellyfin meanwhile.
  4. Only planned fields may be selected. Naming any other field is a 422, because on a full-overwrite API a field the caller can name but we did not plan is one they could destroy.

Two more things are deliberate, not missing:

Quickstart

You need Docker with Compose, a Jellyfin server, and a Last.fm API key:

Requirement Details
Jellyfin ≥ 10.9 The item-update semantics the write path depends on.
Jellyfin administrator API key Dashboard → API Keys. Metadata writes are gated on the RequiresElevation policy, so a non-admin key can browse but not save; GET /api/info tells you which one you have.
Last.fm API key From https://www.last.fm/api/account/create. Read-only usage — no user auth.
Postgres 18 Runs in the compose stack; required for the archive’s declarative monthly partitioning.
git clone https://github.com/sredevopsorg/metaedit.git
cd metaedit

cp .env.example .env
# edit .env: JELLYFIN_URL, JELLYFIN_API_KEY, LASTFM_API_KEY, POSTGRES_PASSWORD

docker compose up -d --build

Then open http://127.0.0.1:8080.

The app binds to 127.0.0.1 on purpose. It holds an administrator Jellyfin key and has no authentication of its own. Put an authenticating proxy in front of it before exposing it anywhere.

The stack runs Postgres, applies migrations as a one-shot migrate service, and only then starts the app. The first visit is the Library screen: search for something, open an item, fetch its Last.fm candidates, and review the diff.

Every knob the app reads is listed with its default in the configuration reference; .env.example is the template to copy and the reference is the complete list.

Documentation

Doc For
Configuration Every environment variable, its default, and what it changes
Development Running from a checkout, checks, tests, the API contract
API Every endpoint, and the rules the write path follows
Operations The archive, the CLI, retention, live-verified server behaviour
Architecture Status, the UI, layering, known deviations
ADRs Why the design is what it is
Design notes Implementation contracts fixed ahead of a phase
Changelog What changed, release by release

Attribution

Metadata originates from Last.fm. The UI links every Last.fm-derived value back to the corresponding artist, album or track page on https://www.last.fm, as the API Terms of Service require. This tool is for personal, non-commercial use.