Response format
The default TTML provider returns lyrics as Timed Text Markup Language, with syllable-level timing.
Response structure
{
"ttml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
"score": 0.97
} | Field | Type | Description |
|---|---|---|
ttml | string | The full TTML XML document as a string |
score | number | Match confidence from 0 to 1. Higher is better. Sent only when the lyrics were fetched for the request (or for a matching request already in progress), not on cache hits. |
TTML structure
The TTML document contains timing information for each word. For the full specification, see the W3C TTML1 Recommendation.
The example below is formatted for readability. Real responses are not formatted. See whitespace handling.
<?xml version="1.0" encoding="UTF-8"?>
<tt xmlns="http://www.w3.org/ns/ttml">
<body>
<div>
<p begin="00:00:18.234" end="00:00:21.891">
<span begin="00:00:18.234" end="00:00:18.567">The </span>
<span begin="00:00:18.567" end="00:00:18.901">club </span>
<span begin="00:00:18.901" end="00:00:19.234">isn't </span>
<span begin="00:00:19.234" end="00:00:19.568">the </span>
<span begin="00:00:19.568" end="00:00:19.901">best </span>
<span begin="00:00:19.901" end="00:00:20.234">place </span>
<span begin="00:00:20.234" end="00:00:20.568">to </span>
<span begin="00:00:20.568" end="00:00:20.901">find </span>
<span begin="00:00:20.901" end="00:00:21.234">a </span>
<span begin="00:00:21.234" end="00:00:21.891">lover</span>
</p>
</div>
</body>
</tt> Key elements
<p> - Line element
Each <p> represents a line of lyrics:
begin- Start time of the lineend- End time of the line
<span> - Word element
Each <span> within a line represents a word:
begin- Start time of the wordend- End time of the word- Text content is the word itself (including trailing spaces)
Important: whitespace handling
Do not format or pretty-print the TTML response.
The response uses whitespace between <span> elements to represent spaces
between syllables/words. This whitespace is not enclosed in tags. If you format the XML
(for example with a pretty-printer), you lose these spaces and break the syllable
sync.
Keep the original string exactly as received. When parsing, use the raw
textContent of each span, which includes the intended whitespace.
Time format
Times can appear in two formats:
HH:MM:SS.mmm- e.g.,00:01:30.500= 90.5 secondsM:SS.mmm- e.g.,1:16.656= 76.656 seconds
Your parser should handle both. The JavaScript example below does.
Parsing example (JavaScript)
@braccato/parsers is the parser Better Lyrics
uses for these responses. It handles background vocals, agents, transliterations and
whitespace for you. The example below shows how to do it by hand.
function parseTTML(ttmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(ttmlString, 'text/xml');
const lines = [];
doc.querySelectorAll('p').forEach(p => {
const agent = p.getAttribute('ttm:agent'); // Agent ID as defined in metadata (v1, v2, v1000, etc.)
const words = [];
// Recursively collect spans, tracking background vocal context
function collectSpans(element, isBackground = false) {
element.childNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'span') {
const isBg = isBackground || node.getAttribute('ttm:role') === 'x-bg';
const begin = node.getAttribute('begin');
if (begin) {
// This is a timed span (word)
words.push({
text: node.textContent,
begin: parseTime(begin),
end: parseTime(node.getAttribute('end')),
isBackground: isBg
});
} else {
// This is a wrapper span, recurse into it
collectSpans(node, isBg);
}
}
});
}
collectSpans(p);
lines.push({
begin: parseTime(p.getAttribute('begin')),
end: parseTime(p.getAttribute('end')),
agent,
words
});
});
return lines;
}
function parseTime(timeStr) {
// Handles "HH:MM:SS.mmm", "M:SS.mmm", "SS.mmm"
const parts = timeStr.split(':');
let seconds = 0;
if (parts.length === 3) {
seconds = parseInt(parts[0]) * 3600 +
parseInt(parts[1]) * 60 +
parseFloat(parts[2]);
} else if (parts.length === 2) {
seconds = parseInt(parts[0]) * 60 +
parseFloat(parts[1]);
} else {
seconds = parseFloat(parts[0]);
}
return Math.round(seconds * 1000); // Return milliseconds
} RTL language support
For right-to-left languages (Arabic, Hebrew, Persian, etc.), the TTML document includes language attributes:
<tt xml:lang="ar" xmlns="http://www.w3.org/ns/ttml">
...
</tt>
Check the xml:lang attribute to determine text direction.
Common RTL language codes: ar, he, fa, ur.
Multiple speakers
Songs with multiple vocalists use the ttm:agent attribute on <p>
elements to identify which speaker is singing each line:
<p ttm:agent="v1" begin="0:05.123" end="0:08.456">
<span begin="0:05.123" end="0:06.789">First </span>
<span begin="0:06.789" end="0:08.456">vocalist</span>
</p>
<p ttm:agent="v2" begin="0:08.456" end="0:11.789">
<span begin="0:08.456" end="0:10.123">Second </span>
<span begin="0:10.123" end="0:11.789">vocalist</span>
</p> Agent IDs are defined in the TTML metadata header:
<head>
<metadata>
<ttm:agent type="person" xml:id="v1">
<ttm:name type="full">Artist Name</ttm:name>
</ttm:agent>
<ttm:agent type="person" xml:id="v2">
<ttm:name type="full">Featured Artist</ttm:name>
</ttm:agent>
</metadata>
</head> - Agent IDs are defined via
<ttm:agent xml:id="...">in the metadata - Most files use
v1,v2, etc., but some use different IDs likevoice1 v1000(group) andv2000(other) conventionally indicate multiple speakers singing together- Agent metadata may include speaker names via
<ttm:name> - The
typeattribute must be one of:person,character,group,organization, orother
Background vocals
Background vocals within a line are wrapped in a <span> with
ttm:role="x-bg":
<p ttm:agent="v1" begin="0:15.000" end="0:20.000">
<span begin="0:15.000" end="0:17.500">Lead lyrics </span>
<span ttm:role="x-bg">
<span begin="0:17.500" end="0:20.000">background vocals</span>
</span>
</p> Transliterations
Some TTML responses include synced transliterations (e.g., romanized Japanese lyrics).
These are stored in the metadata header under <transliterations>:
<head>
<metadata>
<transliterations>
<transliteration xml:lang="ja-Latn">
<text for="L10">
<span begin="1:03.557" end="1:04.868">mayonaka no</span>
<span begin="1:04.868" end="1:05.457">doa o</span>
<span begin="1:05.457" end="1:05.930">tata</span>
<span begin="1:05.930" end="1:07.907">ki</span>
</text>
</transliteration>
</transliterations>
</metadata>
</head> - The
xml:langattribute specifies the target script (e.g.,ja-Latnfor Japanese romanization) - Each
<text for="...">references a line by itsitunes:keyattribute - Contains timed
<span>elements, same as the main lyrics - Note: Some transliterations have punctuation (like
)) outside of<span>elements as raw text nodes
Response headers
These response headers add context:
| Header | Description |
|---|---|
X-Cache-Status | Whether the response came from cache (HIT, MISS, etc.) |
X-Provider | Which provider served the response. Only on the provider-specific endpoints, such as /ttml/getLyrics. /getLyrics does not send it. |
X-RateLimit-Type | Current rate limit tier |