Covers are stored with the assignment and served by the service, since Music Assistant's plain-http image URLs are blocked on an https page. Search results load covers through a signed /covers proxy. Adds a fetch-covers command for imported assignments, a migration for existing databases, and sorting assignments by artist, recently assigned or recently scanned, with both dates shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.6 KiB
Tag albums
Tells Home Assistant which album to play when an RFID card (a Tag) is scanned, and provides a web page to give new cards an album (an Assignment). See CONTEXT.md for the vocabulary.
How it fits together:
- The ESPHome RFID reader sends
tag_scannedto Home Assistant, as before. - HA's
Play album by tagscript callsPOST /api/scans. The service records the Scan and answers with the album, orassigned: false. - A card the service hasn't seen before becomes an Unassigned tag. It appears on the web page within a few seconds, where you search the Music Assistant library and assign an album.
- When a card is removed, HA's
Stop album by tagscript callsGET /api/tags/{tag_id}. It stops the player only if that tag has an album.
Demo
A card is scanned while the page is open, shows up under Unassigned tags, and gets an album from a live search of the Music Assistant library:
API
Both endpoints need Authorization: Bearer <API_TOKEN>.
| Endpoint | Purpose |
|---|---|
POST /api/scans {"tag_id": "53-13-0B-2A"} |
Record a Scan; creates the Tag on first sight. Returns {"tag_id", "assigned": true, "artist", "album"} or {"tag_id", "assigned": false}. |
GET /api/tags/{tag_id} |
Read-only lookup, same response. 404 if the tag has never been seen. |
Tag IDs are normalised to upper case.
The web page (/) has no login: keep the service on the LAN.
Configuration
Environment variables:
| Variable | Meaning |
|---|---|
API_TOKEN |
Shared secret Home Assistant sends in the Authorization header |
HA_URL |
Home Assistant base URL, e.g. https://ha.home.knbg |
HA_TOKEN |
HA long-lived access token, used to search the Music Assistant library |
DB_PATH |
SQLite file (default tag_albums.sqlite3); its directory must be writable |
HTTPS to HA is verified against the system trust store, so a home CA must be installed on the host. If it isn't, point SSL_CERT_FILE at the CA bundle.
Development
uv sync
uv run pytest
API_TOKEN=dev HA_URL=https://ha.home.knbg HA_TOKEN=... uv run tag-albums serve --port 8087
Running with Docker
For local testing. The container has to trust the home CA to reach HA, so mount the CA certificate and point SSL_CERT_FILE at it:
docker build -t tag-albums .
docker run --rm -p 8087:8087 -v tag-albums-data:/data \
-e API_TOKEN=dev -e HA_URL=https://ha.home.knbg -e HA_TOKEN=... \
-v /usr/local/share/ca-certificates/KNBG_Root_CA_306526670621344964268144771797781855543.crt:/ca.crt:ro \
-e SSL_CERT_FILE=/ca.crt \
tag-albums
To import assignments, run tag-albums import-yaml inside the container, e.g. docker run --rm -v tag-albums-data:/data -v $PWD/tag_albums.yaml:/import.yaml:ro tag-albums tag-albums import-yaml /import.yaml.
Deploying in a FreeBSD jail
pkg install python313 py313-sqlite3 py313-uv git rust # any Python >= 3.11; sqlite3 is a separate package
# uv compiles pydantic-core (Rust) and a few C extensions from source, since
# FreeBSD has no prebuilt wheels. The jail needs the base system's development
# files for that (e.g. /usr/lib/Scrt1.o, crti.o, libc): on a pkgbase jail,
# install the FreeBSD-*-dev packages (or the devel set) from the FreeBSD-base repo.
pw useradd tagalbums -d /nonexistent -s /usr/sbin/nologin
git clone <repo> /usr/local/tag-albums && cd /usr/local/tag-albums && uv sync --no-dev
install -d -o tagalbums /var/db/tag_albums
cat > /usr/local/etc/tag_albums.env <<'EOF'
API_TOKEN=<random, e.g. openssl rand -hex 32>
HA_URL=https://ha.home.knbg
HA_TOKEN=<HA long-lived token>
DB_PATH=/var/db/tag_albums/tag_albums.sqlite3
EOF
chmod 600 /usr/local/etc/tag_albums.env
install -m 755 deploy/rc.d/tag_albums /usr/local/etc/rc.d/tag_albums
sysrc tag_albums_enable=YES
service tag_albums start
Logs go to syslog with the tag tag_albums. The port defaults to 8087 (sysrc tag_albums_port=...).
Importing the old tag_albums.yaml
One time, before switching Home Assistant over:
cd /usr/local/tag-albums
env DB_PATH=/var/db/tag_albums/tag_albums.sqlite3 \
su -m tagalbums -c '.venv/bin/tag-albums import-yaml /path/to/tag_albums.yaml'
Album covers
The service stores each assignment's cover and serves it itself. Music Assistant's image URLs are plain http on another host, which browsers block on an https page. Albums assigned from the web page get their cover right away. Imported assignments have none, so fetch them once (this reads the env file for HA access):
cd /usr/local/tag-albums
su -m tagalbums -c 'set -a; . /usr/local/etc/tag_albums.env; set +a; .venv/bin/tag-albums fetch-covers'
It lists the albums Music Assistant has no cover for; those keep the grey placeholder. Running it again only looks at assignments still missing a cover.
Updating
cd /usr/local/tag-albums && git pull && uv sync --no-dev
service tag_albums restart
Database changes are applied automatically at startup.
Home Assistant side
These pieces live in the home-assistant-config repo:
configuration.yaml: tworest_commands,tag_albums_scanandtag_albums_lookup, pointing athttps://tag-albums.home.knbg(a reverse proxy in front of port 8087). They setverify_ssl: false, because the certificate comes from the home CA and HA only trusts its built-in list of public CAs.secrets.yaml:tag_albums_auth: "Bearer <API_TOKEN>".scripts.yaml:play_album_by_tagandstop_album_by_tagcall those commands.
If the service is down, a scan plays nothing and play_album_by_tag writes a warning to the HA log.
