Documentation
¶
Overview ¶
Package meta reads catalog metadata, cheap audio properties, and the tag-independent audio-essence hash without decoding PCM. It adapts WaxLabel, which parses tags, artwork, chapters, lyrics, and essence digests for supported containers. PCM decoding belongs to the analyze pass.
Index ¶
- Constants
- func BookFieldTagKeys(field string) ([]string, bool)
- func EntityFieldTagKey(entityType model.MergeEntity, field string) (string, bool)
- func LRCPartial(lines []model.SyncedLine, dropped []int) bool
- func PackSeriesGrouping(name, seq string) string
- func ParseCue(text string) []model.Chapter
- func ParseLRC(text string) (lines []model.SyncedLine, dropped []int)
- func RoleTagKey(role model.ContributorRole) (string, bool)
- func SplitCredits(s string) []string
- func TagKeyForField(field string) (string, bool)
- type Adapter
- type CueSheet
- type CueTrack
- type DerivedSortPair
- type FileMeta
- type PictureEdit
- type Reader
- type TagEdit
- type WriteResult
- type Writer
Constants ¶
const BookSeriesTagKey = string(tag.Grouping)
BookSeriesTagKey is the single tag that carries a book's series and sequence. The scanner splits it back into a series name and sequence (parseSeries); PackSeriesGrouping is the inverse that builds the value an edit writes.
Variables ¶
This section is empty.
Functions ¶
func BookFieldTagKeys ¶
BookFieldTagKeys returns the on-disk tag keys the audiobook scanner reads back for a book metadata field, and whether the field round-trips through a tag at all. A field with no keys is DB-only by design; see bookFieldTagKeys. series is handled through BookSeriesTagKey, not here.
func EntityFieldTagKey ¶
func EntityFieldTagKey(entityType model.MergeEntity, field string) (string, bool)
EntityFieldTagKey returns the on-disk tag key an entity-curation field fans out to across the entity's member files, and whether the field has one. Only the fields that round-trip through a rescan WITHOUT disturbing the entity's identity are wired: an album's non-identity release identifiers and sort (BARCODE, LABEL, CATALOGNUMBER, ALBUMSORT) and an artist's sort (ARTISTSORT).
An entity MBID is deliberately NOT fanned (neither album nor artist): a member track's MusicBrainz ID is the entity's identity key on the next scan (AlbumKey/artist match are mbid-first), but the entity edit updates only the mbid column, not the row's match_key, so writing the MBID to the files would re-key the entity to a fresh row and orphan its curation and locks. Barcode/label/catalog#/sort are not identity inputs, so they fan safely. A release-group field and a release-group type also stay DB-only.
func LRCPartial ¶
func LRCPartial(lines []model.SyncedLine, dropped []int) bool
LRCPartial reports whether a .lrc parse is worth acting on, meaning some lines were timed and some were dropped.
The guard on len(lines) > 0 carries the weight. A fully untimed .lrc reports every line as dropped, and that is the ordinary case of a plain-text file next to the audio rather than a broken sidecar. Reporting it would flag a great many normal files.
func PackSeriesGrouping ¶
PackSeriesGrouping builds the GROUPING tag value that carries a book's series name and sequence, the inverse of parseSeries: it uses the "#" marker (the shortest form parseSeries recognizes) so a written value re-reads to the same name and sequence on a rescan. An empty name yields an empty value (clearing the tag); an empty sequence yields the bare name.
func ParseCue ¶
ParseCue parses a .cue sheet into file-relative navigation chapters, one per TRACK. It returns nil when the sheet has no usable tracks. A book with no embedded chapters uses these, marked source='cue' so embedded chapters stay authoritative.
func ParseLRC ¶
func ParseLRC(text string) (lines []model.SyncedLine, dropped []int)
ParseLRC parses .lrc sidecar text into WaxBin synced lines (millisecond offsets, time-ordered), plus the 1-based numbers of the lines the parser dropped: non-blank lines that yielded no timed lyric and are not recognized LRC structure, meaning a malformed timestamp or plain untimed text. Blank lines, [ar:]/[ti:] metadata, [offset:], and bare [section] headers count as structure and are not reported.
It returns nil lines for text with no timed lines.
The dropped list is why this reads through the reporting parser at all: it is the only way to tell a partly-broken sidecar from a plain-text one. It uses the uncapped variant because WaxBin reads the whole file into memory before parsing, so the parser's own line cap never protected anything. The read is bounded instead (see maxSidecarBytes).
func RoleTagKey ¶
func RoleTagKey(role model.ContributorRole) (string, bool)
RoleTagKey returns the canonical WaxLabel tag key an on-disk write uses for a contributor role, and whether the role has one wired for write-back.
func SplitCredits ¶
SplitCredits splits a combined credit string (authors or narrators) into trimmed individual names. It delegates to identity.SplitCredits, the canonical shared splitter, and stays here for the adapter/scanner callers that reference meta.SplitCredits.
func TagKeyForField ¶
TagKeyForField returns the canonical WaxLabel tag key an on-disk write uses for a catalog field name, and whether the field has one.
Types ¶
type Adapter ¶
type Adapter struct{}
Adapter reads metadata through WaxLabel. One open file feeds tag parsing, stream properties, and the tag-independent audio-essence digest.
func (*Adapter) Read ¶
Read parses path with WaxLabel and returns its tags, properties, and essence hash. It never decodes PCM. A parse error is returned; a file that parses but carries no hashable essence yields a populated FileMeta with an empty EssenceHash (the scanner falls back to the content hash).
type CueSheet ¶
type CueSheet struct {
Title string // album title (a TITLE line before the first TRACK)
Performer string // album artist (a PERFORMER line before the first TRACK)
Genre string // REM GENRE
Year int // REM DATE (leading four-digit year)
Tracks []CueTrack
}
CueSheet is a parsed .cue sheet describing a single backing audio file: the album-level fields declared before the first TRACK, plus one entry per TRACK. Two readers consume it: ParseCue derives book-navigation chapters from the tracks, and the scanner carves a single-file album rip into virtual tracks (each TRACK its own item and offset window). WaxBin reads .cue sidecars directly (WaxLabel has no cue parser).
func ParseCueSheet ¶
ParseCueSheet parses a .cue sheet into its album-level fields and per-track entries. It returns nil when the sheet declares no TRACK, so a caller can treat an empty or unparseable sheet the same as an absent one.
func (*CueSheet) Chapters ¶
Chapters projects a cue sheet's tracks into file-relative navigation chapters, one per usable TRACK, using each track's INDEX 01 as the start and its TITLE as the label. End offsets are left open (0) for the book read path to fill from the next chapter's start.
A track with no usable INDEX 01 is dropped rather than anchored at 0. A chapter's start is not only its own coordinate: the read path fills each open end from the next chapter's start, so one fabricated 0 would report the chapter before it as ending at the start of the book.
func (*CueSheet) UsableTracks ¶
UsableTracks returns the tracks that declared a parseable INDEX 01, in sheet order. A track without one has no start offset, and the start offset is what every consumer here anchors on, so admitting it at a fabricated 0 does damage in both directions: it claims the head of the file for itself, and it truncates the track or chapter before it, whose end is read off the next one's start.
Callers that must report the drop read Tracks and filter on StartValid themselves; the scanner does, so the sheet's own diagnostics name what was skipped.
type CueTrack ¶
CueTrack is one TRACK of a cue sheet: its declared number, title, and performer (the track's own PERFORMER, empty when it inherits the album performer), plus its INDEX 01 start offset in CD frames.
StartValid reports that the track declared an INDEX 01 and that it parsed. A track without a usable one has no start at all, yet StartFrames still reads 0, and 0 is a real offset naming the file's first sample rather than an absent one.
Consumers should read UsableTracks, which drops those tracks already, or check StartValid themselves. Letting one default to 0 does damage in two directions, because no consumer here reads a start in isolation: the scanner carves a content window from it, and a book chapter's end is read off the next chapter's start, so a fabricated 0 misplaces its own track and truncates the one before it.
type DerivedSortPair ¶
DerivedSortPair names a display field whose edit regenerates a stored sort key: the catalog sort field that can carry an explicit value or a lock (empty for artist, whose sort has no edit surface of its own) and the on-disk tag a scan's derivation reads. The write-back uses it to clear the stale sort tag beside a display-name edit; a tag left behind would feed the next derivation and undo the regenerated sort. See Library.writeBackFields.
func DerivedSortPairs ¶
func DerivedSortPairs() []DerivedSortPair
DerivedSortPairs returns the display-field to derived-sort correspondences, in a fixed order.
type FileMeta ¶
type FileMeta struct {
Tags model.Tags
EssenceHash string
// Lyrics is the file's embedded lyrics (unsynced USLT and/or synced SYLT), or
// nil when it carries none. A sibling .lrc sidecar, parsed by the scanner, takes
// precedence over this.
Lyrics *model.Lyrics
// CoverArt is the file's embedded front-cover image (raw bytes + format), or nil
// when it embeds none. The scanner finalizes its hash and dimensions and falls
// back to a directory cover image when this is absent.
CoverArt *model.ArtImage
// ItemPIDHint is the value of the file's WAXBIN_ITEM_PID tag, if present. It is a
// rebuild-only hint for restoring the backing item's original PID; identity stays
// essence-first (the tag is copyable), so the store adopts it only when unambiguous.
ItemPIDHint string
// Diagnostics are observations about the file worth persisting (an unsupported
// container, truncated audio, a legacy-only tag fallback). They are born here,
// like Lyrics, rather than in the scanner from an os.Stat like an AuxObservation,
// so they share only the model-input-to-store leg of the route with those.
//
// FilePID and DisplayPath are left unset: the reader is given a path, not a
// catalog identity. The store fills them.
Diagnostics []model.FileDiagnostic
}
FileMeta is everything the scanner needs from one parse: the tag/property set plus the tag-independent essence hash. EssenceHash is empty when the container carries no hashable audio essence (e.g. a malformed file); the scanner then falls back to the content hash so the file is still cataloged.
type PictureEdit ¶
PictureEdit is a change to a file's embedded front cover. Clear removes the front cover; otherwise Data (raw image bytes) is embedded as the front cover, replacing any existing one. Either way only the front cover is touched. Other embedded pictures such as a back cover or booklet scan are left intact, matching the catalog's "art" field, which models the front cover alone.
type Reader ¶
Reader reads tags, properties, and the essence hash from a file. It must never decode PCM (scanning is I/O-bound by contract).
type TagEdit ¶
TagEdit is one set-or-clear of a tag key. Values sets the key (replacing any existing values); an empty/nil Values clears it. Key is a canonical tag key name (e.g. "ALBUMARTIST", "REPLAYGAIN_TRACK_GAIN", "R128_TRACK_GAIN", a custom "WAXBIN_ITEM_PID"); an unknown key round-trips as a native custom field.
type WriteResult ¶
type WriteResult struct {
Changed bool
Size int64
MTimeNS int64
ContentHash string
Warnings []model.TagWriteWarning
}
WriteResult reports the file's on-disk state after a write. When Changed is false the edits were a no-op and the file was not rewritten (Size/MTimeNS/ContentHash are left zero, since the caller already holds the current values).
Warnings is independent of Changed: a no-op can still carry a warning, so a caller that branches on Changed alone would miss exactly the case where the edit had no effect because the format could not store the value.
type Writer ¶
type Writer struct{}
Writer applies tag edits to files on disk through WaxLabel. It builds on WaxLabel's file-path parse (ParseFile), which carries the source identity SaveBack needs, and enables essence verification so a tag-only write can never silently alter the audio: the rewrite re-hashes the copied audio bytes and refuses to commit if they changed. WaxBin uses WaxLabel read-only everywhere else; this is the one writer seam.
func (*Writer) Apply ¶
Apply writes edits to the file at path atomically and in place, preserving the audio essence (WithVerifyEssence). It returns the new size, mtime, and content hash so the caller can update the catalog's file row through the optimistic file-state seam. A no-op edit set reports Changed=false without rewriting.
func (*Writer) ApplyPicture ¶
func (w *Writer) ApplyPicture(ctx context.Context, path string, edit PictureEdit) (*WriteResult, error)
ApplyPicture embeds (or clears) the front-cover picture on the file at path, preserving the audio essence exactly as Apply does (WithVerifyEssence re-hashes the copied audio and refuses to commit if it changed). A format that cannot store a picture, or a read-only format, is refused with CodeUnsupported so the caller records a write-back failure rather than silently dropping the cover. A no-op reports Changed=false without rewriting: this covers clearing a file that has no pictures and re-embedding the bytes it already carries.