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

19 KiB
Raw Blame History

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 `

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:

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

// 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_creditartists 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