waxbin

package module
v0.0.0-...-541c572 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 42 Imported by: 0

README

WaxBin

WaxBin is a CGO-free Go library and waxbin CLI that owns the catalog database and is the single source of truth for an audio collection. It indexes, organizes, searches, browses, and tracks per-user state across music, audiobooks, and podcasts. It runs standalone, much like beets, and is a clean dependency for WaxDeck.

Design tenets

  • No CGO, no external binaries. Cataloging is pure Go for every format via [WaxLabel], and so is the analysis pass (decode, loudness, fingerprint, and waveforms) via [WaxFlow]. The two libraries cover the same eight containers, so WaxBin can decode every format it can tag-read, on every host. fpcalc is the sole remaining optional subprocess, used only for AcoustID lookups in enrichment.
  • Hard scan/analyze boundary. Scanning is I/O-bound and never decodes PCM; loudness, fingerprinting, and peaks live only in a resumable analyze pass.
  • Source of truth. Consumers read the catalog through WaxBin's canonical read API instead of rebuilding their own view of the filesystem.
  • Stable identities. Every surfaced entity has an opaque sortable public id (ULID) that survives re-tag, move, and re-encode. Field provenance and locks keep enrichment and organize from ever overwriting curated data.
  • Library + CLI, no network daemon. Storage is SQLite (WAL) on a local filesystem, with a flock-based single-writer ownership seam. The only server is an optional local control socket (waxbin serve), a unix socket that lets a second terminal mutate the catalog while one process holds the write lock. There is no HTTP or network listener.

Quick start

go build ./cmd/waxbin

# create the catalog and register a managed library root
./waxbin init --db ./catalog.db --root /music:managed

# index, analyze (ReplayGain/fingerprint/peaks), and organize
./waxbin scan     --db ./catalog.db
./waxbin analyze  --db ./catalog.db
./waxbin organize --db ./catalog.db --apply

# read it back
./waxbin browse newest --db ./catalog.db
./waxbin search  --db ./catalog.db "midnight"
./waxbin stats   --db ./catalog.db

Roots are declared as path[:mode[:media[:profile]]], for example /music:managed:music, /audiobooks:managed:audiobook, /rips:in-place. Config resolves with flag > env (WAXBIN_*) > JSON > default precedence. Every data command supports --json (with a schemaVersion) and returns stable exit codes (waxbin exit-codes).

CLI reference

Area Commands
Lifecycle init, library add/library list, scan, analyze, watch, serve, doctor, jobs, version, exit-codes
Read / browse query/ls (incl. --tag KEY=VALUE, --tag-contains, --tag-present/--tag-missing, --limit-mode/--seed), browse <list>, facet --group-by (incl. tag.<KEY>, library), search (incl. --max-candidates, --library), show, art (incl. --role), lyrics, stats [--year N], provenance, lock/unlock, entity info
Curation & editing edit (incl. --batch), entity, credit, tag/tag keys, lyrics set, chapters, art set (incl. --role)
Ingest / organize inbox, import, organize, profiles
Deletion / repair trash, rm [--permanent], merge, audit, diagnostics, upgrade
Portability backup, restore, export, manifest, rebuild
Playlists / podcasts playlist, smartplaylist, podcast, opml
Enrichment enrich (MusicBrainz + Cover Art Archive; optional AcoustID; incl. --item/--entity)
Maintenance db verify [--fix], db vacuum [--integrity], db migrate, user, state
Watching for changes
  • waxbin scan is incremental: an unchanged file (same size + mtime) is skipped without re-hashing or re-parsing, and a file deleted on disk is reconciled to missing, behind a survival gate that refuses to act on a transiently unavailable root, so a momentary mount loss cannot mark a whole library missing. scan --force (alias --full) re-hashes and re-parses everything. A deliberate large deletion (more than half a library) is held back by the survival gate; run scan --reconcile-deletions to reconcile it once you have confirmed the files are really gone.
  • waxbin watch keeps the catalog in sync on a schedule (and, with --live, on filesystem events). Scheduled rescans are the primary mechanism because filesystem events are unreliable on WSL2, NFS, SMB, and bind mounts; a periodic full-content rescan (--full-interval) catches same-size/mtime-preserved edits the fast-path misses.
  • watch is a foreground mode. A read-write WaxBin holds an exclusive advisory lock on the catalog for its whole lifetime, so while watch runs, every other mutating command in another terminal (organize, analyze, enrich, import, scan --force) is refused with an ownership conflict (read-only queries always work). Stop the watcher (Ctrl-C) to do manual mutation, or run waxbin serve instead when a second terminal needs to mutate concurrently (see Serving). Idle lock release is deliberately post-1.0.
Serving (multi-client)

waxbin serve [--socket <path>] opens the catalog read-write (taking the write lock) and listens on a local unix control socket (default <db>.waxsock, created owner-only 0600). While it runs, other waxbin commands against the same catalog no longer fail with an ownership conflict. They auto-detect the running server (advertised in the lockfile) and dispatch through it: fast mutations (edit, lock, play state, ratings/stars, playlist membership, user, merge) are proxied to the server, and the heavier mutating commands borrow the lock through a maintenance-mode hand-off. Read commands always run directly. This is a local socket only, with no network or HTTP listener. The server runs until interrupted (Ctrl-C / SIGTERM).

Quality, repair, and maintenance
  • waxbin audit reports quality and integrity problems: duplicate/split entities, inconsistent metadata, missing art/ReplayGain, unportable filenames, orphaned sidecars, case-insensitive path conflicts, invalid feeds, and derived-data drift. --integrity adds an on-disk bitrot (content-hash) and corrupt-audio pass. It reports only; it never deletes.
  • waxbin merge <type> <survivor-pid> <loser-pid>... collapses duplicate artists / release-groups / albums / genres onto one survivor, re-pointing children (so play state and provenance ride along) and recomputing rollups.
  • waxbin upgrade groups alt encodings of the same recording (by fingerprint), ranks each group by quality, and marks the keeper.
  • waxbin db verify --fix repairs derived-data drift (FTS, rollups, sort keys) and reclaims orphaned art. waxbin db vacuum GCs and compacts the database.
  • waxbin stats --year 2025 prints a per-user listening year-in-review.
Curation & editing

The catalog is authoritative, and a curation edit changes the catalog first: it auto-locks the field so a later scan or enrichment pass never re-derives over your change, and records provenance (who set the value and when).

  • waxbin edit <pid> --set field=value edits scalar metadata such as title, artist, album, year, and track/disc numbers.
  • waxbin entity <pid> ... edits a normalized entity (an artist or album name, or a sort name) and its identifiers (ISRC, barcode, MusicBrainz IDs).
  • waxbin credit <pid> ... curates contributor roles such as composer and performer.
  • waxbin tag <pid> --key KEY --value V sets a custom tag: a non-standard frame a file carries that WaxBin's typed model does not map, or one you add yourself. waxbin tag <pid> lists an item's tags, and waxbin tag keys lists every custom-tag key in the catalog with per-key item counts.
  • waxbin lyrics set, waxbin chapters, and waxbin art set curate lyrics, book chapters, and cover art.

Each of these edits the catalog and offers opt-in --write-back to also mirror the change into the backing file(s) (see below). Write-back is best-effort: a file that cannot be written returns a typed error naming the files, while the catalog edit still stands. A book re-anchors its identity on write-back so a later rescan resolves the same item, and a multi-file book is written across every part.

On-disk tag write-back (opt-in)

The catalog is always authoritative; these opt-in features mirror an edit back into files for external players, always preserving audio essence (an essence-verified write never alters the audio):

  • The curation edits above (edit, entity, credit, tag, lyrics set, chapters, art set) take --write-back to embed the committed change into the item's file(s).
  • waxbin analyze --write-replaygain (or write_replaygain_tags in config) writes computed track and album ReplayGain into files after album aggregation (REPLAYGAIN_*, or Opus R128_*).
  • An organize profile with tag_write corrects albumArtist (literal Various Artists for compilations) and disc/track numbering on disk as it moves files, skipping locked fields and re-tagging before the move so a failure aborts cleanly.
  • stamp_item_pid additionally stamps a WAXBIN_ITEM_PID tag during organize, so rebuild can restore original item identities from tags (essence-first: adopted only when unambiguous, minted fresh on any conflict). A full DB backup remains the real disaster-recovery artifact.
Custom-tag queries

Custom tags are filterable and facetable, so they can power browse dimensions and allow/deny rules.

  • Query filters: waxbin query --tag MOOD=happy (equality), --tag-contains MOOD=hap (substring), --tag-present MYKEY and --tag-missing MYKEY (presence). A value may itself contain =, since only the first = splits key from value (--tag DISCOGS_RELEASE=id=12345). The same fields are available in smart-playlist rules as tag.<KEY> with the is, isNot, isPresent, isMissing, contains, startsWith, and endsWith operators. Ordered operators are rejected, since tag values are unordered text.
  • Facets and discovery: waxbin facet --group-by tag.MOOD counts items per distinct value, and waxbin tag keys lists the keys that exist to facet on.
  • isNot is deny-list semantics. tag.X isNot V means the item does not carry value V for key X. It does not mean "carries some value other than V". So an item tagged MOOD=[happy, sad] is not matched by tag.MOOD isNot happy, and an item with no MOOD tag at all is matched. That is exactly "deny when the forbidden value is present".
  • Case sensitivity. Equality (is, isNot) is exact-case (BINARY). Substring (contains, startsWith, endsWith) is ASCII-case-insensitive (SQLite LIKE). Facet value buckets are case-sensitive too, so happy and Happy are distinct buckets; only tag keys are canonicalized to uppercase, never values. Folding browse buckets is the consumer's job. A tag key WaxBin already owns through a scalar, credit, or identifier surface (such as TITLE or ISRC) is reserved and rejected as a tag field.

Library API

lib, err := waxbin.Open(ctx, waxbin.Options{
    DBPath: "catalog.db",
    Roots:  []config.Root{{Path: "/music", Mode: model.ModeManaged}},
})
defer lib.Close()

if _, err := lib.Scan(ctx, waxbin.ScanRequest{}); err != nil { /* ... */ }
page, err := lib.Browse(ctx, read.ListNewest, read.BrowseOptions{Limit: 50})

