301 lines
19 KiB
Markdown
301 lines
19 KiB
Markdown
# Feature Landscape: Tag Editing
|
||
|
||
**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 Desktop Music Players Implement Tag Editing
|
||
|
||
### Reference Players Analyzed
|
||
|
||
| 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 |
|
||
|
||
### Common Patterns Across All Players
|
||
|
||
**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
|
||
|
||
**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)
|
||
|
||
**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
|
||
|
||
**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)
|
||
|
||
### Universal Editable Fields (from Hydrogenaudio Tag Mapping + player analysis)
|
||
|
||
**Basic (ID3v1-level, universal compatibility):**
|
||
- Title, Artist, Album, Year, Genre, Track Number, Comment
|
||
|
||
**Standard (ID3v2/Vorbis, widely supported):**
|
||
- Album Artist, Composer, Disc Number, Track Total, Disc Total, Lyrics
|
||
|
||
**Extended (advanced users, format-dependent):**
|
||
- BPM, Initial Key, Mood, Label, Catalog Number, ISRC, MusicBrainz IDs
|
||
|
||
## Table Stakes
|
||
|
||
Features users expect. Missing = product feels incomplete.
|
||
|
||
| 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 |
|
||
|
||
## Differentiators
|
||
|
||
Features that set the product apart. Not expected, but valued.
|
||
|
||
| 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 |
|
||
|
||
## Anti-Features
|
||
|
||
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)
|