Parsers
@braccato/parsers turns lyrics text into an array of Lyric objects.
It reads TTML, LRC, SRT, QRC and plain text, so one package covers every provider this API
serves. The output is what @braccato/core renders,
but it is plain data and works without the renderer, in the browser or in Node.
Install
npm i @braccato/parsers Detect the format
detectParser looks at the text and returns the parser that can read it. It tries
TTML, LRC, SRT, QRC and plain text, in that order. Plain text accepts anything, so you always get
a parser back.
import { detectParser } from "@braccato/parsers";
const lyrics = detectParser(text).parse(text, durationMs);
Every parser has the same two methods: detect(input) and
parse(input, duration). The duration is the song length in
milliseconds. The API takes d in seconds, so multiply by 1000.
You can also import a parser directly:
import { TTMLParser, LRCParser, SRTParser, QRCParser, PlainParser } from "@braccato/parsers"; The Lyric shape
Each Lyric is one line. Only startTimeMs, durationMs and
words are always present. The other fields depend on what the source provides.
interface Lyric {
startTimeMs: number;
durationMs: number;
words: string;
key?: string;
parts?: LyricPart[];
agent?: string;
translations?: { [lang: string]: string };
romanization?: string;
timedRomanization?: LyricPart[];
isInstrumental?: boolean;
}
interface LyricPart {
startTimeMs: number;
durationMs: number;
words: string;
isBackground?: boolean;
explicit?: boolean;
} | Field | Description |
|---|---|
words | The full line text, background vocals included |
parts | Syllable or word timing. A line without parts is line-synced |
agent | The vocalist: v1, v2, and so on, with v1000 for a group |
key | The source's own line id, used to attach translations and romanizations |
isInstrumental | A synthetic line with empty words that fills a gap nobody sings over |
The TTML and QRC parsers insert instrumental lines for gaps longer than 5 seconds: an intro before the first line, and a break between two lines. They add an outro only when you pass the song duration.
To use the types without the runtime code, import them from @braccato/parsers
(import type) or install @braccato/types.
TTML
The default /getLyrics endpoint returns the document in the ttml field.
Pass the string as received. Do not reformat it (see
whitespace handling).
import { TTMLParser } from "@braccato/parsers";
const { ttml } = await fetch(url).then((response) => response.json());
const lyrics = TTMLParser.parse(ttml);
The TTML parser reads syllable timing, ttm:role="x-bg" background vocals,
ttm:agent vocalists, explicit flags, itunes:key, translations and
transliterations. It maps agent ids to stable slots (v1, v2,
v1000 for a group), so you do not need to know the ids a document declares.
parseTTMLContent
TTMLParser.parse ignores its duration argument, because a TTML document states its own
duration. Use parseTTMLContent when you also want the document language or sync level,
or when you want to give a fallback duration:
import { parseTTMLContent } from "@braccato/parsers";
const { lyrics, isWordSynced, language } = parseTTMLContent(ttml, {
songDurationMs: 170000,
instrumentalGapMs: 5000,
}); | Option | Description |
|---|---|
songDurationMs | Used only when <body> has no dur. Needed for the outro instrumental line |
instrumentalGapMs | Silence longer than this becomes an instrumental line. Defaults to 5000 |
language comes from the document's xml:lang. You can pass it to the
renderer as lyricsOptions.language.
Kugou
/kugou/getLyrics returns LRC-style text in the lyrics field. It is
line-synced, so the parsed lines have no parts.
import { LRCParser } from "@braccato/parsers";
const { lyrics: lrc } = await fetch(url).then((response) => response.json());
const lyrics = LRCParser.parse(lrc, durationSeconds * 1000); LRCParser.parse also runs a set of timing fixers made for word-timed LRC. To get the
document exactly as written, use parseLRC, and run lrcFixers yourself if
you want them:
import { parseLRC, lrcFixers } from "@braccato/parsers";
const lyrics = parseLRC(lrc, durationSeconds * 1000);
lrcFixers(lyrics); lrcFixers changes the array in place.
/qq/getLyrics returns a QRC document inside a <QrcInfos> XML
envelope in the lyrics field. The parser accepts the envelope or a bare QRC body.
See QRC format for the structure.
import { parseQRC } from "@braccato/parsers";
const { lyrics: qrc } = await fetch(url).then((response) => response.json());
const lyrics = parseQRC(qrc, durationSeconds * 1000, {
title: "Wanderlust",
artist: "The Weeknd",
}); The third argument is optional song metadata. The QRC parser:
- Skips header tags such as
[ti:]and[ar:] - Drops credit lines such as
Lyrics by:andComposed by: - With metadata, drops lines near the start that only repeat the title or artist
- Turns a
Name:prefix at the start of a line into anagent, and removes it from the text. The singer carries over to the next lines until a new prefix appears
QRCParser.parse(input, duration) does the same without metadata, and
detectParser returns QRCParser for a QQ response.
Parse once
Parsing is not free. Parse when the lyrics arrive, keep the result, and give the same array to the renderer. Do not parse on every playback tick.