# Lightify Music Library — Design Plan

> Goal: full-control, own-API music storage and playback for the Tauri build,
> using the most efficient *available* codecs with an original container, library,
> and delivery layer that we actually own.
>
> Hosting posture: **user-owned content only.** We compress-at-rest and
> decompress-on-demand for music the user has the legal right to hold.
> See §H for the hosting/legal/runtime model.

---

## §0 — Framing (the uncomfortable part)

We are **not** going to invent a new codec. That loses on every dimension:
size, speed, maintenance, legal risk, and years of engineer time.

We **are** going to invent the layer above the codec, which is where every
streaming service's real moat lives:

1. A **tiered storage model** that picks the right codec per use case.
2. A **container format** (`.lfy`) we fully own — chunked, seekable, crypto-
   per-track, content-addressable, with a stable on-disk spec we control.
3. A **library database** with fast search, tagging, playlists, and
   fingerprint-based dedup.
4. A **local HTTP/WebSocket API** served in-process by Tauri so the frontend
   (and any future client) talks to one endpoint.
5. An **optional neural-codec research track** for cases where 8–24 kbps
   actually matters (huge offline libraries, mobile sync).

Every byte, every cycle, and every dependency below is earned against those
five goals (per §1 of QikInstructions).

---

## §1 — Codec selection matrix

Pick codecs by use case. Three tiers cover 99 % of needs:

| Tier | Codec | Bitrate | Use case | Library | License |
|---|---|---|---|---|---|
| **A — Archival master** | **FLAC 1.5** | ~700–900 kbps (≈40 % of PCM) | User's imported CDs/purchases, reference source for re-encoding | `libflac` (C, BSD) | BSD |
| **B — Streaming default** | **Opus 1.6 (CELT)** | 96–160 kbps VBR | Default playback, skip-heavy browsing, typical listening | `libopus` (C, BSD) | BSD (+ royalty-free patent grant) |
| **C — Ultra-low bitrate** | **DAC 44 kHz** (Descript) or **EnCodec 48 kHz** | 6–24 kbps | Offline mobile sync, huge catalogs on small disks, AI-adjacent features (fingerprint, similarity) | PyTorch → ONNX → `ort` (Rust) | MIT |

### Rationale (short version)

- **Opus at 128 kbps VBR is transparent for music** in public listening tests
  (HydrogenAudio 96 kbps, Google tests). YouTube Music Premium ships 256 kbps
  Opus. It beats AAC/Vorbis/MP3 at every bitrate ≤ 256 kbps.
