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

189 lines
14 KiB
Markdown

# Project Research Summary
**Project:** YellowJacket v1.2.1 Format Parity
**Domain:** Audio metadata tag writing — OGG Vorbis and WAV format support
**Researched:** 2026-03-18
**Confidence:** HIGH
## Executive Summary
YellowJacket v1.2.1 adds OGG Vorbis and WAV tag writing to achieve full format parity across all four supported audio formats. The existing `tagwriter` pipeline was designed for format extension — a switch-case in `pipeline.go` dispatches to format-specific writer functions, and everything downstream (DB sync, events, UI) is format-agnostic. Adding OGG and WAV requires two new writer functions, zero new dependencies, and no frontend changes.
The recommended approach is **custom implementations for both formats**: a custom OGG page rewriter (~350 LOC) for OGG Vorbis, and a custom RIFF chunk parser (~200 LOC) wrapping the existing `bogem/id3v2` library for WAV. No pure-Go library exists for OGG Vorbis tag writing, and the Go WAV ecosystem recently lost its most popular library (`go-audio/wav` archived Feb 2026). The OGG container format and RIFF chunk format are both well-documented and simple enough to implement directly. Vorbis Comments (used by OGG) are the exact same metadata format already used in FLAC — field names, encoding, and comment structure are identical, enabling significant code reuse.
The primary risk is the OGG page infrastructure: CRC32 checksums use a non-standard bit ordering (MSB-first, not the Go standard library's reflected CRC32), page sequence numbers must be strictly sequential, and granule positions must be preserved exactly. These are well-understood constraints with clear specifications, but incorrect implementation produces silently corrupted files. Round-trip testing (write → read back via `dhowden/tag`) is the primary mitigation, following the pattern established by the existing MP3 and FLAC writers.
## Key Findings
### Recommended Stack
Zero new dependencies. Both formats are implemented using Go standard library primitives (`encoding/binary`, `encoding/base64`, `bytes`, `io`) plus existing dependencies for shared functionality.
**Core technologies:**
- **Custom OGG page rewriter:** Parse/write OGG pages with CRC32 and segmentation — no pure-Go OGG writing library exists; `mccoyst/ogg` (37 stars, no semver) saves ~80 LOC but adds dependency risk
- **Custom RIFF chunk parser/writer:** Read/write WAV RIFF structure — `go-audio/wav` and `go-audio/riff` were archived Feb 2026; RIFF is simple enough for custom code
- **`bogem/id3v2` (existing):** Generate ID3v2 tags for WAV `id3 ` chunks — same library already used for MP3 writing, all 8 fields + cover art work identically
- **`go-flac/flacpicture` (existing):** Build METADATA_BLOCK_PICTURE binary blocks for OGG cover art — same binary format, just base64-wrapped for OGG
- **`fileutil.AtomicWrite` (existing):** Crash-safe file writes for both formats — proven pattern from MP3/FLAC writers
### Expected Features
**Must have (table stakes):**
- Write all 8 text fields for OGG Vorbis (same Vorbis Comment field names as FLAC)
- Write all 8 text fields for WAV (via ID3v2 in RIFF chunk)
- Preserve existing non-edited metadata (ReplayGain, lyrics, etc.)
- Preserve audio data byte-for-byte (no re-encoding)
- Crash-safe writes via AtomicWrite
- Batch and single-track editing work for both formats
**Should have (differentiators):**
- OGG cover art via METADATA_BLOCK_PICTURE (base64-encoded FLAC picture block)
- WAV cover art via ID3v2 APIC frame (identical to MP3)
- Preserve existing RIFF INFO chunks when writing ID3v2 to WAV
- Round-trip test coverage (7 tests per format, following FLAC precedent)
**Defer (v2+):**
- RIFF INFO writing (lossy — can't represent album_artist, disc_number, or cover art)
- Dual-write ID3v2 + RIFF INFO in WAV
- Migrating deprecated OGG `COVERART` field to `METADATA_BLOCK_PICTURE`
- RF64 (>4GB WAV) support
- OGG Opus tag writing (different header structure from Vorbis)
### Architecture Approach
Both writers integrate into the existing pipeline with minimal modification: add two format constants, extend the `DetectFormat()` switch, and add two cases to the `WriteTrackTags()` format dispatch. No new interfaces, no refactoring. The frontend is completely format-agnostic and requires zero changes.
**Major components:**
1. **`ogg.go`** (~350 LOC) — OGG page parser/writer, Vorbis Comment serializer, CRC32, METADATA_BLOCK_PICTURE encoding, `writeOggTags()` orchestrator
2. **`wav.go`** (~200 LOC) — RIFF chunk parser/writer, ID3v2 tag in `id3 ` chunk via `bogem/id3v2`, `writeWavTags()` orchestrator
3. **`tagwriter.go` + `pipeline.go`** (~15 LOC changes) — Format constants, detection, dispatch
4. **`ogg_test.go` + `wav_test.go`** (~600 LOC) — 7 round-trip tests each, following FLAC pattern
**Key code reuse:**
- Vorbis Comment field mapping: identical to FLAC (extract shared helpers from `flac.go`)
- ID3v2 tag building for WAV: identical to MP3 (`applyTextChanges()`, `applyCoverArtChanges()`)
- AtomicWrite: used as-is by both writers
- `dhowden/tag` for read-back verification in tests
### Critical Pitfalls
1. **OGG CRC32 non-standard bit ordering (P1)** — OGG uses MSB-first CRC32 with polynomial 0x04c11db7. Go's `hash/crc32` uses reflected (LSB-first) ordering and produces wrong checksums. Must implement custom CRC or port from `jfreymuth/oggvorbis/crc.go`.
2. **OGG page sequence number continuity (P2)** — Rewriting comment header may change the number of header pages, requiring all subsequent page sequence numbers to be renumbered. Use full-stream rewrite approach (correct by construction).
3. **OGG granule position preservation (P3)** — Header pages must have granule position 0; audio pages must preserve original granule positions exactly. Corruption here breaks seeking and duration reporting.
4. **WAV RIFF chunk size updates (P6)** — Adding or resizing the `id3 ` chunk requires updating the outer RIFF header size field. Wrong size makes the file appear truncated to some players.
5. **WAV chunk word alignment (P10)** — RIFF chunks must start at even byte offsets. Odd-length chunks need a padding byte that's NOT included in the chunk's size field but IS part of the physical file.
### WAV Metadata Approach Decision
Research revealed a tension between two approaches:
- **RIFF INFO:** Native WAV format, simple, but can't represent album_artist, disc_number, or cover art
- **ID3v2-in-WAV:** Reuses existing `bogem/id3v2`, full field + cover art support, read by `dhowden/tag`
**Decision: ID3v2-in-WAV.** This gives full field parity with MP3, enables cover art, reuses existing code, and round-trips through `dhowden/tag` (our reader). RIFF INFO is preserved when present but not written to.
## Implications for Roadmap
Based on research, suggested phase structure:
### Phase 1: WAV Tag Writer
**Rationale:** Lower risk, faster to implement. Reuses existing `bogem/id3v2` library and `applyTextChanges()`/`applyCoverArtChanges()` from MP3 writer. RIFF container is simpler than OGG (no checksums, no page segmentation). Building this first proves the pipeline extension pattern works before tackling the harder OGG format.
**Delivers:** WAV text tag writing (all 8 fields) + cover art + round-trip tests
**Addresses:** WAV table stakes + WAV cover art (differentiator, but trivial since it reuses MP3 APIC code)
**Avoids:** P5 (use ID3v2, not RIFF INFO), P6 (careful RIFF size bookkeeping), P10 (chunk alignment padding)
**New code:** ~200 LOC `wav.go` + ~250 LOC `wav_test.go` + ~15 LOC pipeline changes
**Estimated effort:** Small — RIFF parsing is straightforward binary parsing
### Phase 2: OGG Vorbis Text Tag Writer
**Rationale:** OGG requires the most new infrastructure (page parser, CRC32, segmentation). Text-only tag writing exercises all the hard parts (page rewrite, CRC, sequence numbers) without the added complexity of multi-page comment packets from large cover art. This is the riskiest phase and benefits from Phase 1 having proven the pipeline extension works.
**Delivers:** OGG Vorbis text tag writing (all 8 fields) + round-trip tests
**Addresses:** OGG text field table stakes, audio data preservation, existing comment preservation
**Avoids:** P1 (CRC32), P2 (sequence numbers), P3 (granule positions), P4 (three-header structure), P7 (framing bit), P8 (packet prefix)
**New code:** ~300 LOC `ogg.go` (page infra + text writer) + ~300 LOC `ogg_test.go`
**Estimated effort:** Medium — OGG page infrastructure is the hardest new code in this milestone
### Phase 3: OGG Vorbis Cover Art
**Rationale:** Separated from Phase 2 because it adds multi-page packet complexity (large base64-encoded images can exceed the ~64KB OGG page limit). Text fields exercise the page infrastructure with small comment packets; cover art stress-tests it with large ones. Can be deferred if Phase 2 runs long without blocking the milestone.
**Delivers:** OGG Vorbis cover art embed/remove via METADATA_BLOCK_PICTURE
**Addresses:** OGG cover art differentiator
**Avoids:** P9 (METADATA_BLOCK_PICTURE format), P15 (multi-page segmentation for large payloads)
**New code:** ~50 LOC additions to `ogg.go` + ~50 LOC additions to `ogg_test.go`
**Estimated effort:** Small if Phase 2's page infrastructure is solid; medium if multi-page edge cases surface
### Phase 4: Edge Cases and Cleanup
**Rationale:** Validation and hardening after core functionality works. Adds detection/rejection of unsupported edge cases, size warnings, and documentation updates.
**Delivers:** RF64 detection, multi-stream OGG detection, large file warnings, PROJECT.md updates
**Addresses:** P13 (multi-stream OGG), P16 (RF64 WAV), P12 (disk space for large files)
**New code:** ~30 LOC validation checks + documentation updates
**Estimated effort:** Small
### Phase Ordering Rationale
- **WAV before OGG:** WAV is lower risk (reuses existing ID3v2 library, simpler container) and proves the pipeline extension pattern. OGG requires all-new page infrastructure with correctness-critical CRC and sequencing.
- **OGG text before OGG cover art:** Text fields exercise the page rewrite with small comment packets. Cover art adds multi-page complexity that should only be attempted once the core page infrastructure is validated by round-trip tests.
- **Edge cases last:** Detection/rejection of unusual files (RF64, multi-stream) is low risk and low effort — just validation guards at file-open time.
### Research Flags
Phases likely needing deeper research during planning:
- **Phase 2 (OGG text writer):** The OGG page re-segmentation and CRC implementation is the most complex new code. The spec is clear, but implementation details (lacing values, continuation flags, packet splitting across pages) benefit from studying `jfreymuth/oggvorbis` source as reference. Phase-level research recommended.
Phases with standard patterns (skip research-phase):
- **Phase 1 (WAV writer):** RIFF parsing is trivial; ID3v2 tag generation reuses existing code. Well-documented, no unknowns.
- **Phase 3 (OGG cover art):** METADATA_BLOCK_PICTURE format is well-specified; base64 encoding is trivial. Only depends on Phase 2's page infrastructure being correct.
- **Phase 4 (edge cases):** Simple validation checks with clear specifications.
## Confidence Assessment
| Area | Confidence | Notes |
|------|------------|-------|
| Stack | HIGH | Zero new dependencies; all recommendations based on official specs and existing codebase analysis |
| Features | HIGH | Feature set derived from official format specs (Xiph.org, RIFF) and existing codebase field model |
| Architecture | HIGH | Full codebase analysis confirms pipeline was designed for format extension; minimal changes needed |
| Pitfalls | HIGH | Pitfalls sourced from official OGG/RIFF specs, cross-referenced with existing library implementations |
**Overall confidence:** HIGH
### Gaps to Address
- **`bogem/id3v2` WAV compatibility:** The library's `Open()`/`Save()` API expects MP3 file structure. For WAV, we'll need to use `ParseReader()` to read existing ID3v2 tags from a byte slice, and `WriteTo()` to serialize the tag to bytes for embedding in the RIFF chunk. This needs validation during Phase 1 implementation — if `ParseReader` doesn't work for standalone tag parsing, we may need to create tags from scratch (losing existing ID3v2 data in the WAV).
- **OGG test fixture creation:** Cannot programmatically generate a valid OGG Vorbis file (requires Vorbis codebook data in setup header). Need to embed a minimal OGG fixture via `//go:embed`. Can be created once with ffmpeg during Phase 2 setup.
- **Multi-stream OGG prevalence:** Research confirms multi-stream OGG music files are extremely rare, but we should detect and reject them rather than silently corrupting. Validation during Phase 2.
## Sources
### Primary (HIGH confidence)
- OGG framing specification: https://xiph.org/ogg/doc/framing.html
- OGG RFC 3533: https://xiph.org/ogg/doc/rfc3533.txt
- Vorbis I Specification (comment field): https://xiph.org/vorbis/doc/Vorbis_I_spec.html
- Vorbis Comment specification: https://xiph.org/vorbis/doc/v-comment.html
- METADATA_BLOCK_PICTURE: https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
- FLAC Picture block format: https://xiph.org/flac/format.html#metadata_block_picture
- RIFF/WAV format: https://www.mmsp.ece.mcgill.ca/documents/AudioFormats/WAVE/WAVE.html
### Secondary (MEDIUM confidence)
- WAV metadata overview: https://en.wikipedia.org/wiki/WAV#Metadata
- RIFF tag reference: https://exiftool.org/TagNames/RIFF.html
### Libraries (HIGH confidence — direct code review)
- `jfreymuth/oggvorbis` v1.0.5: OGG page reader reference, CRC32 lookup table
- `dhowden/tag`: Reads OGG + WAV tags; validates round-trip correctness
- `bogem/id3v2` v2.1.4: ID3v2 tag generation for WAV `id3 ` chunks
- `go-flac/flacpicture` v2.0.2: FLAC picture block builder, reused for OGG METADATA_BLOCK_PICTURE
- `go-audio/wav` (ARCHIVED 2026-02-21): Evaluated and rejected
- `mccoyst/ogg` (37 stars, no semver): Evaluated and rejected — marginal benefit vs dependency risk
### Codebase (HIGH confidence — validated in v1.2)
- `backend/tagwriter/flac.go` — Vorbis Comment manipulation patterns to reuse
- `backend/tagwriter/mp3.go` — ID3v2 + AtomicWrite patterns to reuse for WAV
- `backend/tagwriter/pipeline.go` — Format dispatch switch to extend
- `backend/tagwriter/tagwriter.go` — TagChanges model, format detection, helpers
- `backend/fileutil/atomicwrite.go` — Crash-safe file write utility
---
*Research completed: 2026-03-18*
*Ready for roadmap: yes*