The stable facade lives in the root package; implementation subsystems live under model/, store/sqlite/, identity/, decode/, analyze/, loudness/, peaks/, fingerprint/, envelope/, query/, read/, art/, meta/, scan/, organize/, inbox/, trash/, playback/, playlist/, podcast/, source/, enrich/, audit/, jobs/, watch/, proxy/, port/, config/, pidpath/, waxerr/, and internal/.

License

MIT. See LICENSE.

Documentation

Overview

Package waxbin is the public facade for the WaxBin audio library engine.

WaxBin is a CGO-free Go library and waxbin CLI that owns the catalog database for an audio collection. It indexes files, records stable identities, searches the catalog, and can organize managed libraries on disk. It can be used standalone, much like beets, or embedded by WaxDeck.

Boundaries

Cataloging is pure Go. Scanning reads tags and decoder-independent identity data without decoding PCM; PCM work belongs to the separate analysis pass, which is itself pure Go: decode, loudness, fingerprint, and waveforms all run through WaxFlow with no CGO and no external binaries. The read side is owned by WaxBin so consumers share one catalog view.

Layout

The stable facade lives in this root package. Implementation subsystems live in their own packages: model (domain types + repository interfaces), query (the shared selection engine), identity (entity identity + essence hashing), store/sqlite (the SQLite DataStore, write coordinator, and flock ownership), read (facet/browse/search/pagination), art (CAS + thumbnails), decode (pure-Go PCM decoding via WaxFlow) and analyze (the PCM analysis pass), scan, organize, inbox, trash, playback, playlist, podcast, source (the acquisition port), enrich (metadata brain), audit (quality/repair), jobs, meta, config, proxy (the local unix-socket control channel behind serve), and pidpath (item PID -> file location, cached off the change feed, for a consumer that serves audio by PID); the CLI lives under cmd/waxbin. The structured-curation edit surface (scalar/ entity/credit/custom-tag/lyrics/chapters/art edits, with opt-in on-disk write-back) lives on the facade alongside the read API.

The engine covers the full lifecycle: scan, analyze, organize, read/browse, curation edits (with opt-in on-disk write-back), playback state, podcasts, audiobooks, enrichment, and audit/quality/repair, all on the same package boundaries. A local control socket (Serve) lets a second process mutate the catalog while one holds the write lock. The merge primitive and the maintenance commands (db verify/vacuum/migrate) close the loop.

Index

Constants

View Source
const OrphanGraceWindow = 24 * time.Hour

OrphanGraceWindow is how long an entity must stay childless before the manual orphan GC sweeps it. It is the safety backstop to the scanner's survival gate: a transient reconciliation blip that briefly orphans an entity will not delete it unless it is still orphaned a full window (and a second manual run) later.

Variables

This section is empty.

Functions

func ReadLockOwner

func ReadLockOwner(dbPath string) (sqlite.OwnerInfo, error)

ReadLockOwner reads the write-owner metadata from a catalog's lockfile without opening the catalog. It is how a CLI discovers whether a server is running and on which socket, before deciding to proxy a mutation. It returns an error when the lockfile is absent or unreadable (no live owner).

Types

type AcquiredFile

type AcquiredFile struct {
	Path string
}

AcquiredFile is a local media file to ingest as externally-acquired media (for example one a source provider already fetched to disk). Path is required for a track/book and optional for an episode (an episode may be ingested remote, to be downloaded later from meta.SourceURL).

type AcquiredMeta

type AcquiredMeta struct {
	// Origin provenance recorded in the acquisition table. SourceType defaults to
	// manual when empty; an explicitly acquired item is never plain local. Local is
	// the read-side default for an item with no acquisition row.
	SourceType      model.SourceType
	SourceURL       string
	SourceID        string
	Provider        string
	ProviderVersion string
	OptionsJSON     string

	// Track/book placement.
	Profile   string          // organization profile override (empty = the target library's)
	Copy      bool            // copy instead of move the source file
	DupPolicy model.DupPolicy // catalog-duplicate policy (default skip)

	// Episode ingest.
	ShowPID   model.PID // existing show to add the episode under; empty creates a manual show
	ShowTitle string    // manual show title when ShowPID is empty (default "Acquired")
	Title     string    // episode title (default the file base name)
	Pinned    *bool     // pinned episode; default true for acquired episodes
}

AcquiredMeta carries the origin provenance recorded against an acquired item plus the per-kind ingest options.

type AcquiredResult

type AcquiredResult struct {
	Kind       model.Kind
	Plan       *inbox.Plan // track/book: review, then ApplyImport
	EpisodePID model.PID   // episode: the ingested episode
	FilePID    model.PID   // episode: its attached file, when a local file was provided
	Path       string      // episode: the placed file path, when attached

	// AlreadyPresent reports that the acquired file's audio essence is already in the
	// catalog, resolved independent of DupPolicy (so a DupAllow import of a genuine
	// duplicate still reports it). AlreadyPresentPID names the existing local item when
	// one resolves by essence. They let the host tell the user "already in your library"
	// on a receive, e.g. "3 of 12 already present". Set for the track/book path only;
	// the host can pre-check an episode via ResolveRef by essence. The action's Outcome
	// separately conveys whether the file was skipped (DupSkip) or imported (DupAllow).
	AlreadyPresent    bool
	AlreadyPresentPID model.PID
}

AcquiredResult reports an ImportAcquired. For a track/book it carries a reviewable import Plan (apply it with ApplyImport); for an episode the ingest is immediate and EpisodePID/FilePID/Path name the result.

type AltEncoding

type AltEncoding struct {
	ItemPID    model.PID
	FilePID    model.PID
	Similarity float64
}

AltEncoding is one verified alternate encoding of a query item: a different file whose fingerprint matches above the similarity threshold.

type AnalyzeOptions

type AnalyzeOptions struct {
	// WriteReplayGainTags mirrors computed ReplayGain into files after aggregation,
	// for this run. It is OR-ed with the library's configured toggle, so a run enables
	// write-back if either the config or this flag asks for it.
	WriteReplayGainTags bool
}

AnalyzeOptions controls one analyze run.

type AnalyzeResult

type AnalyzeResult struct {
	JobPID model.PID
	Result analyze.Result
}

AnalyzeResult reports an analyze run and the job it ran under.

type AuditOptions

type AuditOptions struct {
	// Only, when non-empty, restricts the run to these checks.
	Only []model.AuditCheck
	// Integrity re-reads every audio file to detect bitrot, missing files, and
	// corrupt audio. Off by default (I/O heavy).
	Integrity bool
	// Sample caps the per-check finding sample (0 uses a default).
	Sample int
}

AuditOptions selects which audit checks run.

type BatchEditResult

type BatchEditResult struct {
	Edited          []model.PID
	Skipped         []model.PID
	WriteBackErrors map[model.PID]*WriteBackError
}

BatchEditResult reports a multi-item edit's outcome: the items whose catalog edit applied, the items skipped because a target field was locked (skip-locked mode), and, for a write-back batch, the per-item on-disk write-back failures. The catalog edit is atomic; a WriteBackErrors entry means that item's catalog edit stands but its file tags did not follow.

type CreditEditOptions

type CreditEditOptions struct {
	// WriteBack also writes the role's names into each backing file's on-disk tag: a
	// track's music role, or a book's author (ALBUMARTIST) / narrator (NARRATOR+COMPOSER).
	// A book translator/editor credit has no round-trippable tag and is refused.
	WriteBack bool
	// Lock locks the credit.<role> field against enrichment/organize; on by default.
	Lock bool
	// Force overrides a locked credit role.
	Force bool
}

CreditEditOptions configures a credit edit, mirroring EditOptions.

type DoctorReport

type DoctorReport struct {
	DBPath string
	// SchemaVersion is the catalog's actual applied version; BuildSchemaVersion is
	// what this binary supports. They differ when a catalog has not yet been
	// migrated by a read-write command (doctor itself never migrates).
	SchemaVersion      int
	BuildSchemaVersion int
	ReadOnly           bool
	Owner              sqlite.OwnerInfo
	LibraryCount       int
	ItemCount          int
	FingerprintCount   int
	LoudnessCount      int // files with a stored ReplayGain measurement
	PodcastCount       int // subscribed feeds

	// Enrichment coverage: entities looked up, and how many a provider matched.
	EnrichedEntities int
	EnrichedMatched  int

	// Diagnostic coverage: how many persisted file diagnostics exist, and how many
	// audio files have not had diagnostics derived under the current rule set. A
	// non-zero DiagnosticsStale is why "no diagnostics" must not be read as "clean".
	DiagnosticCount  int
	DiagnosticsStale int
	// EnrichmentEnabled reports whether a MusicBrainz contact is configured.
	EnrichmentEnabled bool

	// Fpcalc is the sole remaining optional helper (Chromaprint for AcoustID); it is
	// never required for core use. Decoding is pure-Go via WaxFlow, so there is no
	// ffmpeg capability to report. Coverage below is the honest "what can this build
	// decode" answer.
	Fpcalc bool

	// Coverage reports, per codec, how the analyze pass decodes it in this build.
	Coverage []decode.FormatSupport
}

DoctorReport summarizes catalog health and detected capabilities.

func (*DoctorReport) NeedsMigration

func (r *DoctorReport) NeedsMigration() bool

NeedsMigration reports whether the catalog is behind the build (a read-write command would upgrade it).

type EditOptions

type EditOptions struct {
	// WriteBack also writes the new value into each backing file's on-disk tags. It is
	// off by default, so an edit is catalog-only unless the caller opts in.
	WriteBack bool
	// Lock locks each edited field against enrichment and organize overwrites. A user
	// edit is authoritative, so the CLI sets this by default. Pass false to leave the
	// field unlocked.
	Lock bool
	// Force overrides a lock. Without it, editing a locked field returns CodeLocked.
	Force bool
	// SkipLocked applies only to a multi-item batch: a locked item is skipped and
	// reported rather than failing the whole batch. Ignored by a single-item edit.
	SkipLocked bool
}

EditOptions controls a catalog field edit.

type EmptyReport

type EmptyReport struct {
	Purged         int
	Errored        int
	ReclaimedBytes int64
}

EmptyReport summarizes an empty-trash pass.

type EmptyTrashOptions

