Files
yellowjacket/.planning/research/FEATURES.md
T

301 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)