# Companion API Python FastAPI server running on Raspberry Pi 5 inside Docker. Provides metadata editing, Smart DJ analysis, visualizer precomputation, lyrics, and library management. ## Deployment ```bash # On the Pi, from ~/docker/navidrome/ docker compose build music-companion && docker compose up -d music-companion ``` - Port: 8000 - Music dir inside container: `/music` (mapped from `/home/pi/navidrome/music`) - Navidrome DB: `/navidrome_data/navidrome.db` (read-only) - Companion data: `/app/data/` (smart_dj.db, vis_cache, cover_art, tag backups) ## Architecture - `main.py` is the entire server (~3800 lines, single file) - `Dockerfile` + `docker-compose.yml` for containerization - `diagnose.py` — standalone diagnostic script - `pre_analyze.py` — bulk pre-analysis for Smart DJ profiles ## Critical Rules ### Tag Safety - ALL tag writes go through `apply_tags()` (single track) or `apply_tags_dict()` (batch) - Both functions call `backup_tags()` FIRST and abort with `RuntimeError` if backup fails - Files are NEVER modified without a backup safety net - `enforce_tag_whitelist()` has separate paths: - FLAC/OGG/Opus: checks against `NAVIDROME_TAGS` (Vorbis Comment names) - MP3: checks against `ID3_FRAME_WHITELIST` (ID3v2 frame IDs like TPE1, TALB) - AIFF: uses `mutagen.aiff.AIFF` with raw ID3 frames (easy=True returns None for AIFF) - NEVER use `audio.delete()` in restore — it destroys APIC (album art) and USLT (lyrics) ### Backup System - Dual-key backups: `_backup_key(full_path)` + `_backup_key_rel(relative_path)` - `_find_backup()` tries 4 strategies (current path, current rel, original path, original rel) - Survives file moves from `/bulk-fix` restructure - `save_batch_manifest()` stores: batch_id, paths, tags_changed, affected_albums/artists, edit_type, is_reverted - Single-track edits (`PATCH /edit-metadata`) also create manifests with `edit_type: "single"` - Double undo prevented: `is_reverted` flag checked, HTTP 409 on repeat ### Path Resolution - `resolve_path()` has 5 fallback strategies for finding files - Handles URL encoding, NFC unicode normalization, library folder prefixes - Falls back to Companion songs table and fuzzy title matching ## Common Pitfalls - `MutagenFile(path, easy=True)` returns `None` for AIFF — always check - `datetime.utcnow().isoformat()` produces timestamps WITHOUT timezone suffix — Swift must parse with fallback formats - `ffmpeg` subprocess can hang on corrupt files — 120s timeout with process kill - Pi has limited CPU (4 cores) — `deploy.resources.limits.cpus: '2.0'` in docker-compose