type EmptyTrashOptions struct {
	// OlderThan keeps recently-trashed files: only entries trashed strictly
	// before now-OlderThan are purged, so an undo window survives a routine
	// space-reclaim pass. Zero purges everything; negative is refused.
	OlderThan time.Duration
}

EmptyTrashOptions scopes an empty-trash pass.

type EnrichOptions

type EnrichOptions struct {
	Force bool // re-enrich already-enriched entities
	Limit int  // cap on entities processed (0 = all needing enrichment)

	ItemPID    model.PID       // scope to one item's targets ("" = no item scope)
	EntityType read.EntityKind // with EntityPID: scope to one entity
	EntityPID  model.PID
}

EnrichOptions controls a metadata enrichment run. ItemPID or EntityType+ EntityPID (mutually exclusive) scope the pass to one item's or entity's enrichable targets: a track scopes to its artist, album artist, release group, and lyrics; a book to its contributors and identifier fill; an entity scope takes artist, release_group, or album (an album resolves to its parent release group). A scoped run implies Force and runs the full pipeline for its targets, provenance, markers, and change deltas included.

type EnrichResult

type EnrichResult struct {
	JobPID model.PID
	Result enrich.Result
}

EnrichResult reports an enrichment run and the job it ran under.

type EntityEditOptions

type EntityEditOptions struct {
	// WriteBack also fans the edited identifiers and sort across the entity's member
	// files' on-disk tags: an album's BARCODE, LABEL, CATALOGNUMBER, and ALBUMSORT, and an
	// artist's ARTISTSORT. A release-group field, a release-group type, and an entity MBID
	// have no fanned tag and stay DB-only.
	WriteBack bool
	// Lock locks each edited entity field against enrichment overwrites; on by default.
	Lock bool
	// Force overrides a locked entity field.
	Force bool
}

EntityEditOptions controls an entity field edit, mirroring EditOptions.

type ImportRequest

type ImportRequest struct {
	Source     string          // staging folder to import (required)
	LibraryPID model.PID       // target managed library; empty uses the single managed one
	Profile    string          // layout; empty uses the library's configured profile
	DupPolicy  model.DupPolicy // how to treat catalog duplicates (default skip)
	Copy       bool            // copy (keep originals) instead of move
}

ImportRequest selects a staging folder and how to import it.

type Library

type Library struct {
	// contains filtered or unexported fields
}

Library is the public handle to a WaxBin catalog. It is safe for concurrent use. A read-only Library refuses mutating operations.

func Open

func Open(ctx context.Context, opts Options) (*Library, error)

Open opens (creating if needed) the catalog and wires the subsystems. A read-write open acquires the write lock, migrates, reclaims orphaned jobs, and ensures the configured roots; a read-only open does none of those.

func (*Library) Acquisition

func (l *Library) Acquisition(ctx context.Context, pid model.PID) (*model.Acquisition, error)

Acquisition returns an item's origin provenance, or CodeNotFound when it was locally scanned (no acquisition row).

func (*Library) AddRoot

func (l *Library) AddRoot(ctx context.Context, spec config.Root) (*model.Library, error)

AddRoot registers a library root at runtime, without reopening the Library. The spec is validated against every root already registered (plus the configured inbox folders and podcast download dir), exactly as Open validates the configured set, then upserted: a new path emits a `library` create delta, and re-adding an existing path refreshes its mode/media/profile under the same pid. Spec defaults match config loading (in-place, mixed, waxbin-native).

The store is the single source of truth for roots. Scan, organize, and import resolve libraries from store rows, so they pick the new root up immediately, and the row survives a process restart even if the embedder never adds the root to its own configuration: a later Open validates only its configured roots against each other and re-upserts them, never pruning rows absent from the config. A running Watch does NOT pick the root up, because watch snapshots its roots at start; restart the watcher after adding one. The create delta on the change feed is the machine-readable signal for that.

func (*Library) Analyze

func (l *Library) Analyze(ctx context.Context, opts AnalyzeOptions) (*AnalyzeResult, error)

Analyze runs the resumable analyze pass: it decodes (the only PCM-decoding stage), fingerprints, and indexes every audio file whose fingerprint is missing or stale, under an "analyze"-scoped job. Files whose codec this build cannot decode are reported as skipped, not failed.

func (*Library) ApplyDelete

func (l *Library) ApplyDelete(ctx context.Context, plan *trash.Plan) (*trash.Report, error)

ApplyDelete executes a deletion plan under a "delete"-scoped job.

func (*Library) ApplyImport

func (l *Library) ApplyImport(ctx context.Context, plan *inbox.Plan) (*inbox.Report, error)

ApplyImport executes an import plan under an "import"-scoped job.

func (*Library) ApplyOrganize

func (l *Library) ApplyOrganize(ctx context.Context, plan *organize.Plan) (*organize.Report, error)

ApplyOrganize executes a plan under an "organize"-scoped job.

func (*Library) ArtRoles

func (l *Library) ArtRoles(ctx context.Context, ref model.EntityRef) ([]model.ArtRoleInfo, error)

ArtRoles lists the artwork slots an entity holds at its own level (no chain fallback): each stored role with the source image's format, dimensions, and content hash. An entity with no art returns an empty list.

func (*Library) Audit

func (l *Library) Audit(ctx context.Context, opts AuditOptions) (*audit.Report, error)

Audit runs the quality/integrity checks and returns their findings. It is read-only. The default run covers the catalog checks (duplicates, split albums, inconsistent metadata, missing art/ReplayGain, bad filenames, orphan sidecars, path conflicts, invalid feeds, derived-data drift); Integrity adds the on-disk bitrot and corrupt-audio passes.

func (*Library) Backup

func (l *Library) Backup(ctx context.Context, dest string, redact bool) error

Backup writes a self-contained byte copy of the catalog to dest. The copy contains every table, including the secret table; with redact, secrets are stripped from the copy while the live catalog is untouched. A full backup is the disaster-recovery artifact.

func (*Library) BeginMaintenance

func (l *Library) BeginMaintenance(ctx context.Context) error

BeginMaintenance suspends the Library and releases the write lock so a foreground process can take it. It implements proxy.Maintainer; the Library object is kept so EndMaintenance can restore it in place.

It refuses while a background job is running: server-run scan/analyze/enrich/ organize passes run in this process, and closing the store out from under one would abort it partway. The foreground caller gets a CodeConflict and should retry once the job completes. Unlike Close, it suspends (keeping in-process change subscribers alive) so an embedder's subscription survives the hand-off.

func (*Library) Book

func (l *Library) Book(ctx context.Context, pid model.PID) (*model.BookDetail, error)

Book returns the full detail for an audiobook: subtitle, series placement, role-tagged contributors (author/narrator/...), backing parts in reading order, and chapters resolved to book-timeline offsets with the total duration. CodeInvalid when pid is not a book.

func (*Library) BookResume

func (l *Library) BookResume(ctx context.Context, userPID, bookPID model.PID) (*model.PlayState, *model.Chapter, error)

BookResume returns a user's play state for a book together with the chapter their resume position falls in, the chapter-level resume answer. An empty userPID selects the default user.

func (*Library) BooksInSeries

func (l *Library) BooksInSeries(ctx context.Context, seriesPID model.PID) ([]*model.ItemView, error)

BooksInSeries lists a series' books in sequence order (decimal/string aware).

func (*Library) Browse

func (l *Library) Browse(ctx context.Context, list read.DiscoveryList, opt read.BrowseOptions) (*read.Page, error)

Browse returns one keyset-paginated window for a discovery list such as newest, recently-added, most-played, recently-played, random, starred, by-year, by-genre, or alphabetical. Play-derived lists read opt.UserPID's play_state (empty selects the default user). The by-year, by-genre, and random lists use opt.Year, opt.GenrePID, and opt.Seed respectively. Pagination is stable under concurrent mutation.

func (*Library) Changes

func (l *Library) Changes(ctx context.Context, sinceSeq int64) ([]model.Change, error)

Changes returns change_log rows after seq.

func (*Library) Chapters

func (l *Library) Chapters(ctx context.Context, pid model.PID) ([]model.Chapter, error)

Chapters returns a book's chapters in book-timeline order. CodeInvalid when pid is not a book.

func (*Library) Close

func (l *Library) Close() error

Close flushes buffered playback progress, then releases the catalog and write lock. The flush is best effort: shutdown should save resume positions, but a flush error should not block release. Read-only handles skip the flush.

It first drains any in-flight server-run jobs so they finalize against the still-open store; otherwise a shutdown mid-scan would leave a job row stuck "running" (reclaimed as crashed only on a later open). The jobs run under a server's lifetime context, so a shutdown that cancels that context makes them return promptly.

func (*Library) Count

func (l *Library) Count(ctx context.Context, q query.Query, userPID model.PID) (int, error)

Count returns the number of items matching q. userPID scopes any per-user field the same way Query does.

func (*Library) Coverage

func (l *Library) Coverage() []decode.FormatSupport

Coverage reports per-codec analysis decode support for doctor.

func (*Library) CreateUser

func (l *Library) CreateUser(ctx context.Context, name string) (*model.User, error)

CreateUser adds a playback user.

func (*Library) Credits

func (l *Library) Credits(ctx context.Context, itemPID model.PID) ([]model.Contributor, error)

Credits returns an item's contributors across every role.

func (*Library) CurrentChapter

func (l *Library) CurrentChapter(ctx context.Context, pid model.PID, positionMS int64) (*model.Chapter, error)

CurrentChapter resolves the chapter a resume position falls in (the nearest preceding chapter when between spans). It returns nil when the book has no chapters.

func (*Library) DataVersion

func (l *Library) DataVersion(ctx context.Context) (int64, error)

DataVersion returns SQLite's data_version, which moves whenever any connection commits. A consumer in another process polls it and pulls Changes when it changes.

func (*Library) DefaultUser

func (l *Library) DefaultUser(ctx context.Context) (*model.User, error)

DefaultUser returns the seeded default user.

func (*Library) DeleteSecret

func (l *Library) DeleteSecret(ctx context.Context, key string) error

DeleteSecret removes a stored credential.

func (*Library) DiagnosticSummary

func (l *Library) DiagnosticSummary(ctx context.Context, filter model.DiagnosticFilter) ([]model.DiagnosticCount, error)

