Repository navigation
Lyrics: the sources still to add, and the labels still to derive #47
robert-dean
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Status (2026-10-10)
Five of this file's six phases shipped between 2026-10-04 and 2026-10-05, in the order it asked for.
packages/plugin-sdk/src/capabilities/lyrics.ts, anddeadair.track_lyrics(0063) holding plain text, synced lines with end offsets, a language, aninstrumentalflag and the fetch provenance.lyrics.sanitize.tsand the module's reads keep thetext off the wire.
plugins/lrclib, strict artist/title/album/duration match, theUser-Agent and
Retry-Afterhandling, a configurable base URL.lyrics/vocal.ranges.tsturns synced lines into stretches of singing,vocal.markers.service.tspublishes where the singing starts and stops, the talk-up limit readsthe first through
vocal.runway.ts, and an operator override of both lives in0064_track_vocal_overrides.sql, read first byvocal.markers.reader.ts.deadair.track_lyric_labels(0065, 0070) holds a versioned mooddistribution with the produced-nothing mark, judged again once a lyric arrives. Its one consumer is
the mood LEAN on a schedule slot (
director/mood.lean.ts), not the never-play predicate this filerecommended; see "Labels" below.
lyric.subject.tswrites one sentence aboutwhat a record is about, refuses one that echoes a run of the lyric (
quoted-lyric), and the breakprompt carries the same guard. A station that speaks clean withholds the subject of any record with
an explicit copy (
lyric.labels.repository.ts).What is left: the library source (old phase 4), the second source, the word-level refinement inside
the markers, and the labels that would refuse a record rather than lean toward one. The constraint
below governs every one of them, so it stays.
The constraint that shapes the whole file
A lyric is somebody's copyrighted text in full, and it is the one kind of enrichment the station
must never do what it does with everything else to: say it out loud. Every other payload in
capabilities/enrichment.tsexists to be spoken or shown. This one exists to be read by the hostand by nothing else.
That is not a caution, it is a structural rule, because there is already a path that would break it
with no model involved and no way to decline.
SourceDocumentis prose handed over verbatim for thehost to extract claims from, and the floor extractor (
fact.lead.ts) takes an article's OPENINGSENTENCE as a claim, because there the claim and the quote are the same span and nothing can
hallucinate. Hand lyrics over as a
documents[]entry and that floor writes the first line of thesong into
facts.claim, withfacts.source_quoteset to the same line, and the break writer thensays it on air with a citation. The floor cannot fail, which is exactly why it must not be pointed
at this.
So the first decision is a negative one, and it is the load-bearing one:
documents. Not as a convenience, not "for now".deadair.factsrow.source_quoteis a column whose entire purpose isto be displayed and to be checkable, which is the opposite of what a lyric quote may be.
forTheWiredropsdocumentsout of the enrichment read, and anything holding lyric text needs the same treatmentfrom its first commit rather than after the console has already shipped a panel.
And the hazard is not confined to a lyrics provider. Verifying the Genius row below turned up the
same rule needing to be stated one file over: an ANNOTATION arrives welded to the lyric line it
annotates. A referent carries the highlighted
fragmentplus up to 200 characters of the surroundinglyric on each side, so annotation prose reaches the host with copyrighted text attached to it.
Anything that treats annotations as a
SourceDocumentinherits this file's hazard whole, which isnow recorded in fact-enrichment too, because that is the file whose path it sits on.
Everything below is what lyrics ARE for, given that.
The library source
What changed since the 2026-09-02 measurement. Then, every record on this install came from one
provider and no library-shaped source was installed, which is why this phase moved from first to
fourth. Navidrome is now a bundled plugin (
plugins/navidrome, capabilitiescatalog,streamandenrichment), so there is a library to ask wherever an operator runs one. It does not declarelyrics, and nothing reads Subsonic lyrics today.The source, as the provider table had it: Subsonic
getLyrics/ OpenSubsonicgetLyricsBySongId,plus whatever is embedded in the files (ID3
USLT/SYLT, VorbisLYRICS/UNSYNCEDLYRICS). Stillthe cleanest source when one exists: no third party, no rate limit, no terms, and the lyric is one the
operator's own files carry. So it takes priority over LRCLIB wherever it can answer.
Four things about that call are easy to get wrong and all four have been paid for already
elsewhere. The plain lyrics call FLATTENS structured lyrics to text and discards the per-line
timings, which are the entire point, so the call to make is the by-song-id one and the field to read
is the structured list. A track may carry several structured entries (languages, a synced one and an
unsynced one), so pick the synced entry rather than the first. Each entry carries a global
offsetwhose sign is counter-intuitive, since positive means the lyrics appear SOONER, making the effective
start
start - offset, clamped at zero. And an older server answers the legacy single-value shapeinstead, which is plain text and has no timings at all.
lrcmux, the second source
An aggregator rather than a database: one request fans out across several upstreams, the best answer
is picked, and everything is cached. MIT, written in Go, and it self-hosts the same way the primary
source does (a compose stack of API, frontend and a cache, or the binary, or a hosted deployment),
with a public instance available for an operator who does not want to run one. Its provider ids
include the large streaming catalogs whose lyrics are otherwise unreachable from here.
It takes the second-source slot for a plain reason: the alternative in that slot is one integration for
one upstream, and this is one integration for several. It also fails in the right direction, since
an aggregator that loses an upstream degrades rather than stops. What it does not do is remove the
matching problem, which is the same names-and-duration problem the primary source has, one layer
further away from the answer.
The word-level database, and why it is not a source
Community-submitted TTML with per-syllable timings, organised as one folder per streaming platform,
each file named by that platform's track id, with a per-platform
index.jsonland a combined rawindex. There is no API; a lookup is a raw file fetch by id. Contributor-written portions are CC0.
It is in this file for one specific reason. Every record here carries a streaming-platform id
already, so this is the one source that can be keyed by an identifier that cannot match the wrong
record, and the one that answers the measured error bar: it times WORDS, and the gap between the
first line and the first word is the entire problem under the vocal onset. Where it has a record, its
onset needs no floor and no safety margin.
The catch is coverage, and it is decisive: it is community-submitted and weighted heavily toward
catalogs this station's library is not drawn from, so most records here will not be in it. That makes
it a refinement applied where it hits and never a source anything falls back to. Treat a hit as
strictly better than a line-timed onset and a miss as ordinary.
Labels
The never-play predicate was not built, because moods arrived first and became a lean. This file
asked for the first consumer of derived labels to be "a never-play predicate rather than mood
steering, because a predicate is checkable and a mood is a matter of taste". It went the other way:
#413 judged moods and wired them to a slot's lean, and the lean is deliberately incapable of keeping
anything off the air (a judgement read off lyrics is wrong often enough that it only ever makes a
fitting record more likely). That is the right posture for a mood. It leaves the checkable half
unbuilt.
What is left of it:
track_lyric_labelsholds moods and a subject and nothing else;track_sources.advisoryis theonly explicitness the station has, and it is the provider's, not a reading of the lyric.
director/block.rules.service.ts,0069_block_rules.sql) refuse by genre or tag today. A lyric-derived field there is the shape thisfile meant, and never-play-rules is where
the "own field, never written over
track_sources.advisory" trap is recorded from the other side.predicates from lyrics found no implementation at all, which is an argument for wiring the first
one to one checkable consumer, not a reason to skip it.
The buy-the-labels question is mostly settled by building them. The licensed Analysis API that
returns meaning, moods, themes and a content rating per ISRC was the alternative to holding text at
all. The station now holds text and judges it with its own model, so the question that remains is
narrower: whether a bought content rating would beat a judged one for the refusing labels above,
where a wrong answer keeps a record off the air.
A console lyrics panel
Still not on this list and still not scoped. It is the one use where the text itself leaves the
machine, and it should be decided on its own rather than arrive as the tail of a feature justified by
a talk-up limit.
Things to get right when it lands
enough to matter, and nothing announces it: a wrong lyric yields a confident wrong vocal onset and
a confident wrong set of labels. Duration is the disambiguator every lyric service keys on for
this reason, and the strictness argument is the one pick-artist-matching makes: a near miss
does not error, it produces the wrong record while the console says otherwise. The measurement
above is not evidence against this. It is evidence that the strict form SUCCEEDS often enough that
there is no reason to ever loosen it.
The established tool in this space excludes two of its own configured sources by DEFAULT because
they block its user agent, which is the failure this station is worst at noticing: a walk that
completes, a count that stays at zero, and nothing in the log that reads as broken. Any source
added here needs its first refusal to be loud. A source that answers
403to a declared client isa source that is not installed, and the console's counts are the only place that distinction will
ever be visible.
posture as
llm.setGeneratorandllm.factExtraction, and the talk-up read is the exceptionworth arguing about rather than assuming.
the thing that cannot fail, and they are the thing that should be incapable of quoting.
shortest path: the extraction service is one function call away from any prose the host holds.
Related
SourceDocumentpath this file exists to stayoff, and for the floor extractor that makes staying off it structural rather than a matter of
care. It carries the annotation finding above: an annotation arrives welded to the lyric line
it annotates, so the annotation path inherits this file's hazard rather than escaping it.
vocal_onset, its one consumer, and the beat layer thisdoes not replace.
consumers of derived labels. The second was station-intelligence §5 and was rescoped out of it
on 2026-08-19; it is also where the "own field, never written over
track_sources.advisory" trapis recorded from the other side.
errors.
analysis/README.md§ "The rule, stated once" for the shape of the licence argument this filemakes a different version of: there the rule was about what enters the analysis path, here it is
about what leaves the station's mouth.
All reactions