Accompanist Lyrics Core ships with parsers for TTML, Lyricify Syllable, Enhanced LRC, and Kugou KRC. If you need to consume a proprietary or niche format that none of those cover, you can implement theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/6xingyv/accompanist-lyrics-core/llms.txt
Use this file to discover all available pages before exploring further.
ILyricsParser interface and plug it straight into AutoParser — no forking required.
When to write a custom parser
Proprietary formats
Your backend or content provider delivers lyrics in a format unique to your service that no built-in parser recognises.
Niche community formats
Lesser-known open formats (e.g. ASS subtitles repurposed for music, plain-text with custom delimiters) that are not part of the built-in set.
Implementing ILyricsParser
The ILyricsParser interface has three methods, but you only need to implement canParse and ONE of the two parse overloads — each overload has a default body that delegates to the other:
The two
parse overloads have mutual default implementations — each delegates to the other. Implement whichever suits your parsing logic and the second will work automatically.canParse is called first, before any parsing work begins. It should return true only when the content is definitively your format. Keep it cheap — a header check or a regex on the first few lines is ideal.Be as specific as possible. A loose check (e.g. “contains a pipe character”) risks matching content intended for a different parser.
Avoid expensive operations here —
canParse can be called multiple times in a row as AutoParser tries each registered parser in order.Choose the overload that best fits how your format is structured. If your format is naturally line-oriented, implement
parse(lines: List<String>). If you need access to the raw string (e.g. for multi-line tokens or regex over the whole document), implement parse(content: String).The return type is
SyncedLyrics, which holds a list of ISyncedLine items. Use SyncedLine for simple timestamped text, or KaraokeLine.MainKaraokeLine for syllable-level karaoke data.override fun parse(content: String): SyncedLyrics {
val lines = content.lines()
.filter { it.isNotBlank() && !it.startsWith("##") }
.mapIndexedNotNull { _, line ->
val parts = line.split("|")
if (parts.size < 2) return@mapIndexedNotNull null
val startMs = parts[0].toLongOrNull()?.toInt() ?: return@mapIndexedNotNull null
val text = parts[1]
SyncedLine(
content = text,
translation = null,
start = startMs,
end = startMs + 3000 // placeholder end time
)
}
return SyncedLyrics(lines = lines)
}
SyncedLine requires end >= start. If your format omits end times, derive them from the next line’s start time, or use a fixed offset as a safe placeholder.Pass a list of
ILyricsParser instances to AutoParser. Parsers are tried in the order they appear in the list — the first one whose canParse returns true wins.val autoParser = AutoParser(
parsers = listOf(
MyCustomParser(), // checked first
TTMLParser(),
LyricifySyllableParser,
EnhancedLrcParser,
KugouKrcParser
)
)
Full working example
The example below parses a hypothetical format where the first line is a header tag (##MY_FORMAT##) and each subsequent line is <startMs>|<lyric text>:
Tips for robust parsers
Fast canParse checks
Check only the minimum needed — a magic byte sequence, a header tag, or the first non-empty line. Avoid regex over the full document.
Graceful null handling
Use
mapIndexedNotNull or similar to skip malformed lines rather than throwing, so one bad line does not discard the entire file.Derive missing end times
If your format has no explicit end timestamps, compute each line’s end from the next line’s start, and give the last line a fixed offset (e.g. +3 000 ms).
Return empty SyncedLyrics on failure
If parsing fails entirely, return
SyncedLyrics(emptyList()) rather than throwing — consistent with how AutoParser itself handles unrecognised content.