DiagnosticSummary returns the matching diagnostics grouped by writer, code, and severity, most severe first. The filter's dimensions apply; its Limit and Offset do not (a summary aggregates the whole match).

func (*Library) Doctor

func (l *Library) Doctor(ctx context.Context) (*DoctorReport, error)

Doctor reports catalog stats and capability coverage. It is read-only: it never migrates, and it reports the catalog's applied schema version next to the build's so an operator can see when a read-write command would upgrade.

func (*Library) EditEntity

func (l *Library) EditEntity(ctx context.Context, entityType model.MergeEntity, entityPID model.PID, edits map[string]string, opts EntityEditOptions) error

EditEntity applies curation edits to one shared entity (an artist, release group, or album): sort-name overrides and release identifiers (barcode/label/catalog number and the entity MBIDs, plus the release-group type). It records user provenance and, by default, locks each edited field so enrichment leaves it alone. The catalog write is atomic. A field that does not apply to the entity type, or an invalid value, is rejected; a locked field returns CodeLocked unless opts.Force is set.

With opts.WriteBack the edited values that round-trip through a rescan are also fanned out across the entity's member files' on-disk tags. Write-back runs after the catalog edit committed, so a file that cannot be written is reported through a *WriteBackError naming the failed files while the entity edit stands.

func (*Library) EditField

func (l *Library) EditField(ctx context.Context, itemPID model.PID, field, value string, opts EditOptions) error

EditField edits one metadata field on a track or book item. See EditFields.

func (*Library) EditFields

func (l *Library) EditFields(ctx context.Context, itemPID model.PID, edits map[string]string, opts EditOptions) error

EditFields applies metadata-field edits to a track or book item. It records the edit as user provenance and, unless told otherwise, locks each field so enrichment and organize leave it alone. The catalog write is atomic and runs first. Which fields are editable depends on the kind (a track has artist, album, and the rest; a book has author, narrator, series), and a field that does not apply to the item's kind is rejected.

With opts.WriteBack set, the new values are also written into the backing files' on-disk tags through the WaxLabel writer seam. A track writes every edited scalar to its file; a book writes the audiobook tags a scan reads back (title→ALBUM, author→ALBUMARTIST, author_sort→ALBUMARTISTSORT, narrator→NARRATOR+COMPOSER, series+sequence→GROUPING, genre/year) across all its parts, leaving the enrichment-only book fields (subtitle, identifiers, publisher, description) DB-only. A file that cannot be written is reported through a *WriteBackError while the catalog edit stands.

Write-back is a separate step, not part of the atomic catalog edit. The edit has already committed by the time it runs, so a file that cannot be written does not roll the edit back. That covers a read-only mount, a permission error, a value the format cannot store, and a file shared by several items whose tags must not be clobbered per item. In those cases EditFields returns a *WriteBackError naming the failed files and records a per-file drift diagnostic, while the catalog edit stands. Callers should surface that as "catalog updated, on-disk tag sync failed" rather than a failed edit.

func (*Library) EditItemsFields

func (l *Library) EditItemsFields(ctx context.Context, edits []model.ItemFieldEdit, opts EditOptions) (*BatchEditResult, error)

EditItemsFields applies a per-item field-edit map to several items in one atomic transaction: each entry carries its own values (distinct titles and track numbers across an album, say) where EditManyFields applies one map to every item. The whole catalog batch commits or rolls back together; a duplicate pid or any invalid entry rejects it. Write-back then mirrors each edited item's own map into its on-disk tags, per item and best-effort, exactly as EditManyFields does; see there for the error contract (the returned result stays meaningful when err reports a post-commit write-back interruption).

func (*Library) EditManyFields

func (l *Library) EditManyFields(ctx context.Context, itemPIDs []model.PID, edits map[string]string, opts EditOptions) (*BatchEditResult, error)

EditManyFields applies the same field edits to several items in one atomic transaction, then optionally mirrors each edited item's new values into its on-disk tags. The catalog edit commits or rolls back as a whole. Write-back is per-item and best-effort: an item whose on-disk sync failed is recorded in the result's WriteBackErrors rather than failing the batch, matching single-item semantics. With opts.SkipLocked a locked item is skipped (reported in Skipped) instead of failing.

The returned *BatchEditResult is non-nil and meaningful even when err is non-nil: the catalog batch already committed, and err is only returned for a non-write-back failure during the on-disk pass (such as a canceled context). Callers should inspect the result's Edited list in that case, since those items were edited.

func (*Library) EmptyTrash

func (l *Library) EmptyTrash(ctx context.Context, opts EmptyTrashOptions) (*EmptyReport, error)

EmptyTrash permanently removes active trashed files from disk and drops their journal rows, reclaiming space. Options.OlderThan narrows the pass to entries older than that window. It runs under a "delete"-scoped job.

func (*Library) EndMaintenance

func (l *Library) EndMaintenance(ctx context.Context) error

EndMaintenance reopens the Library after a maintenance hand-off. It implements proxy.Maintainer.

func (*Library) Enrich

func (l *Library) Enrich(ctx context.Context, opts EnrichOptions) (*EnrichResult, error)

Enrich runs the metadata enrichment pass under an "enrich"-scoped job: MusicBrainz release-group/artist/genre resolution (MBID-first), Cover Art Archive covers, and the optional AcoustID fingerprint fallback. It is resumable and lock-respecting (never overwriting a tagged or user-locked field), caches provider responses, and degrades gracefully offline. Enrichment requires a MusicBrainz contact (Options.Enrichment.Contact); without one it returns CodeUnsupported. A scoped run (see EnrichOptions) holds the same "enrich" job lease as a full pass, so the two exclude each other (CodeConflict while one runs).

func (*Library) EnrichmentCoverage

func (l *Library) EnrichmentCoverage(ctx context.Context) (model.EnrichmentCoverage, error)

EnrichmentCoverage reports how many entities have been enriched, for doctor.

func (*Library) EntityByPID

func (l *Library) EntityByPID(ctx context.Context, kind read.EntityKind, pid model.PID) (*read.EntityInfo, error)

EntityByPID returns the summary info for one shared entity (artist, release group, album, genre, or series) by its public id: name, sort key, external identifiers, parent links, membership counts, and the libraries its members' files live in. It answers the pid a facet bucket, an entity-handle query field, or an item view hands out, without a facet scan. An unknown kind is CodeInvalid; an unknown pid is CodeNotFound.

func (*Library) EntityByPIDs

func (l *Library) EntityByPIDs(ctx context.Context, kind read.EntityKind, pids []model.PID) (map[model.PID]*read.EntityInfo, error)

EntityByPIDs is the batched form of EntityByPID: it returns summary info for many entities of one kind keyed by pid, omitting unknown pids and collapsing a repeated pid. It retires the per-hit EntityByPID cost when a consumer hydrates a page of entity pids (a restricted-user entity search that scopes each hit to the user's libraries, for instance). The caller batches within a single kind; a set larger than one internal chunk is not an atomic snapshot across chunks.

func (*Library) EntityCuration

func (l *Library) EntityCuration(ctx context.Context, entityType model.MergeEntity, entityPID model.PID) ([]model.EntityCuration, error)

EntityCuration returns an entity's curation rows (only non-default fields have rows, so an un-curated entity returns an empty slice).

func (*Library) EntityPlayState

func (l *Library) EntityPlayState(ctx context.Context, userPID model.PID, kind model.MergeEntity, entityPID model.PID) (*model.EntityPlayState, error)

EntityPlayState returns a user's star/rating state for one catalog entity. An unknown user or entity pid is CodeNotFound (like PlayStateFor); an entity the user has never starred or rated reads back zero-valued, not an error.

func (*Library) Export

func (l *Library) Export(ctx context.Context, w io.Writer) (*port.Manifest, error)

Export writes a versioned logical JSON export of catalog metadata plus critical per-user playback state. It never contains secrets and is for inspection and cross-tool portability; a byte Backup is the disaster-recovery path. It returns the export manifest.

func (*Library) ExportPlaylistRefs

func (l *Library) ExportPlaylistRefs(ctx context.Context, playlistPID, userPID model.PID) ([]model.PortableRef, error)

ExportPlaylistRefs returns portable identity descriptors for a playlist's members in order (a static list's stored order, a smart list evaluated for userPID). A playlist of local PIDs cannot cross catalogs, so the host ships these refs and rebuilds the list on the far side via ResolvePlaylistRefs. An empty userPID selects the default user.

func (*Library) Facet

func (l *Library) Facet(ctx context.Context, q query.Query, g read.GroupBy, userPID model.PID) (*read.FacetResult, error)

Facet groups the items matching q by a dimension and counts each bucket. The CLI, OpenSubsonic adapters, and stats code use this same API, so they share one canonical grouping result. userPID scopes any per-user filter in q.

func (*Library) File

func (l *Library) File(ctx context.Context, filePID model.PID) (*model.File, error)

File returns a backing file's identity and quality by its public id: size, mtime, content and essence hashes, the analyzed-essence stamp, and the audio quality fields (codec, bitrate, sample rate, bit depth), or CodeNotFound. It is the file-level companion to Get, so an embedder can pin an item's file identity without re-scanning.

func (*Library) FileDiagnostics

func (l *Library) FileDiagnostics(ctx context.Context, filter model.DiagnosticFilter) ([]model.FileDiagnostic, error)

FileDiagnostics returns the persisted per-file diagnostics matching the filter (the zero filter returns everything), each joined to its file's display path, in a stable path/origin/code order. It is the query surface over the rows scan, organize, analyze, and edit write-back record, so a consumer can drive a review queue ("which files have unsynced tags in this library") without running a full audit.

func (*Library) FindAltEncodings

func (l *Library) FindAltEncodings(ctx context.Context, itemPID model.PID) ([]AltEncoding, error)

FindAltEncodings returns other catalog items that are alt encodings of the given item: the inverted index proposes candidates (shared terms within the duration bucket), then each is verified by full-fingerprint similarity. The item must already be analyzed; an unanalyzed item yields no matches.

func (*Library) FindUpgrades

func (l *Library) FindUpgrades(ctx context.Context) ([]UpgradeGroup, error)

