{"id":2851,"date":"2026-10-17T17:03:34","date_gmt":"2026-10-17T15:03:34","guid":{"rendered":"https:\/\/extendsclass.com\/blog\/?p=2851"},"modified":"2026-10-17T16:53:42","modified_gmt":"2026-10-17T14:53:42","slug":"building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt","status":"publish","type":"post","link":"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt","title":{"rendered":"Building a synced lyrics view in JavaScript: Parsing LRC, enhanced LRC and WebVTT"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Scrolling, highlighted lyrics used to be a feature of big streaming apps. Today it shows up in much smaller projects: an artist&#8217;s website, a karaoke mode in a party game, a language-learning app that plays songs, a lyric video tool. The browser side is not hard, but the details are easy to get wrong: odd timestamp formats, files with Windows line endings, word-level tags, and audio that drifts against the text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide walks through a small, dependency-free implementation: parse an LRC file (including the enhanced word-by-word variant), find the current line efficiently, keep the display in sync with an &lt;audio&gt; element, and convert the result to WebVTT when you need subtitles. At the end there is a checklist of edge cases and a practical way to get reliable test files.<\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_47_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"ez-toc-toggle-icon-1\"><label for=\"item-6ad4cc12475ba\" aria-label=\"Table of Content\"><span style=\"display: flex;align-items: center;width: 35px;height: 30px;justify-content: center;direction:ltr;\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/label><input  type=\"checkbox\" id=\"item-6ad4cc12475ba\"><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#The_formats_in_two_minutes\" title=\"The formats in two minutes\">The formats in two minutes<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Step_1_Parse_LRC_into_lines_and_words\" title=\"Step 1: Parse LRC into lines and words\">Step 1: Parse LRC into lines and words<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Step_2_Find_the_current_line_quickly\" title=\"Step 2: Find the current line quickly\">Step 2: Find the current line quickly<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Step_3_Keep_the_view_in_sync_with_the_audio\" title=\"Step 3: Keep the view in sync with the audio\">Step 3: Keep the view in sync with the audio<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Step_4_Convert_to_WebVTT_for_subtitles\" title=\"Step 4: Convert to WebVTT for subtitles\">Step 4: Convert to WebVTT for subtitles<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Edge_cases_worth_testing\" title=\"Edge cases worth testing\">Edge cases worth testing<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Getting_reliable_test_files\" title=\"Getting reliable test files\">Getting reliable test files<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/extendsclass.com\/blog\/building-a-synced-lyrics-view-in-javascript-parsing-lrc-enhanced-lrc-and-webvtt\/#Wrapping_up\" title=\"Wrapping up\">Wrapping up<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"The_formats_in_two_minutes\"><\/span><strong>The formats in two minutes<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Standard LRC<\/strong> is plain text. Each lyric line starts with a timestamp in square brackets:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;ti:Night Drive]\n&#91;ar:Example Artist]\n&#91;offset:+200]\n&#91;00:12.40]Headlights on the empty road\n&#91;00:16.90]Radio low, the windows down\n&#91;00:21.30]&#91;01:05.30]Keep on driving, keep on driving\n&#91;00:25.80]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lines can carry several timestamps when the same words repeat (a chorus, for example). Tags such as [ti:] and [ar:] are metadata, and [offset:] shifts every timestamp by a number of milliseconds. A line with a timestamp and no text is commonly used to clear the display during an instrumental break.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Enhanced LRC<\/strong> adds a timestamp in angle brackets before each word, so a player can light up words as they are sung:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;00:12.40]&lt;00:12.40&gt;Headlights &lt;00:13.10&gt;on &lt;00:13.35&gt;the &lt;00:13.60&gt;empty &lt;00:14.20&gt;road<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>WebVTT<\/strong> is the subtitle format browsers understand natively. Each cue has a start and end time, so it maps naturally onto lines of lyrics.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_1_Parse_LRC_into_lines_and_words\"><\/span><strong>Step 1: Parse LRC into lines and words<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The parser below handles multiple timestamps per line, optional fractions with one to three digits, metadata, the [offset:] tag, word tags and Windows line endings.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const TIME = '(\\\\d{1,3}):(\\\\d{2})(?:&#91;.:](\\\\d{1,3}))?';\nconst LINE_TAG = new RegExp(`^\\\\&#91;${TIME}\\\\]`);\n\nconst WORD_TAG = new RegExp(`&lt;${TIME}>(&#91;^&lt;]*)`, 'g');\nconst toSeconds = (min, sec, frac) =>\n\u00a0\u00a0Number(min) * 60 + Number(sec) + (frac ? Number(`0.${frac}`) : 0);\n\nfunction parseLrc(text) {\n\u00a0\u00a0let offset = 0;\n\u00a0\u00a0const entries = &#91;];\n\u00a0\u00a0for (const raw of text.replace(\/^\/, '').split(\/\\r?\\n\/)) {\n\u00a0\u00a0\u00a0\u00a0let rest = raw.trim();\n\u00a0\u00a0\u00a0\u00a0const off = rest.match(\/^\\&#91;offset:\\s*(&#91;+-]?\\d+)\\s*\\]$\/i);\n\u00a0\u00a0\u00a0\u00a0if (off) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0offset = Number(off&#91;1]) \/ 1000;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0continue;\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0const times = &#91;];\n\u00a0\u00a0\u00a0\u00a0let m;\n\u00a0\u00a0\u00a0\u00a0while ((m = rest.match(LINE_TAG))) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0times.push(toSeconds(m&#91;1], m&#91;2], m&#91;3]));\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0rest = rest.slice(m&#91;0].length);\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0for (const time of times) entries.push({ time, raw: rest.trim() });\n\u00a0\u00a0}\n\n\u00a0\u00a0\/\/ A positive &#91;offset:] makes the lyrics appear earlier.\n\u00a0\u00a0const shift = (t) => Math.max(0, t - offset);\n\u00a0\u00a0return entries\n\u00a0\u00a0\u00a0\u00a0.map(({ time, raw }) => ({\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0time: shift(time),\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0text: raw.replace(\/&lt;&#91;^>]*>\/g, '').trim(),\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0words: &#91;...raw.matchAll(WORD_TAG)].map((w) => ({\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0time: shift(toSeconds(w&#91;1], w&#91;2], w&#91;3])),\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0text: w&#91;4],\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0})),\n\u00a0\u00a0\u00a0\u00a0}))\n\u00a0\u00a0\u00a0\u00a0.sort((a, b) => a.time - b.time);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Metadata lines such as [ar:Example Artist] never match LINE_TAG, so they are skipped. Fractions are read as decimals, so .4, .40 and .400 all mean 0.4 seconds. If words is empty, the file is plain line-timed LRC and you highlight whole lines only.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_2_Find_the_current_line_quickly\"><\/span><strong>Step 2: Find the current line quickly<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On every frame you need the last line whose start time is at or before the playback position. Lines are sorted, so a binary search keeps this cheap even for long tracks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function currentIndex(lines, t) {\nlet lo = 0;\n\u00a0\u00a0let hi = lines.length - 1;\n\u00a0\u00a0let found = -1;\n\u00a0\u00a0while (lo &lt;= hi) {\n\u00a0\u00a0\u00a0\u00a0const mid = (lo + hi) >> 1;\n\u00a0\u00a0\u00a0\u00a0if (lines&#91;mid].time &lt;= t) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0found = mid;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0lo = mid + 1;\n\u00a0\u00a0\u00a0\u00a0} else {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0hi = mid - 1;\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0}\n\u00a0\u00a0return found; \/\/ -1 means \"before the first line\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same function works for words inside the active line: pass lines[i].words instead of lines.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_3_Keep_the_view_in_sync_with_the_audio\"><\/span><strong>Step 3: Keep the view in sync with the audio<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">It is tempting to listen for the timeupdate event, but browsers fire it only a few times per second. That is fine for line highlighting and visibly late for word highlighting. A requestAnimationFrame loop that runs while the audio plays gives smooth results:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const audio = document.querySelector('audio');\nlet active = -1;\n\nfunction tick() {\n\u00a0\u00a0const i = currentIndex(lines, audio.currentTime);\n\u00a0\u00a0if (i !== active) {\n\u00a0\u00a0\u00a0\u00a0active = i;\n\u00a0\u00a0\u00a0\u00a0renderLine(i); \/\/ add a class to line i and scroll it into view\n\u00a0\u00a0}\n\u00a0\u00a0if (i >= 0 &amp;&amp; lines&#91;i].words.length) {\n\u00a0\u00a0\u00a0\u00a0renderWord(currentIndex(lines&#91;i].words, audio.currentTime));\n\u00a0\u00a0}\n\u00a0\u00a0if (!audio.paused) requestAnimationFrame(tick);\n}\n\naudio.addEventListener('play', () => requestAnimationFrame(tick));\naudio.addEventListener('seeked', tick);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Only touch the DOM when the index changes, and use scrollIntoView({ block: &#8216;center&#8217;, behavior: &#8216;smooth&#8217; }) on the active line rather than calculating scroll offsets by hand.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_4_Convert_to_WebVTT_for_subtitles\"><\/span><strong>Step 4: Convert to WebVTT for subtitles<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If the same lyrics need to appear on a video, convert the parsed lines into WebVTT cues. Each cue ends where the next line begins, which also means a blank timestamped line correctly ends the previous cue:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const vttTime = (t) => {\nconst ms = Math.round(t * 1000);\n\u00a0\u00a0const hh = String(Math.floor(ms \/ 3600000)).padStart(2, '0');\n\u00a0\u00a0const mm = String(Math.floor(ms \/ 60000) % 60).padStart(2, '0');\n\u00a0\u00a0const ss = String(Math.floor(ms \/ 1000) % 60).padStart(2, '0');\n\u00a0\u00a0return `${hh}:${mm}:${ss}.${String(ms % 1000).padStart(3, '0')}`;\n};\n\nfunction toVtt(lines, tail = 4) {\n\u00a0\u00a0const cues = &#91;];\n\u00a0\u00a0lines.forEach((line, i) => {\n\u00a0\u00a0\u00a0\u00a0if (!line.text) return;\n\u00a0\u00a0\u00a0\u00a0const end = i + 1 &lt; lines.length ? lines&#91;i + 1].time : line.time + tail;\n\u00a0\u00a0\u00a0\u00a0cues.push(`${vttTime(line.time)} --> ${vttTime(end)}\\n${line.text}`);\n\u00a0\u00a0});\n\u00a0\u00a0return `WEBVTT\\n\\n${cues.join('\\n\\n')}\\n`;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Attach the result to a &lt;video&gt; with a &lt;track kind=&#8221;captions&#8221;&gt; element. On an &lt;audio&gt; element the browser will not draw the cues, but you can set the track&#8217;s mode to &#8220;hidden&#8221; and listen for cuechange events to drive your own lyrics view.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Edge_cases_worth_testing\"><\/span><strong>Edge cases worth testing<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Real-world LRC files are messy. Before shipping, make sure your parser and view handle:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>a UTF-8 byte order mark at the start of the file and \\r\\n line endings;<\/li>\n\n\n\n<li>lines with two or more timestamps, and timestamps that are not in order;<\/li>\n\n\n\n<li>[mm:ss] with no fraction, and fractions with three digits;<\/li>\n\n\n\n<li>metadata tags anywhere in the file, including [offset:] after the first lyric line;<\/li>\n\n\n\n<li>empty timestamped lines used as breaks;<\/li>\n\n\n\n<li>lines with no timestamp at all, which should be ignored rather than shown at 0:00;<\/li>\n\n\n\n<li>languages without spaces between words. In Chinese or Japanese lyrics, word tags often wrap single characters, so never assume a space separates two highlighted units.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Getting_reliable_test_files\"><\/span><strong>Getting reliable test files<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The hardest part of building a lyrics view is often the test data. You need audio you are allowed to use and lyrics that are timed accurately, ideally down to the word. Tapping along by hand is slow and imprecise, and lyric databases rarely include word-level timing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An alignment tool saves a lot of time here. <a href=\"https:\/\/www.lacuna.fm\/lrc-generator\">Lacuna&#8217;s LRC generator<\/a> takes an MP3, WAV, FLAC, M4A or OGG file (up to 10 MB and 10 minutes), aligns lyrics you paste to the vocals, or transcribes the song if you have no lyrics, and gives every word its own timestamp. You can fix any line in its editor and export standard LRC, word-by-word LRC, SRT, VTT or plain text. Because every export comes from the same timing, the files make a useful reference when you compare your own parser and converter output. You can try it with free credits; downloading the files requires a paid plan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you do not have a suitable song, create one. You can <a href=\"https:\/\/www.lacuna.fm\/lyrics-to-song\">generate a song from lyrics you write<\/a> instead of borrowing a commercial track, which also means you know every word in advance. Generating the same lyrics as a fast rap and a slow ballad is a quick way to stress-test word highlighting at very different speeds.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Wrapping_up\"><\/span><strong>Wrapping up<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A synced lyrics view needs surprisingly little code: a tolerant parser, a binary search and an animation loop. Most of the work is in handling messy input and checking the timing against real audio. Build those tests early, and the feature will hold up when users start loading files from every player and tool they have ever used.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Scrolling, highlighted lyrics used to be a feature of big streaming apps. Today it shows up in much smaller projects: an artist&#8217;s website, a karaoke mode in a party game, a language-learning app that plays songs, a lyric video tool. The browser side is not hard, but the details are easy to get wrong: odd [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2852,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_sitemap_exclude":false,"_sitemap_priority":"","_sitemap_frequency":"","footnotes":""},"categories":[2],"tags":[],"class_list":["post-2851","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/posts\/2851","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/comments?post=2851"}],"version-history":[{"count":1,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/posts\/2851\/revisions"}],"predecessor-version":[{"id":2853,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/posts\/2851\/revisions\/2853"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/media\/2852"}],"wp:attachment":[{"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/media?parent=2851"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/categories?post=2851"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/extendsclass.com\/blog\/wp-json\/wp\/v2\/tags?post=2851"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}