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

/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: and Composed 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 an agent, 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.