FindUpgrades groups the catalog's alt encodings (the same recording in different files) and ranks each group by audio quality, so a consumer can keep the best and prune or upgrade the rest. Grouping uses the fingerprint index via FindAltEncodings. MBID- and essence-identical encodings already collapse to one item during scan, so this surfaces the cross-encoding case. Items must be analyzed (fingerprinted); unanalyzed items never group.

It is a maintenance scan: it walks every track item and probes the fingerprint index for each, so it is meant for occasional use, not the hot path.

func (*Library) GCArt

func (l *Library) GCArt(ctx context.Context) (sources, thumbnails int, err error)

GCArt reclaims orphaned art: map rows whose entity is gone, then source images without live map references and their cached thumbnails. It returns the source and thumbnail counts removed. It is the repair for the orphan counts VerifyDerived reports.

func (*Library) GCOrphans

func (l *Library) GCOrphans(ctx context.Context) (*model.OrphanGCReport, error)

GCOrphans deletes childless artist/release_group/album/genre/series rows that have stayed orphaned past the grace window, recording the rest for a later sweep. It is manual-only (invoked by Vacuum and db verify --fix), never the watch loop.

func (*Library) Get

func (l *Library) Get(ctx context.Context, pid model.PID) (*model.ItemView, error)

Get returns a single item by public id.

func (*Library) GetMany

func (l *Library) GetMany(ctx context.Context, pids []model.PID) ([]*model.ItemView, error)

GetMany returns item views for the given pids in input order, skipping any pid with no matching item and collapsing a duplicate pid to its first position.

It is NOT an atomic snapshot: a pid array longer than the internal batch size is read across multiple SELECTs, so a concurrent write between batches can yield a mixed view. A UI list can feed from it; a caller that needs a consistent view of every pid at one instant cannot.

func (*Library) GetSecret

func (l *Library) GetSecret(ctx context.Context, key string) (string, error)

GetSecret returns a stored credential, or CodeNotFound.

func (*Library) ImportAcquired

func (l *Library) ImportAcquired(ctx context.Context, file AcquiredFile, kind model.Kind, meta AcquiredMeta) (*AcquiredResult, error)

ImportAcquired routes an acquired or manual file by kind. Tracks and books go through the import planner for the matching managed library, including duplicate checks, destination rendering, free-space checks, and acquisition provenance. Episodes go into the internal podcast library under an existing or manual show and are pinned by default. WaxBin never performs platform extraction itself; callers hand it an already acquired file or a remote enclosure URL.

func (*Library) ImportBatches

func (l *Library) ImportBatches(ctx context.Context, limit int) ([]*model.ImportBatch, error)

ImportBatches lists recorded import batches, newest first (limit 0 = all).

func (*Library) InboxFolders

func (l *Library) InboxFolders() []string

InboxFolders returns the configured staging folders.

func (*Library) IntegrityCheck

func (l *Library) IntegrityCheck(ctx context.Context) ([]string, error)

IntegrityCheck runs SQLite's PRAGMA integrity_check and returns the problems it reports (a healthy database returns a single "ok"). It is read-only.

func (*Library) ItemTags

func (l *Library) ItemTags(ctx context.Context, itemPID model.PID) ([]model.ItemTag, error)

ItemTags returns an item's custom tags (the non-standard frames WaxBin's model does not map, plus user-set tags), grouped by key.

func (*Library) ItemsByContentHash

func (l *Library) ItemsByContentHash(ctx context.Context, hash string) ([]*model.ItemView, error)

ItemsByContentHash returns every item backed by a file with the given whole-file content hash (identity.ContentHash, "sha256:" plus hex). Unlike the essence hash it changes on any byte change, tag writes included, which makes it the pre-transfer byte-identity probe: "do I already hold these exact bytes". Same result shape as ItemsByEssence.

func (*Library) ItemsByEssence

func (l *Library) ItemsByEssence(ctx context.Context, essence string) ([]*model.ItemView, error)

ItemsByEssence returns every item backed by a file with the given audio essence hash, matching any of an item's files. The essence hash is tag-stable (it survives a retag), which makes it the dedup oracle: "do I already hold this audio". The result is 0, 1, or N items; a single-file CUE album returns one item per virtual track carved from the shared file. A clean miss is an empty slice, not an error.

func (*Library) Job

func (l *Library) Job(ctx context.Context, pid model.PID) (*model.Job, error)

Job returns a job by public id, the read a client uses to tail a server-run job it submitted: progress and status while running, the terminal state, and the JSON Result summary once done.

func (*Library) Jobs

func (l *Library) Jobs(ctx context.Context, limit int) ([]*model.Job, error)

Jobs lists recent jobs, newest first.

func (*Library) Libraries

func (l *Library) Libraries(ctx context.Context) ([]*model.Library, error)

Libraries lists registered library roots.

func (*Library) Lock

func (l *Library) Lock(ctx context.Context, pid model.PID, fields ...string) error

Lock marks item fields as protected from enrichment and organize tag write-back. Unknown fields are rejected.

func (*Library) Loudness

func (l *Library) Loudness(ctx context.Context, itemPID model.PID) (*model.Loudness, error)

Loudness returns an item's measured ReplayGain (track and album gain/peak), or CodeNotFound when it has not been analyzed for loudness.

func (*Library) Lyrics

func (l *Library) Lyrics(ctx context.Context, pid model.PID) (*model.Lyrics, error)

Lyrics returns an item's structured lyrics (synced timed lines and/or an unsynchronized block), or CodeNotFound when it has none. Lyrics come from a sibling .lrc sidecar or embedded USLT/SYLT tags, captured at scan time; the catalog row is authoritative for reads.

func (*Library) Merge

func (l *Library) Merge(ctx context.Context, entityType model.MergeEntity, survivorPID, loserPID model.PID) (*model.MergeReport, error)

Merge collapses the loser entity onto the survivor: children (tracks, albums, genre links, contributor credits) are re-pointed onto the survivor, its MBID and enrichment marker are unioned when it lacks one, rollups are recomputed, and the loser is deleted. The survivor keeps its PID. This is the first-class entity-merge primitive for artists, release groups, albums, and genres. It repairs the duplicate-entity findings audit reports, and is the seam late enrichment uses to unify two heuristic rows that resolve to one MBID.

func (*Library) MergeMany

func (l *Library) MergeMany(ctx context.Context, entityType model.MergeEntity, survivorPID model.PID, loserPIDs []model.PID) ([]*model.MergeReport, error)

MergeMany collapses several loser entities onto the survivor in one atomic transaction: if any loser fails (e.g. an unknown PID), the whole batch rolls back, so a partial merge can never be left behind. Returns one report per loser.

func (*Library) OwnerInfo

func (l *Library) OwnerInfo() (sqlite.OwnerInfo, error)

OwnerInfo returns the current write-owner metadata from the lockfile.

func (*Library) Peaks

func (l *Library) Peaks(ctx context.Context, itemPID model.PID) (*model.PeaksData, error)

Peaks returns an item's stored waveform overview, or CodeNotFound.

func (*Library) PlanDelete

func (l *Library) PlanDelete(ctx context.Context, q query.Query, mode model.DeleteMode) (*trash.Plan, error)

PlanDelete computes a dry-run deletion plan for the items matching q under a mode (trash|prune|permanent). DeleteTrash moves files to the reversible per-library trash; the other modes bypass it to reclaim space. Every mode keeps the logical item (archived when it loses its last file).

func (*Library) PlanDeletePIDs

func (l *Library) PlanDeletePIDs(ctx context.Context, pids []model.PID, mode model.DeleteMode) (*trash.Plan, error)

PlanDeletePIDs computes a deletion plan for explicit item pids. It is the `rm <pid>` path; PlanDelete is the query-driven path used by retention/dedup.

func (*Library) PlanImport

func (l *Library) PlanImport(ctx context.Context, req ImportRequest) (*inbox.Plan, error)

PlanImport computes a reviewable import plan for a staging folder: which files would be imported (with destinations), which are catalog duplicates, and which are quarantined. It is read-only.

func (*Library) PlanOrganize

func (l *Library) PlanOrganize(ctx context.Context, q query.Query, profileName string) (*organize.Plan, error)

PlanOrganize computes a dry-run move plan for the selected items across every managed library, routing each item to the library whose root already contains it. Roots are non-overlapping, so kind routing is implicit in the current file path. A single managed library behaves exactly as before. profileName overrides each library's configured profile when non-empty.

func (*Library) PlayStatesForItems

func (l *Library) PlayStatesForItems(ctx context.Context, itemPIDs []model.PID) (map[model.PID][]model.PlayState, error)

PlayStatesForItems returns every user's playback state for each of the given items, keyed by item pid, each item's states ordered by user pid. Untouched and unknown items are absent. It goes through the playback service, so this process's buffered (not yet flushed) resume positions are overlaid; a reader in another process sees flushed state only, and a buffered-but-unflushed pair may synthesize a position-only state (see playback.Service.StatesForItems for the exact contract). It is the bulk "is anyone using these" read a multi-user consumer runs before dropping items.

func (*Library) Playback

func (l *Library) Playback() *playback.Service

Playback returns the playback-state service for progress, played status, ratings, stars, bookmarks, queue, and play sessions.

func (*Library) Playlists

func (l *Library) Playlists() *playlist.Service

Playlists returns the playlist service for static and smart playlists plus M3U8 import/export.

func (*Library) Podcasts

func (l *Library) Podcasts() *podcast.Service

Podcasts returns the podcast service: subscribe/sync feeds, download episodes, store transcripts/artwork, OPML import/export, and retention.

func (*Library) Profiles

func (l *Library) Profiles() []string

Profiles lists the organization profile names available to this library (built-ins plus any configured custom profiles), sorted.

func (*Library) Provenance

func (l *Library) Provenance(ctx context.Context, pid model.PID) ([]model.FieldProvenance, error)

Provenance returns an item's field provenance. Only non-default fields have rows, so a tag-only item returns an empty slice.

func (*Library) PruneChangeLog

func (l *Library) PruneChangeLog(ctx context.Context, keep int) (int, error)

PruneChangeLog trims the change_log to its newest keep rows, returning how many were deleted. A consumer that has fallen behind the retained horizon must full-resync (the documented delta-sync contract).

func (*Library) PurgeTrash

