docs: complete tag editing project research

This commit is contained in:
2026-03-16 16:29:49 -04:00
parent 909958c03a
commit a6752c8295
5 changed files with 1412 additions and 271 deletions
+279 -52
View File
@@ -1,73 +1,300 @@
# Features Research: Multi-Library Support
# Feature Landscape: Tag Editing
**Researched:** 2026-03-08
**Confidence:** HIGH
**Domain:** Metadata tag editing in desktop music players
**Researched:** 2026-03-16
**Confidence:** HIGH (based on analysis of MusicBee, foobar2000, Kid3, Mp3tag, Picard patterns + Hydrogenaudio tag standards + existing YellowJacket codebase)
## How Mature Players Handle Multiple Libraries
## How Desktop Music Players Implement Tag Editing
| Player | Model | Libraries Separate? | Cross-Library Playlists? |
|--------|-------|--------------------|-----------------------|
| foobar2000 | Multiple folders, one merged library | No — all folders merge | N/A (one library) |
| MusicBee | Multiple folders per library, separate library databases | Yes (separate DBs) | No |
| Plex | Separate typed libraries, multiple folders each | Yes (fully isolated) | No |
| Jellyfin | Virtual collections with multiple paths | Yes | No |
| Navidrome | Named libraries with user access control | Yes (with multi-select merge) | Yes |
| Roon | Watched folders, one unified library | No — all merge | N/A (one library) |
### Reference Players Analyzed
### Desktop Player Pattern (foobar2000, Roon)
| Player | Single Edit | Batch Edit | Cover Art Edit | Auto-Tag | Tag Format Handling |
|--------|------------|------------|---------------|----------|-------------------|
| foobar2000 | Properties dialog | Multi-select → Properties (shared fields) | Embed/remove from Properties | Via plugins | ID3v2, Vorbis, APEv2; configurable write format |
| MusicBee | Inline + dialog | Multi-select → Edit panel (keep/clear/set) | Drag-drop + file picker + paste | Built-in | ID3v2.3/2.4, Vorbis; auto-convert on write |
| Kid3 | Side panel + dialog | Multi-select → panel applies to all | File picker + paste + drag | MusicBrainz/Discogs | ID3v1/v2, Vorbis, APEv2; shows raw frames |
| Mp3tag | List view + panel | Inherent (panel always applies to selection) | Drag-drop + file picker + clipboard | Tag Sources | All formats; extended tag view |
| Picard | Panel per file/album | Album-level batch via MusicBrainz match | Automatic via MusicBrainz + manual | Core feature | All formats; submission to MusicBrainz |
All folders contribute to one unified library. No folder-level filtering in default UI. User never thinks about "which folder."
### Common Patterns Across All Players
### Server Pattern (Plex, Jellyfin, Navidrome)
**Single-track editing:**
- Dialog/panel with labeled fields, plain text inputs
- Title, artist, album shown prominently (larger/bolder)
- Cover art displayed alongside fields (150-250px)
- Numeric fields (year, track #, disc #) use number inputs or constrained text
- Genre usually free-text (not dropdown — genre lists are opinionated and incomplete)
- Non-editable properties shown separately (bitrate, sample rate, file path, file size)
- Save button writes to file → updates database
- Cancel discards all changes
Separate libraries with access control. More suited to multi-user server apps.
**Batch editing (the critical UX challenge):**
- Select multiple tracks → open editor
- Fields show current value if identical across selection, blank/placeholder if mixed
- A "keep" / "don't change" / "mixed" indicator distinguishes "empty because cleared" from "empty because mixed"
- User types a value → it applies to ALL selected tracks on save
- Fields left unchanged preserve each track's individual value
- Common pattern: three-state per field — "keep original" (default), "set to value", "clear"
- Track number is special: batch edit typically excludes it (each track needs unique number) OR offers auto-number (sequential from N)
### YellowJacket Fit
**Cover art editing:**
- Display current embedded art (or "no cover" placeholder)
- Replace from file: file picker (JPEG, PNG)
- Remove embedded art (less common, but available in Kid3/Mp3tag)
- Cover art in batch edit: applies same image to all selected tracks (common for fixing an album)
- No crop/resize UI — users prepare images externally
- Players typically accept any size but recommend 500-1000px square
Desktop player = **merged by default**, with optional filter. Follows foobar2000/Roon pattern but adds Navidrome-style library selector for power users.
**File safety:**
- Write-to-temp-then-rename (atomic write) is universal best practice
- Some players (foobar2000) create backups before writing
- All players update their internal database after successful file write (no rescan)
## Feature Classification
### Universal Editable Fields (from Hydrogenaudio Tag Mapping + player analysis)
### Table Stakes (Must Have)
**Basic (ID3v1-level, universal compatibility):**
- Title, Artist, Album, Year, Genre, Track Number, Comment
| Feature | Complexity | Dependencies |
|---------|-----------|-------------|
| Add multiple watched folders | Low | Config to DB migration |
| Unified merged view (default) | Medium | All browse views, search aggregate |
| Per-folder independent scan | Medium | Scan pipeline scoping |
| Remove folder without data loss | Low | Phantom tracks for playlists |
| Folder status indicators | Low | Scan event system |
| Graceful offline handling | Medium | Guard orphan cleanup |
| Existing playlists unaffected | Low | File-path references already work |
**Standard (ID3v2/Vorbis, widely supported):**
- Album Artist, Composer, Disc Number, Track Total, Disc Total, Lyrics
### Differentiators (Nice to Have)
**Extended (advanced users, format-dependent):**
- BPM, Initial Key, Mood, Label, Catalog Number, ISRC, MusicBrainz IDs
| Feature | Complexity | Dependencies |
|---------|-----------|-------------|
| Filter/narrow by source folder | Medium | UI filter chip, query-level filtering |
| Named libraries | Low | DB stores name + path |
| Per-folder scan concurrency | Low | Extend existing ScanConcurrency per library |
| Folder health dashboard | Medium | Aggregate scan metrics |
## Table Stakes
### Anti-Features (Do NOT Build)
Features users expect. Missing = product feels incomplete.
| Anti-Feature | Why Avoid |
|--------------|-----------|
| Separate databases per library | Breaks unified browse, doubles query logic |
| User/access control per library | Desktop app is single-user |
| Auto-merge/deduplicate across folders | Complex, error-prone, unexpected |
| Library-specific settings/themes | Over-engineering |
| Feature | Why Expected | Complexity | Dependencies | Notes |
|---------|-------------|------------|--------------|-------|
| Single track tag editing (title, artist, album, genre, year, track#, disc#, composer) | Every player with tag editing supports these 8 fields minimum | Medium | Tag writing library, DB update queries, FTS5 reindex | Existing `track-details` dialog has edit mode UI scaffolded (save is no-op TODO) |
| Write tags to MP3 (ID3v2) | MP3 is the most common format; must-have | High | Need tag writing library (dhowden/tag is read-only) | Format-specific: must write ID3v2.3 or ID3v2.4 frames |
| Write tags to FLAC (Vorbis Comments) | FLAC is the standard lossless format | High | Same writing library | Vorbis comments in FLAC metadata block |
| Write tags to OGG (Vorbis Comments) | Already supported for reading | Medium | Same writing library | Same Vorbis comment format as FLAC |
| Write-to-temp-then-rename | File corruption on crash/power loss = unacceptable data loss | Low | `os.Rename` after writing to temp file | Universal best practice; Go stdlib handles this well |
| Inline DB + FTS5 update after tag write | Users expect immediate UI update; forcing rescan is unacceptable | Medium | UPDATE queries for recordings, artist_credit, release_groups, genres; FTS5 search_index rebuild for affected rows | Must update the `track_metadata` VIEW's source tables |
| Batch editing shared fields across multiple selected tracks | Every tag editor supports this; multi-select already exists in track list | High | Batch editor UI component, backend batch write endpoint, progress tracking | The hard UX problem: mixed-value indicators, three-state fields |
| Save confirmation / error feedback | User must know if write succeeded or failed | Low | Event emission, toast/notification UI | Especially important for read-only files or permission errors |
| Cover art set/replace from image file | Fundamental tag editing feature; cover art is visually prominent | Medium | File picker (already have `FrontendUtil.OpenFileDialog`), image embedding in tag write, cover art cache update | Must update both embedded tag and cover art cache + thumbnails |
## Library Removal Patterns
## Differentiators
All players that support library removal:
1. Show confirmation dialog
2. Remove tracks from DB (or mark as missing)
3. Handle playlist references (delete, mark phantom, or leave as-is)
4. Don't delete files from disk
Features that set the product apart. Not expected, but valued.
**YellowJacket approach:** Phantom tracks (preserving metadata) for playlists. Queue tracks cascade-deleted (ephemeral). Orphan cleanup for shared entities via reference counting.
| Feature | Value Proposition | Complexity | Dependencies | Notes |
|---------|------------------|------------|--------------|-------|
| Album artist field editing | Distinguishes VA compilations; power users expect it | Low | One additional field in edit form; already extracted by `dhowden/tag` | Not in PROJECT.md active list but low-hanging fruit |
| Lyrics field editing | Multi-line text editing for embedded lyrics | Low | Textarea in dialog; lyrics field already in `recordings` schema… wait, it's in `TrackMetadata` struct but not shown in track-details UI | Would need multiline input; niche but straightforward |
| Comment field editing | Standard tag field, some users store notes | Low | Already extracted, just needs UI input | Very low effort to include |
| Auto-number tracks in batch edit | Select album tracks → auto-assign sequential track numbers | Low | Frontend logic to generate sequential numbers, apply in batch write | Huge time-saver when retagging an album |
| Dirty indicator / unsaved changes warning | Prevent accidental dialog close with unsaved edits | Low | Track `editValues` diff vs original values | MusicBee and foobar2000 both do this |
| Undo last tag write (restore from backup) | Safety net for mistakes; builds user trust | Medium | Write original tag values to a backup store before overwriting | Most players don't do this — would be a genuine differentiator |
| Cover art remove (strip embedded art) | Some users want to remove bloated embedded art | Low | Write tags without picture data | Available in Kid3/Mp3tag but not most players |
| Cover art paste from clipboard | Quick workflow: copy image from browser → paste into editor | Medium | Clipboard API in WebView, image data extraction | MusicBee supports this; convenient for web-sourced art |
| Progress indicator for batch operations | Visual feedback during multi-file writes (batch of 20+ tracks) | Low | Progress event emission, progress bar in UI | Important when writing to many files (can take seconds per file for FLAC) |
| Total Tracks / Total Discs fields | Part of standard tag spec; power users tag these | Low | Two additional number fields; already in `TrackMetadata` struct | Mp3tag and Kid3 expose these; foobar2000 uses "X/Y" format |
## Offline Handling
## Anti-Features
Universal pattern: Don't delete tracks when source goes offline. Mark as unavailable. Auto-recover on next scan when source returns. Never auto-delete on temporary unavailability.
Features to explicitly NOT build.
| Anti-Feature | Why Avoid | What to Do Instead |
|--------------|-----------|-------------------|
| Inline editing in track list columns | Extremely complex (virtual scrolling + inline inputs + focus management + multi-select conflicts); fragile UX | Use the existing modal dialog approach — click to open editor. This is what foobar2000 does. |
| MusicBrainz auto-tagging / lookup | Massive scope expansion (API integration, fuzzy matching, network dependency); separate milestone material | Defer to future "MusicBrainz browser" milestone already in PROJECT.md |
| Genre dropdown with predefined list | Genre lists are subjective, never complete, frustrate users who use custom genres | Free-text input with optional suggestions from existing genres in DB (future enhancement) |
| Tag format conversion (ID3v1→v2, strip APEv2) | Edge case tool feature; desktop tagger territory (Mp3tag) | Write the "correct" format for each file type; don't expose format internals to users |
| Raw tag frame editing | Power-user-only feature; complex UI for marginal value | Edit semantic fields (title, artist, etc.); abstract away ID3 frames vs Vorbis comments |
| Custom/arbitrary tag field editing | Requires extensible UI, arbitrary field names, format-specific storage concerns | Support the standard fields; users with custom tags use Mp3tag |
| Filename renaming from tags | Common in dedicated taggers (Mp3tag, Kid3) but orthogonal to tag editing; adds file system mutation risk | Out of scope; would need separate file operations system |
| ReplayGain scanning/writing | Separate audio analysis feature, not tag editing | Future milestone if ever; requires DSP analysis |
| Drag-and-drop cover art from external apps | Complex browser/WebView drag interop; unreliable across platforms | File picker is the reliable universal approach |
| Multi-value field editing (multiple artists/genres as separate entries) | ID3v2 and Vorbis support multiple values per field, but the UI complexity is enormous | Store as single string; genre already uses `||` separator internally |
## Feature Dependencies
```
Single Track Edit ──→ Tag Writing Library (MP3/FLAC/OGG)
──→ DB Update Queries (recordings, artist_credit, release_groups, genres)
──→ FTS5 Reindex (search_index)
──→ Event Emission (UI refresh)
Batch Edit ────────→ Single Track Edit (batch = N × single with shared values)
────────→ Mixed-value UI (three-state field indicators)
────────→ Multi-select (already exists in track-list)
Cover Art Edit ───→ Tag Writing Library (picture frame embedding)
───→ Cover Art Cache Update (saveCoverArt + thumbnail generation)
───→ File Picker Dialog (already exists: FrontendUtil.OpenFileDialog)
Write Safety ─────→ Temp file + os.Rename (no dependencies on existing code)
DB Update ────────→ Existing schema: recordings, artist_credit, artists,
release_groups, release_group_recordings, genres, genre_recordings,
cover_art, audio_files
────→ FTS5 search_index rebuild for affected rows
────→ track_metadata VIEW reflects changes automatically (it's a VIEW)
```
### Critical Dependency Chain
```
Tag Writing Library → Single Track Edit → Batch Edit
→ Cover Art Edit
```
The tag writing library choice gates everything. Until a library can write ID3v2 and Vorbis comments, no editing features can ship.
### Dependency on Existing Architecture
| Existing Feature | How Tag Editing Uses It |
|-----------------|----------------------|
| `track-details` component | Already has edit mode scaffolded with input fields, edit/save/cancel buttons, and `editValues` state. Save handler is a TODO stub. |
| Multi-select in track-list | Entry point for batch editing — selected file paths already accessible via `selection.getSelectedKeysOrdered()` |
| Context menu system | "Edit Tags" menu item for single or multi-select (currently shows "Track Details" for single) |
| `FrontendUtil.OpenFileDialog` | File picker for cover art image selection |
| `Library.saveCoverArt` + thumbnail pipeline | Reusable for cover art embedding — same hash-based cache, same thumbnail generation |
| Event system | New events needed: `TagsWritten`, `TagWriteProgress`, `TagWriteError` |
| `backend/metadata/tags.go` | `TrackMetadata` struct defines all writable fields; `ExtractTags` used for reading |
## Batch Editing UX Patterns (Deep Dive)
The batch editor is the highest-complexity feature. Here's how mature players handle it:
### Three-State Field Model
For each editable field in batch mode:
1. **Keep** (default): Shows "[Mixed]" or "[Various]" if values differ, shows the common value if all tracks share it. On save, each track retains its original value.
2. **Set**: User has typed a new value. On save, all selected tracks get this value.
3. **Clear**: User explicitly cleared the field. On save, all selected tracks have this field emptied.
**Implementation approach:**
```typescript
type FieldState = 'keep' | 'set' | 'clear';
interface BatchField {
state: FieldState;
value: string; // The new value (only meaningful when state === 'set')
commonValue: string; // Value shared across all tracks (empty if mixed)
isMixed: boolean; // Whether tracks have different values
}
```
### Backend Batch Write Contract
```go
// TagEdits contains the fields to write. nil = don't change, empty string = clear.
type TagEdits struct {
Title *string
Artist *string
Album *string
Genre *string
Year *int
TrackNumber *int
DiscNumber *int
Composer *string
CoverArt *CoverArtEdit // nil = keep, non-nil = set/remove
}
type CoverArtEdit struct {
ImageData []byte // empty = remove cover art
MIMEType string
}
```
Using pointer fields: `nil` = keep original, non-nil = set to this value (empty string/zero = clear). This is the standard Go pattern for optional updates and maps directly to the three-state UI model.
### Batch Write Ordering
1. Validate all edits before writing any files (fail fast)
2. Write files sequentially (not concurrently — avoids disk thrashing and simplifies error handling)
3. For each file: read → modify → write-to-temp → rename
4. After ALL files written successfully: batch-update DB + FTS5
5. Emit success event with count
6. On error: stop, report which file failed, files already written are committed (no rollback — file writes are atomic individually)
## Cover Art Editing Workflow
### Set/Replace Cover Art (Table Stakes)
1. User clicks "Change Cover" in edit dialog
2. File picker opens (filter: `*.jpg, *.jpeg, *.png`)
3. User selects image file
4. Preview shown in dialog (replacing current art)
5. On save:
a. Read image bytes from selected file
b. Embed in audio file tag (APIC frame for ID3v2, METADATA_BLOCK_PICTURE for FLAC/OGG)
c. Save to cover art cache (via existing `saveCoverArt` pipeline → hash, dedupe, thumbnails)
d. Update `cover_art` table if hash changed
e. Update UI with new cover art URLs
### Batch Cover Art (Same Image to All Selected Tracks)
Common use case: fixing an album where some tracks have wrong/missing cover art.
1. In batch editor, cover art section shows "[Mixed]" or common art
2. User selects new image → applies to ALL selected tracks on save
3. This is the same flow as single-track, just repeated N times
### What NOT to Build for Cover Art
- No crop/resize — users use external tools (GIMP, Preview, etc.)
- No web search — would require API integration (future MusicBrainz milestone could add this)
- No multiple picture types (front, back, booklet) — only front cover. ID3v2 supports picture types but the complexity isn't worth it for v1.
## Field Mapping: Tag Format → Database Schema
Understanding how edited fields map through the system:
| Edit Field | Tag (ID3v2) | Tag (Vorbis) | DB Table | DB Column | Notes |
|-----------|------------|-------------|----------|-----------|-------|
| Title | TIT2 | TITLE | `recordings` | `name` | |
| Artist | TPE1 | ARTIST | `artist_credit``artists` | `text` / `name` | May need to create new artist_credit + artist rows |
| Album | TALB | ALBUM | `release_groups` | `name` | May need to create new release_group row |
| Album Artist | TPE2 | ALBUMARTIST | (not currently stored separately) | — | Would need schema addition or use existing artist credit |
| Genre | TCON | GENRE | `genres` + `genre_recordings` | `name` | Multiple genres: split on `;` or `,` |
| Year | TYER/TDRC | DATE | `recordings` | `year` | |
| Track # | TRCK | TRACKNUMBER | `recordings` | `track_number` | |
| Disc # | TPOS | DISCNUMBER | `recordings` | `disc_number` | |
| Composer | TCOM | COMPOSER | `recordings` | `composer` | |
| Cover Art | APIC | METADATA_BLOCK_PICTURE | `cover_art` | `file_path` | Binary data; separate storage |
| Comment | COMM | COMMENT | `recordings` | `comment` | |
| Lyrics | USLT | LYRICS | `recordings` | `lyrics` | |
### Schema Update Complexity
Simple fields (title, year, track#, disc#, composer, comment, lyrics) → UPDATE `recordings` directly.
Relational fields (artist, album, genre) → must handle entity lifecycle:
- **Artist change:** Look up or create new `artists` + `artist_credit` rows, update `recordings.artist_credit_id`
- **Album change:** Look up or create new `release_groups` row, update `release_group_recordings` link
- **Genre change:** Parse genre string, look up or create `genres` rows, update `genre_recordings` links
This entity lookup logic already exists in `library.go`'s `processMetadata` / `saveAudioFile` pipeline — it should be extracted and reused.
## MVP Recommendation
**Prioritize (Phase 1 — Tag Editing Core):**
1. Tag writing library integration (MP3 + FLAC + OGG)
2. Single track editing (the 8 active fields from PROJECT.md)
3. Write-to-temp-then-rename safety
4. DB + FTS5 inline update
5. Cover art set/replace from file
**Prioritize (Phase 2 — Batch Editing):**
6. Batch editing with three-state field model
7. Progress feedback for batch operations
8. Error handling and partial-success reporting
**Defer:**
- Album artist editing (schema question, low priority)
- Lyrics/comment editing (easy to add later, niche)
- Auto-numbering tracks (convenience, not core)
- Undo/backup system (nice-to-have, not table stakes)
- Cover art paste from clipboard (WebView clipboard API complexity)
## Sources
- Hydrogenaudio Knowledgebase: Tag Mapping (https://wiki.hydrogenaud.io/index.php/Tag_Mapping) — HIGH confidence, authoritative tag format reference
- Hydrogenaudio Knowledgebase: foobar2000 Encouraged Tag Standards (https://wiki.hydrogenaud.io/index.php/Foobar2000:Encouraged_Tag_Standards) — HIGH confidence
- Hydrogenaudio Knowledgebase: Tag (metadata) (https://wiki.hydrogenaud.io/index.php/Tag) — HIGH confidence, basic/advanced/personalized field categorization
- YellowJacket codebase analysis: `track-details.ts`, `tags.go`, `coverart.go`, `library.go`, database schemas — PRIMARY source for dependency analysis
- MusicBee, foobar2000, Kid3, Mp3tag, Picard — feature set analysis from training data (MEDIUM confidence on specific UI details)