- **FLAC is already near-optimal for PCM lossless.** Further gains need
  domain-specific tricks (WavPack's hybrid mode, OptimFROG) that save 1–3 %
  at 2–5× the decode cost. Not worth it.
- **Neural codecs are the only place real 2026 gains exist**, and only at
  bitrates Opus can't reach cleanly (≤ 24 kbps). Use them surgically.

### What we explicitly reject

- **Rolling our own lossless.** Zero upside.
- **MP3.** Strictly worse than Opus at every bitrate. No.
- **AAC / xHE-AAC.** Patent-encumbered, no quality win over Opus.
- **Proprietary formats (MQA, ATRAC).** Patent bombs, no quality win.

---

## §2 — The `.lfy` container format

This is the part we own. It is a thin, versioned container — not a codec.
Its job: hold any of the tier A/B/C payloads with uniform metadata, seeking,
and integrity.

### Design goals
- **O(1) seek** to any second without scanning the file.
- **Append-only metadata** so tags/lyrics/art can be patched without re-encoding.
- **Per-chunk integrity** (BLAKE3) — corruption localized, not fatal.
- **Optional encryption** (ChaCha20-Poly1305) per track, keyed from user vault.
- **Self-describing codec payload** — the container doesn't care if it's
  FLAC frames, Opus packets, or DAC codebook indices.
- **Small header overhead** (< 2 KB typical, never > 8 KB).

### On-disk layout

```
┌─────────────────────────────────────────────────────────┐
│ MAGIC "LFY1" (4B)                                       │
│ HEADER_LEN (u32 LE)                                     │
│ HEADER (CBOR, signed)                                   │
│   - format_version: u8                                  │
│   - codec: enum { Flac, Opus, Dac, EncodecV1, Pcm }     │
│   - sample_rate, channels, duration_ms                  │
│   - encryption: { None | ChaCha20Poly1305 { nonce } }   │
│   - content_hash: BLAKE3 of decoded PCM (dedup key)     │
│   - metadata_offset: u64                                │
│   - chunk_index_offset: u64                             │
│   - chunk_size_ms: u16 (typical 1000 ms)                │
├─────────────────────────────────────────────────────────┤
│ CODEC_INIT (codec-specific: FLAC STREAMINFO, Opus head) │
├─────────────────────────────────────────────────────────┤
│ CHUNK 0: [len u32][blake3 u256][codec payload]          │
│ CHUNK 1: ...                                            │
│ ...                                                     │
│ CHUNK N                                                 │
├─────────────────────────────────────────────────────────┤
│ CHUNK_INDEX (array of (file_offset u64, pcm_ms u32))    │
├─────────────────────────────────────────────────────────┤
│ METADATA (CBOR, append-only log of patches)             │
│   - title, artists[], album, year, genre[]              │
│   - replaygain_track_db, replaygain_album_db            │
│   - lyrics (timed or plain), cover_art_ref              │
│   - user_tags, play_count, last_played, rating          │
└─────────────────────────────────────────────────────────┘
```

### Why CBOR not JSON
- 2–5× smaller headers, binary-typed (no float parsing), deterministic
  canonical form for hashing. Crate: `ciborium` (pure Rust, ~40 KB).

### Why BLAKE3 not SHA-256
- ~10× faster on modern CPUs, SIMD-friendly, 256-bit output. Crate: `blake3`.

### Why ChaCha20-Poly1305 not AES-GCM
- Constant-time on any CPU (no AES-NI dependency), well-audited.
  Crate: `chacha20poly1305` (RustCrypto, MIT).

### Cover art
- **Not** inlined in the container. Stored content-addressed in
  `covers/<blake3>.{jpg|webp}`, referenced by hash. Avoids duplicate 2 MB
  covers per track on multi-disc albums.

---

## §3 — Library database

### Schema (SQLite + FTS5)

```sql
CREATE TABLE tracks (
    id              INTEGER PRIMARY KEY,
    content_hash    BLOB UNIQUE NOT NULL,     -- BLAKE3 of decoded PCM
    file_path       TEXT NOT NULL,            -- path to .lfy
    codec           TEXT NOT NULL,            -- 'flac' | 'opus' | 'dac'
    duration_ms     INTEGER NOT NULL,
    sample_rate     INTEGER NOT NULL,
    channels        INTEGER NOT NULL,
    file_size       INTEGER NOT NULL,
    bitrate         INTEGER NOT NULL,
    title           TEXT,
    artist          TEXT,
    album           TEXT,
    album_artist    TEXT,
    year            INTEGER,
    track_no        INTEGER,
    disc_no         INTEGER,
    genre           TEXT,
    replaygain_db   REAL,
    added_at        INTEGER NOT NULL,         -- unix ms
    last_played_at  INTEGER,
    play_count      INTEGER NOT NULL DEFAULT 0,
    rating          INTEGER,                  -- 0–5
    cover_hash      BLOB
);

CREATE VIRTUAL TABLE tracks_fts USING fts5(
    title, artist, album, genre,
    content='tracks', content_rowid='id'
);

CREATE TABLE playlists (
    id        INTEGER PRIMARY KEY,
    name      TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
);

CREATE TABLE playlist_tracks (
    playlist_id INTEGER NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
    track_id    INTEGER NOT NULL REFERENCES tracks(id)    ON DELETE CASCADE,
    position    INTEGER NOT NULL,
    PRIMARY KEY (playlist_id, position)
);

CREATE TABLE fingerprints (
    track_id     INTEGER PRIMARY KEY REFERENCES tracks(id) ON DELETE CASCADE,
    chromaprint  BLOB NOT NULL                             -- 32 * u32 hashes
);
```

### Why SQLite
- Zero-config, single file, ACID, FTS5 built in, supports millions of rows,
  ~700 KB compiled, MIT-adjacent public domain. Rust crate: `rusqlite`
  (bundled feature ships libsqlite3 statically).

### Why Chromaprint fingerprints
- 32-hash perceptual fingerprint (≈ 128 B/track) enables:
  - Dedup regardless of source codec.
  - "Find duplicates" UI feature.
  - Match to MusicBrainz if user opts in for metadata enrichment.

---

## §4 — The local API

Served by Tauri's `tauri::command` for frontend calls, **and** mounted on
`127.0.0.1:<random_port>` as HTTP for any future client (CLI, mobile, LAN
streaming). Bind address is loopback by default; LAN is an opt-in setting.

### Endpoints (v1)

```
GET    /api/v1/tracks?q=<fts>&limit=&offset=&sort=
GET    /api/v1/tracks/{id}
GET    /api/v1/tracks/{id}/stream          → chunked audio (Opus or PCM)
GET    /api/v1/tracks/{id}/art             → image/jpeg or webp
POST   /api/v1/tracks/import               → body: file path or upload
DELETE /api/v1/tracks/{id}

GET    /api/v1/albums
GET    /api/v1/albums/{id}

GET    /api/v1/playlists
POST   /api/v1/playlists                   → { name }
PUT    /api/v1/playlists/{id}              → rename / reorder
POST   /api/v1/playlists/{id}/tracks       → { track_ids[], position? }
DELETE /api/v1/playlists/{id}/tracks/{position}

GET    /api/v1/stats                       → library counters, disk usage
GET    /api/v1/diagnostics                 → §7 exportable report

WS     /api/v1/events                      → play state, scan progress
```

### Streaming strategy
- **Same-origin frontend** (Tauri WebView) → use HTTP Range requests against
  `/stream`; the route either:
  - serves raw Opus packets in an Ogg container (browser-native decoder), or
  - demuxes FLAC/DAC → Opus on-the-fly (shared encoder, throttled).
- **Scrubbing** hits the chunk index for O(1) seek, never a linear scan.
- **No re-encode on FLAC → Opus transcode caching**: first transcode is
  cached in a sidecar `.lfy.opus` sibling, evicted by LRU under a disk budget.

### Security (per `security-boundaries` instruction)
- Bind `127.0.0.1` by default; LAN mode requires explicit config.
- Every endpoint requires a per-install token (generated on first run,
  stored in OS keychain via `keyring` crate).
- All track IDs validated against DB — never used as file paths.
- `POST /import` sanitizes paths, refuses symlinks outside user library root,
  rejects non-audio content by magic-byte sniff + codec probe.
- No outbound network calls unless user enables MusicBrainz / Discogs lookup.

---

## §5 — Performance budgets (per `performance-budgets`)

| Metric | Budget | Notes |
|---|---|---|
| Cold scan of 10 000 tracks | < 30 s on SSD | parallel, bounded thread pool |
| Library DB size | < 5 MB per 10 000 tracks | excluding cover art |
| Search latency (FTS5) | < 20 ms P99 for 100 000 tracks | |
| Track start latency (local) | < 100 ms | |
| Seek latency | < 50 ms | O(1) chunk index |
| Memory (idle) | < 40 MB RSS | |
| Memory (playing) | < 80 MB RSS | includes decode buffers |
| Idle CPU | 0 % | no timers, no polling |
| Container header overhead | < 2 KB typical | |
| Transcode cache | user-configurable, default 2 GB | LRU eviction |

All enforced via benchmarks in `lightify-tauri/src-tauri/benches/`, gated in CI.

---

## §6 — Dependency audit (per `dependency-audit`)

Only crates that pass §3 of QikInstructions:

| Crate | Job | Size | Why it earns its place |
|---|---|---|---|
| `rusqlite` (bundled) | DB | ~800 KB | No alternative at this feature/quality |
| `symphonia` | Decode FLAC/Vorbis/MP3 for import | ~400 KB | Pure Rust, MPL-2, no C deps |
| `claxon` or native `libflac-sys` | FLAC encode | ~250 KB | Pick one after benchmark |
| `opus` (audiopus) | Opus encode/decode | ~350 KB (wraps libopus) | Industry standard |
| `ogg` | Ogg muxing | ~80 KB | Tiny, needed for browser streaming |
| `ciborium` | CBOR | ~40 KB | Pure Rust, serde-native |
| `blake3` | Hashing | ~60 KB | SIMD, fastest |
| `chacha20poly1305` | Encryption | ~100 KB | RustCrypto, audited |
| `keyring` | OS keychain | ~50 KB | Cross-platform secret storage |
| `axum` or `hyper` bare | HTTP server | ~700 KB (axum) / ~300 KB (hyper) | Start with hyper + tower routing |
| `chromaprint` (C FFI) | Fingerprinting | ~200 KB | Only if we ship dedup feature |

Optional / gated behind feature flag:
| Crate | Job | Gate reason |
|---|---|---|
| `ort` (ONNX Runtime) | DAC/EnCodec decode | +30 MB binary, only if user opts in to Tier C |
| `reqwest` | MusicBrainz / Discogs | Only if user enables external lookup |

**Hard reject list**: `serde_json` for on-disk format (use CBOR), any crate
with > 5 MB transitive closure unless audited, any GPL dep for a library path
(GPL in CLI tools is fine).

---

## §7 — Diagnostics (per `diagnostics` instruction)

`GET /api/v1/diagnostics` returns a Markdown report conforming to §8.3 of
QikInstructions. Counters exposed:

- Library: `tracks_total`, `tracks_by_codec{flac,opus,dac}`, `total_duration_ms`,
  `total_bytes`, `orphan_files`, `fts_index_size`.
- Scanner: `scans_completed`, `files_processed_last_scan`, `scan_duration_ms_p50/p95/p99`.
- Player: `play_count_session`, `decode_xruns`, `seek_latency_ms_p50/p95/p99`.
- Storage: `transcode_cache_bytes`, `cache_hits`, `cache_misses`, `cache_evictions`.
- API: `requests_total`, `requests_by_endpoint`, `latency_p50/p95/p99`, `4xx`, `5xx`.
- Security: `auth_failures_last_hour` (rate-limited).

Zero-cost when idle: counters are atomic `u64`s behind `Arc`, no work
happens unless someone hits the endpoint.

---

## §8 — Tier C (neural) — research track only

**Don't build this until tiers A and B ship and prove themselves.** Entry
criteria before starting:
1. Library + player in production with > 1 month uptime on our own machines.
2. Measured real user pain: "I ran out of disk syncing to my phone."
3. Concrete target: fit a 10 000-track library in under 2 GB.

If and only if those are true, scope is:
1. Integrate `ort` (ONNX Runtime) via feature flag.
2. Pre-convert DAC 44 kHz weights (MIT) to ONNX; ship as optional download.
3. Encode path: offline only, batch, never realtime. Python tool or
   Rust wrapper around a small Python helper (we already have that pattern
   in `lightify-audio`).
4. Decode path: Rust, realtime on modern CPU only (benchmark Zen 2 baseline).
   Fall back to re-transcode-to-Opus-on-import if hardware too slow.
5. **Quality gate**: MUSHRA-style A/B against Opus 64 kbps. If DAC at 8 kbps
   is not preferred by ≥ 50 % of blinded test listeners on our own test set
   (rock, classical, electronic, jazz, vocal), we abandon the tier.

Honest expectation: neural codecs *are* the future but 2026 decoder
performance on modest CPUs is marginal. This is a 2027 feature at earliest.

---

## §9 — Module layout in the repo

```
lightify-tauri/
├── src-tauri/
│   ├── src/
│   │   ├── main.rs
│   │   ├── library/
│   │   │   ├── mod.rs          ← public facade
│   │   │   ├── db.rs           ← SQLite, migrations, queries
│   │   │   ├── scan.rs         ← import + fingerprint
│   │   │   ├── container.rs    ← .lfy read/write
│   │   │   ├── transcode.rs    ← FLAC↔Opus, cache
│   │   │   └── fingerprint.rs  ← chromaprint wrapper
│   │   ├── api/
│   │   │   ├── mod.rs
│   │   │   ├── routes.rs       ← axum/hyper routes
│   │   │   ├── auth.rs         ← token middleware
│   │   │   └── stream.rs       ← ranged streaming
│   │   ├── player/             ← existing, unchanged
│   │   └── diagnostics.rs
│   └── benches/
│       ├── container.rs
│       ├── db_search.rs
│       └── transcode.rs
└── docs/
    ├── music-library-design.md  ← this file
    └── lfy-format-v1.md         ← formal spec (next deliverable)
```

Nothing under `library/` or `api/` exists yet. Everything is additive; the
existing Spotify/Beatport modules remain untouched.

---

## §10 — Milestones (no dates, just ordering)

**M1 — Container + codec bridge (no UI)**
- `.lfy` writer/reader, CBOR header, BLAKE3 per chunk.
- Import: WAV/FLAC/MP3 → `.lfy` wrapping FLAC or Opus payloads.
- Export: `.lfy` → WAV (round-trip integrity test).
- Benches: encode/decode throughput, header overhead.

**M2 — Library DB + scan**
- SQLite schema, migrations, FTS5.
- Folder scan, metadata probe via Symphonia, dedup by content hash.
- Bench: 10 000-track scan within budget.

**M3 — Local API + streaming**
- hyper/axum server on loopback, token auth.
- `/tracks`, `/tracks/{id}/stream` with Range, `/art`.
- Tauri frontend switched to call `/api/v1/*` instead of direct Rust commands
  (same transport, unified shape).

**M4 — Playlists, stats, diagnostics**
- Playlist CRUD, play counters, ReplayGain application.
- `/api/v1/diagnostics` Markdown report.

**M5 — Transcode cache + hybrid storage**
- Automatic Opus sidecar for FLAC streams.
- LRU eviction under disk budget.

**M6 (gated) — Tier C neural codec research**
- Only if §8 entry criteria are met.

---

## §11 — What this plan deliberately does *not* do

- **Does not replace Spotify integration.** Spotify stays; the library is
  additive for user-owned content.
- **Does not invent encryption-at-rest for every install.** Opt-in, per-track.
- **Does not add a cloud component.** Full local control was the whole point.
- **Does not add DRM.** We're the library owner, not a rights holder.
- **Does not add telemetry.** Zero outbound traffic by default (§3.2 of
  dependency audit, §4 of security).

---

## §12 — Open questions for the owner (you)

1. **Import scope**: just user files, or also Spotify downloads? (Latter is
   impossible without breaking Spotify TOS — answer is probably "just user files".)
2. **LAN streaming**: do you want phones/other devices on your network to
   hit this API? Changes the auth story.
3. **Disk budget for transcode cache**: default 2 GB reasonable?
4. **Cover art**: fetch from MusicBrainz / Cover Art Archive if missing in tags?
   (User-opt-in network call.)
5. **Encryption-at-rest**: on by default for the whole library, or per-track?

Answers here shape M2 and M3 scope. Everything else can proceed without them.

---

## §H — Hosting music legally + compress-at-rest, decompress-on-demand

This section defines the concrete pipeline: **where bytes live, how they're
compressed on disk, and how they become sound in realtime when the user asks
for them.** It also fences off the legal posture so we don't accidentally
build something we can't ship.

### H.1 — Legal posture (what we host, what we refuse)

Compression is a technical choice. *Hosting* is a rights choice. The two
must never be confused.

**Legal sources we support:**

| Source | Legality | Our handling |
|---|---|---|
| User's own rip from CD/vinyl they own | Legal (private copying, jurisdiction-dependent in EU/US) | Import → `.lfy` |
| Bandcamp / Beatport / Qobuz / 7digital purchases (unencrypted FLAC/MP3) | Legal | Import → `.lfy` |
| Public-domain / CC-licensed (Archive.org, Free Music Archive, Jamendo) | Legal | Import + preserve license tag |
| User's own original recordings / masters | Legal | Import → `.lfy` (maybe encrypt at rest) |
| Promos, label-provided content the user has rights to | Legal (per-license) | Import, store license metadata |

**Sources we refuse by design:**

- **DRM-protected files** (Apple FairPlay `.m4p`, Audible, Amazon encrypted):
  Importer detects DRM via header sniff and refuses. We do *not* strip DRM.
- **Spotify / Apple Music / Tidal stream caches**: technically impossible and
  a TOS violation. Spotify playback stays in the existing Spotify module;
  nothing from that pipe ever lands in the library.
- **Arbitrary URLs the user types in**: no YouTube-DL-style ripper shipped.
  If the user wants to import from a URL, they do it with their own tool;
  we accept the resulting file.

**What this means in code**: `POST /api/v1/tracks/import` accepts only local
filesystem paths the user already has. No network downloaders. No browser
extension. Legal liability stays with the user's source choice, not the app.

Per-track metadata carries a `source` field (`rip | purchase | original |
promo | public_domain | unknown`) and optional `license` free-text. User-
visible in the UI; never sent anywhere.

### H.2 — Compress-at-rest: the decision tree

On import, for every track, the scanner makes **one** automatic decision:

```
            ┌─ Is source already lossy (MP3, AAC, Opus, Vorbis)?
            │     └─ YES → transcode is net loss. Wrap original bitstream
            │              in .lfy as Tier-B (Opus if already Opus, or
            │              remux as-is with codec=Passthrough).
            │
            ├─ Is source lossless (WAV, FLAC, ALAC, APE, DSD, 24-bit masters)?
            │     │
            │     ├─ User marked "archival" OR source bit-depth > 16 OR
            │     │  sample_rate > 48 kHz OR genre ∈ {classical, jazz, acoustic}?
            │     │     └─ YES → Tier A: FLAC (level 8). Keep full resolution.
            │     │
            │     └─ Otherwise (default pop/rock/electronic CD rip):
            │           └─ Dual-encode:
            │                 • Tier A:  FLAC level 8  (archive master)
            │                 • Tier B:  Opus 128 kbps (realtime streaming)
            │              Library DB rows reference both .lfy siblings.
            │
            └─ Unknown/corrupt → reject, log, surface in diagnostics.
```

**Why dual-encode by default**: FLAC decode at 44.1 kHz stereo is ~0.5 % CPU
on any 2015+ machine — trivial. But every frontend scrub, every "play the
next track" is ~600 KB/s of disk read and a full-frame FLAC decode. Opus at
128 kbps is ~16 KB/s of disk read and ~0.3 % CPU. For a library with lots of
UI interaction, the Opus sibling is the hot path; FLAC is only touched for
deliberate archival playback or re-export.

**Disk cost of dual-encode:**
- FLAC master: ~60 % of WAV ≈ 6 MB/min stereo.
- Opus sibling: ~1 MB/min stereo.
- Overhead vs FLAC-only: +17 %. Vs WAV-only: −33 % (still wins).

Users with tight disk can disable Opus sidecars (setting:
`storage.always_dual_encode = false`) and fall back to on-demand transcode
cache (§5).

### H.3 — Decompress-on-demand pipeline (realtime path)

This is the hot path. It must be boring, short, and benchmarked.

```
 HTTP GET /api/v1/tracks/{id}/stream
       │
       │  Range: bytes=0-    (or bytes=<seek_offset>-)
       ▼
 ┌──────────────────┐
 │ StreamHandler    │  ← axum/hyper route
 │  • auth token    │
 │  • rate-limit    │
 └────────┬─────────┘
          │ track_id → DB lookup (indexed, ~50 µs)
          ▼
 ┌──────────────────┐
 │ ContainerReader  │
 │  • open .lfy     │  ← mmap or File, depending on size
 │  • parse header  │  ← CBOR, ~5 µs
 │  • find chunk    │  ← O(1) from chunk_index, by ms→offset
 └────────┬─────────┘
          │
          ├── If codec == Opus: stream Ogg-Opus bytes directly →
          │       browser's native WebCodecs / <audio> decodes. Zero
          │       Rust-side decode cost.
          │
          └── If codec == FLAC:
                      ├── Check transcode cache: is there a .lfy.opus
                      │   sibling OR a cached Opus blob for this track?
                      │     ├─ HIT  → stream cached Opus as above.
                      │     └─ MISS → SPAWN transcoder task:
                      │                • decode FLAC frame (claxon)
                      │                • encode Opus packet (audiopus)
                      │                • emit Ogg page → HTTP body (flushed)
                      │                • write through to cache sidecar
                      │       First byte within ~60 ms, rest streams live.
                      │
                      └── Raw-FLAC path (audiophile mode, opt-in):
                              serve FLAC bytes directly (browser has no
                              FLAC decoder in all versions → WASM fallback
                              or native <audio> where supported).
```

**Key guarantees:**
- **O(1) seek**: chunk index is a flat `[u64 offset, u32 pcm_ms]` array
  loaded once at open. Seeking to minute 2:47 is one binary search in
  memory → one `seek()` syscall → stream resumes.
- **No whole-file decode ever**: we decode frame-by-frame as bytes leave
  the socket. Memory per stream is bounded: < 64 KB buffers.
- **Backpressure-aware**: if the client (WebView) stops reading, the
  Tokio sender blocks the transcoder. No runaway encode.
- **Transcoder is bounded**: one `Semaphore` limits concurrent live
  transcodes (default 2). Over the limit → 503 with `Retry-After`, which
  the frontend treats as "wait then retry" (rare; the cache absorbs it).

### H.4 — Transcode cache (the disk budget)

Located at `%APPDATA%/Lightify/cache/transcode/` (or XDG equivalent).

- **Key**: `blake3(track.content_hash ‖ target_codec ‖ target_bitrate)`.
- **Format**: raw `.lfy` with Tier-B Opus payload. Same container, same
  integrity guarantees as a first-class track.
- **Budget**: user-configurable, default 2 GB.
- **Eviction**: LRU by `last_read_at`, updated atomically on every stream
  open. Eviction runs on cache-miss when space < 5 % free.
- **Warmup hook**: on idle (no playback for 5 min) a low-priority task
  transcodes the next N tracks in the current playlist. Zero-cost when
  user is active (§6 idle-zero policy, rendering-efficiency rules).
- **Cold start**: every track has at least one decoder path that works
  without cache (raw Opus source or FLAC→live-transcode). Cache is
  strictly an accelerator.

### H.5 — Playback-time guarantees (the SLA)

These are the numbers the hot path must hit. They go into the CI bench
suite (§5) and block merge on regression.

| Scenario | Budget | Path |
|---|---|---|
| Click "play" on Opus-native track | < 80 ms to first sample | cache / sidecar HIT |
| Click "play" on FLAC archival, cache HIT | < 80 ms to first sample | transcoded sibling |
| Click "play" on FLAC archival, cache MISS | < 200 ms to first sample | live transcode first page |
| Seek within playing track | < 50 ms | chunk index, O(1) |
| Skip to next track (same album) | < 120 ms | preloader warms next chunk |
| Memory per active stream | < 5 MB working set | |
| CPU per active stream (Opus decode in browser) | < 0.5 % on baseline (Zen 2 / 8th-gen Intel) | |
| CPU per active stream (FLAC live transcode) | < 6 % on baseline | single-core |

### H.6 — Content-addressing & dedup (why the same song never stores twice)

Every imported file is hashed on its **decoded PCM** (not the file bytes).
This means:

- MP3 re-encoded at different bitrates but from the same master → *different*
  content hashes (correct: they sound different).
- The same FLAC copied twice from two folders → *same* content hash →
  second import becomes a library pointer, no new file written.
- A FLAC and its Opus sibling → *different* content hashes but linked by a
  `source_track_id` foreign key in the DB.

Chromaprint fingerprint (§3) catches the weaker case: same song, different
masters or different rips. Surfaces as "possible duplicate" in the UI; the
user decides whether to merge.

### H.7 — Encryption-at-rest (optional, off by default)

For user masters / originals / label-promos where the user wants privacy:

- Per-track ChaCha20-Poly1305; key derived from a library master key via
  HKDF with the track's content hash as info.
- Library master key sits in the OS keychain (`keyring` crate). Never
  on disk in plaintext. Never sent anywhere.
- Enabled at import time per-track, or bulk via setting
  `storage.encrypt_new_imports = true`.
- Streaming decrypts per-chunk (16 KB typical) → straight into decoder
  pipeline. Adds ~0.1 % CPU (ChaCha20 is fast). Zero impact on latency SLA.

### H.8 — What this *doesn't* do

- **No cloud mirror.** If the user wants offsite backup, they sync the
  library folder with their own tool (rclone, Syncthing). We don't touch it.
- **No cross-device sync built in.** That's a v2 feature and a different
  threat model (needs E2E crypto, conflict resolution, OT or CRDT). Out
  of scope for M1–M6.
- **No re-encoding of user's lossy imports**. If you give us an MP3, we
  wrap it; we never recompress it through Opus. (Tandem coding artifacts
  are real. Opus → Opus, Opus ← MP3 is never worth it.)
- **No DRM, ever**. Not on import, not on storage, not on output.

### H.9 — Summary of the answer to your question

**"Compress on disk, decompress in realtime" =**

1. **On disk**: Tier-A FLAC (lossless archive) **+** Tier-B Opus (streaming
   sibling), both wrapped in our own `.lfy` container. Sometimes only one
   tier exists (user preference, disk budget, source was already lossy).
2. **In realtime**: HTTP range request → chunk-indexed seek → frame-by-frame
   decode (or raw pass-through if Opus) → back into browser audio → speakers.
3. **Legal**: import only from sources the user legitimately owns. No DRM
   bypass, no network ripping, no cloud caches of copyrighted material.

The architecture is specifically designed so the **realtime** path never
does more work than necessary: most of the time it's zero-copy byte
streaming of pre-encoded Opus. Only the FLAC-archival-without-sidecar case
invokes a live transcoder, and that path has a bounded, cached fast lane.