func (l *Library) PurgeTrash(ctx context.Context, trashPID model.PID) (int64, error)

PurgeTrash permanently removes a single active trash entry (file and journal row), returning the bytes reclaimed. A pid that is unknown, already purged, or already restored is CodeNotFound. Like EmptyTrash it runs under an fs-mutate job, one lease per call; the entry is resolved inside the lease, so a restore racing in (it holds the same scope) cannot slip between the check and the purge and lose its journal row.

func (*Library) Query

func (l *Library) Query(ctx context.Context, q query.Query, userPID model.PID) ([]*model.ItemView, error)

Query runs a compiled selection and returns matching item views. A query that references a per-user field (starred, rating, play_count, played, finished, or last_played) evaluates against userPID's play_state. An empty userPID selects the default user. A query with no user-state field is not scoped by user.

func (*Library) QueryPage

func (l *Library) QueryPage(ctx context.Context, q query.Query, cursor read.Cursor, limit int, desc bool, userPID model.PID) (*read.Page, error)

QueryPage returns one keyset-paginated, collation-correct window of items. Pass an empty cursor for the first page and the returned Next cursor for each subsequent page; pagination is stable under concurrent mutation. userPID scopes any per-user filter in q.

func (*Library) ReSealSecrets

func (l *Library) ReSealSecrets(ctx context.Context) (int, error)

ReSealSecrets seals every plaintext secret with the configured cipher, leaving already-sealed values untouched, in one transaction. It is the one-time adoption step after a secret cipher is first configured and is idempotent. It requires a configured cipher (Options.SecretCipher). Returns the number of secrets sealed.

func (*Library) ReadOnly

func (l *Library) ReadOnly() bool

ReadOnly reports whether the library was opened read-only.

func (*Library) RefreshAlbumGain

func (l *Library) RefreshAlbumGain(ctx context.Context) error

RefreshAlbumGain recomputes album-aware ReplayGain from the per-file loudness. Analyze runs it automatically; it is exposed for repair after a manual import.

func (*Library) RefreshRollups

func (l *Library) RefreshRollups(ctx context.Context) error

RefreshRollups recomputes the maintained rollups, the repair for the rollup drift that VerifyDerived can report.

func (*Library) RelocateRoot

func (l *Library) RelocateRoot(ctx context.Context, libPID model.PID, newRoot string) error

RelocateRoot re-points a library and every file under it at a new root path, for a portable restore onto a different machine or mount. The new path is validated against the other registered roots, the inbox folders, and the podcast download dir with the moved library's entry substituted, so a relocation cannot create the overlap Open would refuse. The internal podcast library is not relocatable: its root follows the podcasts dir config.

func (*Library) Reopen

func (l *Library) Reopen(ctx context.Context) error

Reopen restores a Library that was Closed for a maintenance-mode hand-off. It reopens the store in place (every subsystem keeps its store handle) and re-ensures the configured roots, mirroring a read-write Open. It refuses on a read-only library.

func (*Library) ResolveArt

func (l *Library) ResolveArt(ctx context.Context, ref model.EntityRef, role model.ArtRole, size int) (*model.ArtBlob, error)

ResolveArt resolves artwork for an entity in one role (empty = front). The front cover walks the fallback chain (track -> album -> release_group -> artist -> genre) to the first level that has one; every other role (back, disc, booklet, background) resolves at the requested level only, since an ancestor's auxiliary image would be misleading. A non-positive size returns the original image; a positive size returns a thumbnail scaled to fit a square box with that maximum side (generated once and cached). The blob reports the answering Level and whether an album's answer was Derived from a member track's cover. CodeNotFound means no consulted level has art in that role.

func (*Library) ResolvePlaylistRefs

func (l *Library) ResolvePlaylistRefs(ctx context.Context, refs []model.PortableRef) ([]model.RefResolution, error)

ResolvePlaylistRefs resolves a batch of portable refs to local items, preserving input order and reporting each entry's matched rung (MatchNone for a miss). The host derives the resolved and missing sets and rebuilds a local playlist from the resolved PIDs with the existing Playlists() primitives.

func (*Library) ResolveRef

func (l *Library) ResolveRef(ctx context.Context, ref model.PortableRef) (*model.ItemView, model.MatchRung, error)

ResolveRef walks the match ladder from most to least confident and returns the local item a portable ref names, along with the rung that matched so the host can report how sure the match is. The rungs are essence (exact bytes), strong id (an exact external identifier), fingerprint (the same recording in another encoding), then descriptive (fuzzy metadata). A clean miss returns (nil, MatchNone, nil); only a real IO failure returns an error. Every rung treats an empty or not-found result as a fall-through to the next. The strong-id and descriptive rungs dispatch on ref.Kind; essence and fingerprint do not care about kind.

func (*Library) RestoreTrash

func (l *Library) RestoreTrash(ctx context.Context, trashPID model.PID) error

RestoreTrash undoes a delete: it moves the trashed file back to its original path and re-scans it so the catalog re-links it (un-archiving its item). It refuses if the original path is occupied.

func (*Library) RotateSecrets

func (l *Library) RotateSecrets(ctx context.Context, oldCipher, newCipher model.SecretCipher, newKeyID string) (int, error)

RotateSecrets re-seals every secret from oldCipher to newCipher under newKeyID in one transaction, so a crash rolls the whole rotation back rather than leaving a mix of key generations. After it succeeds the caller reopens the Library with newCipher as Options.SecretCipher. Returns the number of secrets rotated.

func (*Library) RunOrganize

func (l *Library) RunOrganize(ctx context.Context, q query.Query, profileName string) (model.PID, error)

RunOrganize submits an organize pass as a background job and returns its PID. The job plans across the managed libraries and executes the moves in this process, so a server stays available while it runs. profileName overrides each library's configured profile when non-empty. The client tails the job and reads the organize.Report summary from Result.

func (*Library) Scan

func (l *Library) Scan(ctx context.Context, req ScanRequest) (*ScanResult, error)

Scan indexes the selected libraries under a single "scan"-scoped job.

Rollups are maintained transactionally for the entities touched by each scanned track, so no whole-catalog refresh is needed here; RefreshRollups is the repair path for drift reported by `db verify`. StartScan is the asynchronous variant a server exposes so a CLI can submit a scan without pausing it.

func (*Library) Search

func (l *Library) Search(ctx context.Context, q string, opt read.SearchOptions) (*read.SearchResult, error)

Search runs a grouped, BM25-ranked search across artists, albums, tracks, books, and episodes (episode transcript-body hits rank below metadata hits). Field weighting puts title hits above artist and album hits. opt.MaxCandidates bounds how many matches are ranked (recency-biased under truncation) and opt.Libraries scopes the search to items playable from those libraries; see read.SearchOptions for both contracts.

func (*Library) Serve

func (l *Library) Serve(ctx context.Context, socketPath string) error

Serve runs the local control server: it listens on the unix socket at socketPath and dispatches proxied catalog mutations (and the reads a mutating CLI command needs for its confirmation output) to this Library, plus the maintenance-mode hand-off. It blocks until ctx is canceled, then returns nil.

The socket is created owner-only (0600); the endpoint drives admin mutations, so a broader mode would let any local user issue unauthenticated writes. socketPath should match the Options.IPCSocket advertised in the lockfile so a CLI can discover it. Serve refuses on a read-only library.

func (*Library) SetChapters

func (l *Library) SetChapters(ctx context.Context, itemPID model.PID, chapters []model.Chapter, lock, force bool) error

SetChapters replaces a book's user-curated chapters (which win on read over the scanned ones), locking the "chapters" field by default. An empty list clears them. The input is a flat book-timeline list (StartMS/EndMS, what Chapters returns); for a multi-file book the store splits it across the parts.

func (*Library) SetCredits

func (l *Library) SetCredits(ctx context.Context, itemPID model.PID, role model.ContributorRole, names []string, opts CreditEditOptions) (int, error)

SetCredits replaces the contributors of one role on an item (music roles on a track, book roles on a book), recording user provenance and, by default, locking the credit.<role> field. With opts.WriteBack it also mirrors the credit into the backing file's on-disk tag: a track's music role, or a book's author/narrator across its parts. A book translator/editor credit has no round-trippable tag and is refused (returned as a *WriteBackError) while the catalog edit stands. It returns the number of contributor names actually stored (after trimming blanks and de-duplicating by artist), so a caller does not report a wipe (an unresolvable name that cleared the role) as a set.

func (*Library) SetEntityArt

func (l *Library) SetEntityArt(ctx context.Context, entityType model.ArtEntity, entityPID model.PID, role model.ArtRole, raw []byte, writeBack bool) error

SetEntityArt sets a durable image on a non-item entity (album, artist, release group, genre, or podcast) under one role (empty = front; the closed model.ArtRole vocabulary, validated by the store). This makes album art durable: ResolveArt prefers it over the read-derived track cover. Entity art takes no lock/force (the lock system is item-scoped). With writeBack an album front cover is also embedded into every member track's file; other entity covers stay catalog-only on disk (they have no single natural file target), and a non-front role is refused with writeBack for the same reason an item's is.

func (*Library) SetEntityRating

func (l *Library) SetEntityRating(ctx context.Context, userPID model.PID, kind model.MergeEntity, entityPID model.PID, rating *int, asOf *int64) error

SetEntityRating sets (0..100) or clears (rating nil) a user's rating for a catalog entity. asOf carries the recorded change time and enforces recorded-time last-writer-wins; see SetEntityStar.

func (*Library) SetEntityStar

func (l *Library) SetEntityStar(ctx context.Context, userPID model.PID, kind model.MergeEntity, entityPID model.PID, starred bool, asOf *int64) error

SetEntityStar stars or unstars a catalog entity for a user, recording the star time for recency ordering of the starred list. asOf (unix nanoseconds, nil = server now) is the recorded flip time; when supplied the engine enforces recorded-time last-writer-wins, so a replayed offline toggle cannot undo a later out-of-band change, exactly as item SetStar does. A value-identical call is a silent no-op that preserves the stored star time.

func (*Library) SetItemArt

func (l *Library) SetItemArt(ctx context.Context, itemPID model.PID, role model.ArtRole, raw []byte, lock, force, writeBack bool) error

SetItemArt sets (or, with empty bytes, clears) one artwork role on a track/book item from raw image bytes. An empty role means the front cover, which locks the "art" field by default (the lock guards the scan's cover re-derive; the other roles have no scan producer, so lock/force are ignored for them). A clear deletes only the named role. With writeBack the cover is also embedded into the item's backing file, or into every part of a multi-file book so an external player sees the same cover on each part; only the front cover has an embedded representation, so writeBack with any other role is refused with CodeInvalid before anything is written. A file that cannot be written is reported through a *WriteBackError while the catalog edit stands.

func (*Library) SetItemTag

func (l *Library) SetItemTag(ctx context.Context, itemPID model.PID, key string, values []string, opts TagEditOptions) (string, int, error)

SetItemTag replaces a custom tag's ordered values on an item, locking "tag.<KEY>" by default so a scan does not re-derive it from the file. Empty (or whitespace-only) values clear the tag. The key is normalized to canonical uppercase; a reserved key (one WaxBin owns through the scalar, credit, or identifier APIs) is rejected. It returns the canonical key stored and the number of values actually stored after trimming (0 means the tag was cleared).

func (*Library) SetLyrics

func (l *Library) SetLyrics(ctx context.Context, itemPID model.PID, ly *model.Lyrics, lock, force bool) error

SetLyrics replaces an item's lyrics with a user-curated set, locking the "lyrics" field by default. Passing nil clears the lyrics.

func (*Library) SetSecret

func (l *Library) SetSecret(ctx context.Context, key, value string) error

SetSecret stores a named credential in the secret table. Values are never logged or written to a logical export, but a full byte Backup contains them unless redacted.

func (*Library) StarredEntities

func (l *Library) StarredEntities(ctx context.Context, userPID model.PID, kind model.MergeEntity) ([]model.EntityPlayState, error)

StarredEntities lists a user's starred entities of one kind, most-recently-starred first. Pair it with EntityByPIDs to hydrate display names in one batch.

func (*Library) StartAnalyze

func (l *Library) StartAnalyze(ctx context.Context, opts AnalyzeOptions) (model.PID, error)

StartAnalyze submits the analyze pass as a background job and returns its PID.

func (*Library) StartEnrich

func (l *Library) StartEnrich(ctx context.Context, opts EnrichOptions) (model.PID, error)

StartEnrich submits the enrichment pass as a background job and returns its PID. It refuses (before starting a job) when enrichment is not configured or the scoping options do not resolve, matching the synchronous Enrich.

func (*Library) StartScan

func (l *Library) StartScan(ctx context.Context, req ScanRequest) (model.PID, error)

StartScan submits a scan as a background job and returns its PID immediately. The job runs to completion in this process; a client follows it through Job and reads the scan.Result summary from the finished job's Result.

func (*Library) Stats

func (l *Library) Stats(ctx context.Context, userPID model.PID, topN int) (*read.Stats, error)

Stats returns a library summary using the same Facet grouping as browse plus per-user playback state. An empty userPID selects the default user; topN caps the ranked lists.

func (*Library) Subscribe

func (l *Library) Subscribe() (<-chan model.Change, func())

Subscribe registers an in-process listener for change_log rows after each mutating commit. The cancel func unsubscribes. Cross-process consumers should poll DataVersion and then call Changes.

func (*Library) TagKeys

func (l *Library) TagKeys(ctx context.Context) ([]read.TagKeyCount, error)

TagKeys returns every custom-tag key in the catalog with the number of distinct items carrying it, most-used first. It is the discovery primitive for custom-tag browse dimensions: list the keys, then facet or filter on tag.<KEY>.

func (*Library) Trash

func (l *Library) Trash(ctx context.Context, includeRestored bool, limit int) ([]model.TrashEntry, error)

Trash lists trash journal entries, newest first. includeRestored controls whether already-restored rows are shown; limit 0 returns all.

func (*Library) Unlock

func (l *Library) Unlock(ctx context.Context, pid model.PID, fields ...string) error

Unlock clears the lock on each field, dropping rows that no longer carry any curated state so provenance stays sparse.

func (*Library) Users

func (l *Library) Users(ctx context.Context) ([]*model.User, error)

Users lists the playback users (the default first).

func (*Library) Vacuum

func (l *Library) Vacuum(ctx context.Context) (*VacuumReport, error)

Vacuum GCs orphaned entities and art, then compacts the database file, returning what was reclaimed. It takes the write lock. Orphan entities are swept before art so their freed art-map rows are reclaimed in the same pass.

func (*Library) VerifyDerived

func (l *Library) VerifyDerived(ctx context.Context) (*sqlite.DerivedReport, error)

VerifyDerived runs the derived-data consistency check (FTS, rollups, and generated sort keys versus the source rows). It is read-only; it reports drift rather than repairing it.

func (*Library) Watch

func (l *Library) Watch(ctx context.Context, opts WatchOptions) error

Watch runs a foreground watcher that keeps the catalog in sync with the filesystem until ctx is canceled (returning a CodeCanceled error on a clean shutdown). It refuses on a read-only library.

WATCH IS A FOREGROUND MODE. A read-write WaxBin holds an exclusive advisory lock on the catalog for the whole process lifetime, so while watch runs, every OTHER mutating command in another terminal (organize, analyze, enrich, import, scan --force) is refused (read-only queries are always allowed). Stop the watcher to do manual mutation, or run waxbin serve instead when other terminals need to mutate concurrently: it proxies mutations over a local control socket (see Library.Serve). Idle lock release is deliberately post-1.0.

The watched roots are snapshotted here, at start. A root registered later through AddRoot is scanned and organized (those resolve roots per run) but not watched until the watcher restarts; the root's create delta on the change feed is the signal to do so.

func (*Library) YearInReview

func (l *Library) YearInReview(ctx context.Context, userPID model.PID, year, topN int) (*read.YearReview, error)

YearInReview returns a per-user listening recap for one calendar year (UTC): session/minute/track totals, catalog additions that year, and the top artists/genres/tracks by play count. An empty userPID uses the default user.

type Options

type Options struct {
	// DBPath is the catalog database (local filesystem only).
	DBPath string
	// Roots are library roots to ensure on open (upserted; never deleted here).
	Roots []config.Root
	// ReadOnly opens without taking the write lock and forbids mutations.
	ReadOnly bool
	// Logger receives structured logs; nil discards (the library never prints).
	Logger *slog.Logger
	// WriteOwner identifies this owner in the lockfile and job rows; defaulted
	// from hostname + pid when empty.
	WriteOwner string
	// IPCSocket, if set, is advertised in the lockfile for local proxy support.
	IPCSocket string

	// Profiles defines additional organization profiles and built-in overrides.
	Profiles []config.ProfileDef
	// Inbox folders are staging directories importable into a managed library.
	Inbox []string
	// FreeSpaceReserveBytes is headroom an import preflight keeps free.
	FreeSpaceReserveBytes int64
	// WriteReplayGainTags mirrors computed ReplayGain into files after analyze (off
	// by default; the catalog stays authoritative).
	WriteReplayGainTags bool
	// StampItemPID stamps the backing item's WaxBin PID into a tag during organize's
	// tag write-back on managed roots (off by default).
	StampItemPID bool
	// Podcasts configures the podcast engine (download dir + network policy).
	Podcasts config.PodcastConfig
	// Enrichment configures the metadata enrichment pass (MusicBrainz/CAA/AcoustID).
	Enrichment config.EnrichConfig
	// SourceProviders are injected acquisition providers, such as a youtube provider
	// supplied by another module. The built-in netsafe rss provider is always
	// registered; these register under their own source types. The default CLI build
	// does not ship extra providers.
	SourceProviders []source.Provider
	// EnrichmentProviders are injected metadata-enrichment providers (Discogs, Last.fm,
	// Audnexus, Hardcover, fanart.tv, ...) supplied by an embedding module. They take
	// priority over the key-free built-ins (Cover Art Archive, ListenBrainz, LRCLIB)
	// for a value conflict; the MusicBrainz identity spine still resolves the anchoring
	// MBID first. The default CLI build ships none.
	EnrichmentProviders []enrich.Provider
	// SecretCipher, when set, seals secret-table values (private-feed passwords) at
	// rest; an embedder supplies one to own the key. Nil keeps secrets in plaintext.
	SecretCipher model.SecretCipher
	// SecretKeyID labels the key/epoch sealed values are written under; defaults to
	// "1" when a cipher is set without one. Change it when rotating to a new key.
	SecretKeyID string

	// Storage tuning; zero values fall back to library defaults.
	BusyTimeoutMS int
	CacheSizeKB   int
	MmapSizeBytes int64
	ReadPoolSize  int
}

Options configures opening a Library.

func OptionsFromConfig

func OptionsFromConfig(cfg *config.Config, log *slog.Logger) Options

OptionsFromConfig derives Options from a resolved Config.

type ScanRequest

type ScanRequest struct {
	LibraryPID model.PID // empty scans every library
	SubPath    string    // optional sub-path under a single library's root
	// Force bypasses the incremental fast-path, re-hashing and re-parsing every file
	// even when its size and mtime are unchanged (repair, or after an essence bump).
	Force bool
	// AdoptStampedPIDs restores item PIDs from WAXBIN_ITEM_PID tags during a rebuild
	// (essence-first, adopted only when unambiguous). Off for a normal scan.
	AdoptStampedPIDs bool
	// ForceReconcile bypasses the survival-gate floor so a deliberate large deletion
	// is reconciled to missing (the recovery path). An explicit operator action; the
	// watcher never sets it.
	ForceReconcile bool
	// IgnoreLocks re-derives every field from disk even when the user locked it, so a
	// `scan --force --ignore-locks` discards curated edits. Off by default: a scan
	// (including --force) preserves locked fields, and write-back is what propagates a
	// DB edit back onto disk.
	IgnoreLocks bool
}

ScanRequest selects what to scan.

type ScanResult

type ScanResult struct {
	JobPID model.PID
	Total  scan.Result
	Runs   []scan.Result
}

ScanResult reports a scan, including the job it ran under.

type TagEditOptions

type TagEditOptions struct {
	// Lock locks the "tag.<KEY>" field against a scan re-deriving it from the file; on
	// by default.
	Lock bool
	// Force overrides a locked custom tag.
	Force bool
}

TagEditOptions configures a custom-tag edit, mirroring EditOptions.

type UpgradeCandidate

type UpgradeCandidate struct {
	ItemPID    model.PID
	FilePID    model.PID
	Title      string
	Artist     string
	Codec      string
	Bitrate    int
	SampleRate int
	BitDepth   int
	Lossless   bool
	// Best marks the highest-quality member of the group (the recommended keeper);
	// the rest are lower-quality alt encodings that could be pruned or upgraded.
	Best bool
}

UpgradeCandidate is one encoding of a recording, with the quality fields the policy ranks on.

type UpgradeGroup

type UpgradeGroup struct {
	Members []UpgradeCandidate
}

UpgradeGroup is a set of catalog items that are the same recording in different encodings (grouped by fingerprint), ordered best-quality first.

type VacuumReport

type VacuumReport struct {
	ArtSourcesReclaimed int
	ThumbnailsReclaimed int
	OrphansDeleted      int
	OrphansPending      int
}

VacuumReport summarizes a vacuum: the derived garbage reclaimed before the on-disk compaction.

type WatchActivity

type WatchActivity struct {
	Trigger string // initial | scheduled | full | live
	Changed bool
}

WatchActivity summarizes one watch cycle for a heartbeat consumer.

type WatchOptions

type WatchOptions struct {
	LibraryPID         model.PID // empty watches every user library root
	Interval           time.Duration
	FullRescanInterval time.Duration
	Live               bool
	WriteSettle        time.Duration
	MaxWatchDirs       int // 0 = unlimited; caps live fsnotify watches (see watch.Options)
	Analyze            bool
	SyncSources        bool
	// OnActivity, when set, is called after each cycle for a CLI heartbeat.
	OnActivity func(WatchActivity)
}

WatchOptions configures a foreground watch (see watch.Options).

type WriteBackError

type WriteBackError struct {
	ItemPID  model.PID
	Edits    map[string]string
	Failures []WriteBackFailure
}

WriteBackError reports that a catalog edit committed but its on-disk tag write-back did not fully apply. The catalog holds the new values. The named files' tags stay out of sync until they are re-written, which is also recorded as a per-file diagnostic.

func (*WriteBackError) Error

func (e *WriteBackError) Error() string

type WriteBackFailure

type WriteBackFailure struct {
	FilePID model.PID
	Path    string
	Reason  string
}

WriteBackFailure records one backing file whose on-disk tag write-back did not apply, with a human-readable reason.

Directories

Path Synopsis
Package analyze owns the PCM-decoding pass behind the scan/analyze boundary.
Package analyze owns the PCM-decoding pass behind the scan/analyze boundary.
Package art contains WaxBin's pure-Go image handling for the read-side art resolver: content hashing for the content-addressed store, format and dimension probing, and thumbnail generation (decode, scale to fit, re-encode).
Package art contains WaxBin's pure-Go image handling for the read-side art resolver: content hashing for the content-addressed store, format and dimension probing, and thumbnail generation (decode, scale to fit, re-encode).
Package audit inspects a catalog for quality and integrity problems and reports severity-ranked findings: duplicate/split entities, inconsistent metadata, missing art/ReplayGain, bad filenames, orphaned sidecars, path conflicts, invalid feeds, derived-data drift, and (opt-in) on-disk integrity/corruption.
Package audit inspects a catalog for quality and integrity problems and reports severity-ranked findings: duplicate/split entities, inconsistent metadata, missing art/ReplayGain, bad filenames, orphaned sidecars, path conflicts, invalid feeds, derived-data drift, and (opt-in) on-disk integrity/corruption.
cmd
waxbin command
Command waxbin is the scriptable CLI for the WaxBin audio library engine.
Command waxbin is the scriptable CLI for the WaxBin audio library engine.
Package config loads WaxBin's configuration with the precedence flag > env (WAXBIN_*) > json > default, and validates that library roots are absolute and non-overlapping (a file belongs to exactly one library).
Package config loads WaxBin's configuration with the precedence flag > env (WAXBIN_*) > json > default, and validates that library roots are absolute and non-overlapping (a file belongs to exactly one library).
Package decode is WaxBin's PCM-decoding layer, and the only package in the cataloging and analysis path that knows WaxFlow exists (test fixtures and pidpath's integration Examples name it too, neither of which ships).
Package decode is WaxBin's PCM-decoding layer, and the only package in the cataloging and analysis path that knows WaxFlow exists (test fixtures and pidpath's integration Examples name it too, neither of which ships).
Package enrich populates catalog entities from external metadata providers: MusicBrainz (release-group type, artist aliases/relations, genres, and the MBIDs that anchor identity) and the Cover Art Archive (release-group cover art), with an optional AcoustID fingerprint fallback for release groups that text search cannot resolve.
Package enrich populates catalog entities from external metadata providers: MusicBrainz (release-group type, artist aliases/relations, genres, and the MBIDs that anchor identity) and the Cover Art Archive (release-group cover art), with an optional AcoustID fingerprint fallback for release groups that text search cannot resolve.
Package envelope is the version envelope for persisted, re-parsed artifacts (query rule documents, organization profile definitions, export manifests).
Package envelope is the version envelope for persisted, re-parsed artifacts (query rule documents, organization profile definitions, export manifests).
Package fingerprint computes WaxBin's internal acoustic fingerprint and the inverted-index terms used for alt-encoding grouping.
Package fingerprint computes WaxBin's internal acoustic fingerprint and the inverted-index terms used for alt-encoding grouping.
Package identity computes the content hash and the entity-identity keys that keep public ids stable across rescans, retags, and moves.
Package identity computes the content hash and the entity-identity keys that keep public ids stable across rescans, retags, and moves.
Package inbox imports audio staged outside the library.
Package inbox imports audio staged outside the library.
internal
caps
Package caps detects the optional external helper the analyze pass can use.
Package caps detects the optional external helper the analyze pass can use.
diskfree
Package diskfree reports the free space on the filesystem holding a path, for import/move preflight checks.
Package diskfree reports the free space on the filesystem holding a path, for import/move preflight checks.
fsx
Package fsx provides the long-path-safe filesystem move/copy primitives shared by organize, inbox, and trash.
Package fsx provides the long-path-safe filesystem move/copy primitives shared by organize, inbox, and trash.
netsafe
Package netsafe provides the HTTP client used for remote feeds, enclosures, transcripts, artwork, and future metadata lookups.
Package netsafe provides the HTTP client used for remote feeds, enclosures, transcripts, artwork, and future metadata lookups.
pathx
Package pathx holds small filesystem-path helpers shared across subsystems.
Package pathx holds small filesystem-path helpers shared across subsystems.
testaudio
Package testaudio synthesizes minimal tagged audio files for tests.
Package testaudio synthesizes minimal tagged audio files for tests.
Package jobs runs mutating background work under a scoped advisory lease and a tracked job row.
Package jobs runs mutating background work under a scoped advisory lease and a tracked job row.
Package loudness models EBU R128 / ReplayGain 2.0 loudness for the catalog: the Result a track's measurement is stored as, the reference level its gain is computed against, and the conversion from a measured dB domain into that Result.
Package loudness models EBU R128 / ReplayGain 2.0 loudness for the catalog: the Result a track's measurement is stored as, the reference level its gain is computed against, and the conversion from a measured dB domain into that Result.
Package meta reads catalog metadata, cheap audio properties, and the tag-independent audio-essence hash without decoding PCM.
Package meta reads catalog metadata, cheap audio properties, and the tag-independent audio-essence hash without decoding PCM.
Package model holds WaxBin's unified domain types and the repository interfaces the rest of the engine depends on.
Package model holds WaxBin's unified domain types and the repository interfaces the rest of the engine depends on.
Package organize renders managed-library paths and applies file relocations.
Package organize renders managed-library paths and applies file relocations.
Package peaks computes compact max-amplitude waveforms for scrubbers and track overviews.
Package peaks computes compact max-amplitude waveforms for scrubbers and track overviews.
Package pidpath resolves an item PID to where its audio lives, cached, with the cache invalidated from the catalog's change feed (a cheap DataVersion read, then Changes rows).
Package pidpath resolves an item PID to where its audio lives, cached, with the cache invalidated from the catalog's change feed (a cheap DataVersion read, then Changes rows).
Package playback is the consumer-facing playback-state service.
Package playback is the consumer-facing playback-state service.
Package playlist is the consumer-facing playlist service: static and smart playlist CRUD plus M3U8 import/export.
Package playlist is the consumer-facing playlist service: static and smart playlist CRUD plus M3U8 import/export.
Package podcast parses RSS podcast feeds and OPML, then exposes the service for subscriptions, sync, downloads, transcripts, artwork, and retention.
Package podcast parses RSS podcast feeds and OPML, then exposes the service for subscriptions, sync, downloads, transcripts, artwork, and retention.
Package port handles catalog backup, restore, redaction, and logical JSON export.
Package port handles catalog backup, restore, redaction, and logical JSON export.
Package proxy is WaxBin's local control channel.
Package proxy is WaxBin's local control channel.
Package query is WaxBin's shared selection engine.
Package query is WaxBin's shared selection engine.
Package read holds WaxBin's canonical read/consumer API primitives: faceted aggregation, collation-correct keyset pagination, and the canonical "unknown" buckets.
Package read holds WaxBin's canonical read/consumer API primitives: faceted aggregation, collation-correct keyset pagination, and the canonical "unknown" buckets.
Package scan walks library roots and persists catalog rows.
Package scan walks library roots and persists catalog rows.
Package source defines the provider interface used to resolve, enumerate, and fetch remote media.
Package source defines the provider interface used to resolve, enumerate, and fetch remote media.
store
sqlite
Package sqlite is WaxBin's SQLite-only DataStore.
Package sqlite is WaxBin's SQLite-only DataStore.
Package trash plans and applies file removal without deleting catalog history.
Package trash plans and applies file removal without deleting catalog history.
Package watch runs a long-lived watcher that keeps the catalog in sync with the filesystem while WaxBin holds the write lock.
Package watch runs a long-lived watcher that keeps the catalog in sync with the filesystem while WaxBin holds the write lock.
Package waxerr defines WaxBin's typed errors.
Package waxerr defines WaxBin's typed errors.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL