<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kizuki]]></title><description><![CDATA[Public data as clean rows. Pay per returned row plus Apify's run start fee; no row charge for empty runs or failed rows. Indie dev, unaffiliated with Apify.]]></description><link>https://kizukidata.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6af3543b6f6fdf3b91b95195/0744a298-7eb8-4de5-b3ae-8ca854c4f1ad.png</url><title>Kizuki</title><link>https://kizukidata.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 14 Nov 2026 19:41:06 GMT</lastBuildDate><atom:link href="https://kizukidata.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Timestamp citations from YouTube transcripts: a one-line formula, and where it stops being the problem]]></title><description><![CDATA[14 videos, 1,257 probes and 322 rewritten questions: LangChain's default chunks give a playable link about 25% of the time, and with rewritten questions…]]></description><link>https://kizukidata.hashnode.dev/timestamp-citations-from-youtube-transcripts-a-one-line-formula-and-where-it-stops-being-the-problem</link><guid isPermaLink="true">https://kizukidata.hashnode.dev/timestamp-citations-from-youtube-transcripts-a-one-line-formula-and-where-it-stops-being-the-problem</guid><category><![CDATA[RAG ]]></category><category><![CDATA[Python]]></category><category><![CDATA[llm]]></category><category><![CDATA[langchain]]></category><dc:creator><![CDATA[Kizuki]]></dc:creator><pubDate>Sat, 14 Nov 2026 10:30:28 GMT</pubDate><content:encoded><![CDATA[<p>If a RAG answer cites a YouTube video, the link should open just before the moment that answers the question, so that pressing play reaches it. When the cited second is the start of the retrieved transcript passage, there is a one-line formula for how often that happens. For LangChain's <code>YoutubeLoader</code> chunks at their default length it gives about 25%.</p>
<p>I checked the formula on 14 videos (22 hours of talks and lectures). It holds, with one exception that comes from how LangChain stamps its chunks. Then I tried questions phrased the way a viewer might type them. Most bad links came from retrieving the wrong passage, often minutes away. Keeping a start second for every caption cue inside each passage removes the timestamp cost of long passages, but comparing designs at the same number of passages handed to an LLM made long passages look better than they are, a trap I fell into before I compared them at the same amount of transcript.</p>
<h2>The formula</h2>
<p>I count a link as good when the cited second is between 30 seconds before the right moment and 1 second after it. Press play and you reach the moment within 30 seconds.</p>
<p>Say every passage carries the second where it starts, retrieval returns the passage that contains the moment, and moments are spread evenly over the speech. A passage of length L covers L seconds of moments, and only the first 30 of them are within 30 s of its start. So the share of good links is</p>
<pre><code>sum of L × min(1, 30/L) over all passages / sum of L
</code></pre>
<p>If all passages are the same length, that is min(1, 30/L). The median error is about L/2.</p>
<p><code>YoutubeLoader</code> returns one document per video with no timestamps by default. With <code>transcript_format=TranscriptFormat.CHUNKS</code> it groups caption cues into windows of <code>chunk_size_seconds</code>, 120 by default, and the formula gives 30/120 = 25% for those.</p>
<h2>Checking it on real captions</h2>
<p>Real captions are not evenly paced: cues vary in length, speakers pause, and passages cut by characters vary in duration.</p>
<ul>
<li>14 public videos with English captions: 8 talks by Andrej Karpathy (one of them his Microsoft Build talk), 4 from 3Blue1Brown, and 2 Stanford lectures (CS229 and CS224N, lecture 1 of each). 1,322 minutes in total, from 8 to 241 minutes per video. 7 have human-written captions and 7 have auto-generated ones.</li>
<li>One probe per minute of video, at a seeded random second inside that minute: 1,257 probes. The probe is a caption cue, the right answer is its start second, and the query is the text of that cue and its two neighbours.</li>
<li>All passages of all videos go into one BM25 index. The top hit is the retrieved passage and its second is the cited second. A wrong video counts as a miss.</li>
</ul>
<p>The probes are spread evenly over time by construction, so this checks the passage builders and the retriever against the formula. It does not check whether real questions are spread evenly.</p>
<p>With the cue's own words as the query, the right video comes back 98.4% to 100% of the time, so this setting tests the formula. I compared 20 ways of cutting the transcript that do not estimate the second from a character offset (single-cue passages are left out because each query also contains the next cue). For 19 of them the measured share is between 2.8 points below and 1.1 points above the formula:</p>
<table>
<thead>
<tr>
<th>Passages</th>
<th align="right">Formula</th>
<th align="right">Measured</th>
</tr>
</thead>
<tbody><tr>
<td>30 s time windows</td>
<td align="right">98.2%</td>
<td align="right">97.4%</td>
</tr>
<tr>
<td>Transcript-tool paragraphs (median 38 s)</td>
<td align="right">76.2%</td>
<td align="right">76.5%</td>
</tr>
<tr>
<td>60 s time windows</td>
<td align="right">50.1%</td>
<td align="right">50.5%</td>
</tr>
<tr>
<td>Transcript-tool 1,500-char chunks (median 84 s)</td>
<td align="right">35.1%</td>
<td align="right">35.8%</td>
</tr>
<tr>
<td>120 s time windows</td>
<td align="right">25.1%</td>
<td align="right">24.1%</td>
</tr>
<tr>
<td>LangChain 120 s chunks (default)</td>
<td align="right">25.1%</td>
<td align="right">24.7%</td>
</tr>
<tr>
<td>Cues packed to 3,000 chars (median 182 s)</td>
<td align="right">16.1%</td>
<td align="right">13.9%</td>
</tr>
<tr>
<td>LangChain 30 s chunks</td>
<td align="right">99.9%</td>
<td align="right">88.3%</td>
</tr>
</tbody></table>
<p><img src="https://cdn.hashnode.com/uploads/covers/6af3543b6f6fdf3b91b95195/72a21f80-37fa-4d97-801a-9dda429f06ae.png" alt="Left: share of good links by passage length, with the formula. Right: rewritten questions, right passage among those handed over, at the same seconds of transcript" /></p>
<p>How the passages are cut (characters, packed cues, time windows, paragraphs) makes no difference beyond their length. The median error is 13.8 s for 30-second windows, 29.4 s for 60-second windows and 56.6 s for LangChain's default. The exception is the last row.</p>
<h2>LangChain's chunk second is a clock boundary</h2>
<p>I did not call the loader. I ran a line-for-line copy of its chunking rule (<code>langchain_community</code> 0.4.2) on the same caption cues. In that version, a chunk's <code>start_seconds</code> is the window boundary (0, 30, 60, ... for 30-second chunks), not the start of its first cue. A cue that runs past a boundary goes into the next chunk, so a citation of that cue points a few seconds after the cue began. The cited second lands after the moment for 10.9% of probes with 30-second chunks, 5.6% with 60 and 3.0% with 120. Windows that take their second from the first cue land after it 1.1% of the time at 30 seconds. At 30 seconds that is enough to pull LangChain 11.6 points below the formula.</p>
<h2>With rewritten questions, most misses happen before the timestamp</h2>
<p>Reusing the transcript's words makes retrieval easy. To make it harder, I had an LLM write 322 questions the way a viewer might type them. It used one model, saw the video title and about 30 seconds of context on each side, and was told not to copy any three-word run from the target cues. I ran the questions through BM25 and a dense retriever (<code>BAAI/bge-small-en-v1.5</code>) and split every top-1 result into three cases:</p>
<table>
<thead>
<tr>
<th>Passages</th>
<th>Retriever</th>
<th align="right">Wrong video</th>
<th align="right">Right video, wrong passage</th>
<th align="right">Right passage</th>
<th align="right">Good link if right passage</th>
</tr>
</thead>
<tbody><tr>
<td>30 s windows</td>
<td>BM25</td>
<td align="right">39.4%</td>
<td align="right">39.8%</td>
<td align="right">20.8%</td>
<td align="right">100.0%</td>
</tr>
<tr>
<td>30 s windows</td>
<td>dense</td>
<td align="right">36.6%</td>
<td align="right">40.7%</td>
<td align="right">22.7%</td>
<td align="right">100.0%</td>
</tr>
<tr>
<td>Paragraphs (38 s)</td>
<td>BM25</td>
<td align="right">40.4%</td>
<td align="right">35.1%</td>
<td align="right">24.5%</td>
<td align="right">75.9%</td>
</tr>
<tr>
<td>60 s windows</td>
<td>BM25</td>
<td align="right">37.9%</td>
<td align="right">32.6%</td>
<td align="right">29.5%</td>
<td align="right">53.7%</td>
</tr>
<tr>
<td>LangChain 120 s</td>
<td>BM25</td>
<td align="right">39.4%</td>
<td align="right">29.2%</td>
<td align="right">31.4%</td>
<td align="right">19.8%</td>
</tr>
<tr>
<td>LangChain 120 s</td>
<td>dense</td>
<td align="right">43.8%</td>
<td align="right">34.8%</td>
<td align="right">21.4%</td>
<td align="right">33.3%</td>
</tr>
</tbody></table>
<p>When the right passage comes back, the formula roughly holds (for LangChain's default, 19.8% and 33.3% against 25%, on 101 and 69 questions). But it comes back for only about 21% to 31% of these questions. About 4 in 10 go to another video, and another 3 to 4 in 10 land in the right video at the wrong spot. Those wrong spots are not next door: the median distance for 30-second windows is 184.3 s with BM25 and 245.8 s with the dense retriever. Seo and Kim argue the same for video LLMs on hour-long videos (<a href="https://arxiv.org/abs/2606.12300">arXiv 2606.12300</a>): search, not recognition, is the bottleneck. For transcripts alone, Yılbırt, Litvak and Färber found on meeting recordings that timestamp accuracy depends strongly on retrieval (<a href="https://arxiv.org/abs/2606.20890">arXiv 2606.20890</a>). The numbers here add passage length and the source of the cited second to that picture.</p>
<p>At top 1, short passages still give more good links: 21.1% for 30-second windows against 6.2% for LangChain's default with BM25 (23.0% and 7.1% with the dense retriever). Among passages of 40 seconds or less the differences are inside the noise; for 30-second windows the 95% interval over videos is 15.9% to 26.0% with BM25.</p>
<h2>Where the timestamp should live</h2>
<p>Most pipelines pass several passages to an LLM. If each passage carries one start second, the best the LLM can do is cite the best start among them. With the top 5 passages that is a good link for 46.6% of questions with 30-second windows and 15.2% with LangChain's default (BM25; 42.9% and 12.1% with the dense retriever). Short passages win, as the formula says.</p>
<p>The other design stores a start second for every cue inside each passage, so the LLM can cite any line it reads. Then the ceiling is whether the passage holding the right cue was handed over at all. Five 120-second LangChain chunks contain it for 60.2% of questions with BM25, against 45.7% for five 30-second windows. That looks like a win for long passages, and I first read it that way. But five 120-second passages are 600 seconds of transcript and five 30-second windows are 150. At the same amount of transcript the gap closes or flips:</p>
<table>
<thead>
<tr>
<th>Transcript handed to the LLM</th>
<th align="right">30 s windows</th>
<th align="right">60 s windows</th>
<th align="right">120 s windows</th>
</tr>
</thead>
<tbody><tr>
<td>300 s</td>
<td align="right">50.9% / 50.9%</td>
<td align="right">51.6% / 43.2%</td>
<td align="right"></td>
</tr>
<tr>
<td>600 s</td>
<td align="right">58.7% / 61.8%</td>
<td align="right">64.0% / 55.6%</td>
<td align="right">60.9% / 46.6%</td>
</tr>
<tr>
<td>1,200 s</td>
<td align="right">65.5% / 68.6%</td>
<td align="right">70.5% / 63.7%</td>
<td align="right">69.3% / 58.4%</td>
</tr>
</tbody></table>
<p>(Right passage among those handed over, BM25 / dense.)</p>
<p>With BM25, 120-second LangChain chunks and 30-second windows are level at 600 s (+1.6 points, 95% interval −3.4 to +6.3 over videos), and 60-second windows are ahead of 30-second ones by 5.3 points (1.8 to 8.5). With the dense retriever, short passages are ahead: 30-second windows beat 120-second LangChain chunks by 13.7 points (7.9 to 20.2). So once every cue keeps its second, passage length is no longer a timestamp decision. What moved the ceiling here was how much transcript the LLM gets to read.</p>
<p>That ceiling assumes the LLM picks the right cue. I have not tested that. A simple stand-in does poorly: retrieving one 120-second window and picking its best-matching cue with BM25 gave 13.7% good links, against a ceiling of 31.1% for one window. It often picked a later cue: 23.6% landed within 30 seconds on either side.</p>
<h2>Two traps in evaluating chunkers</h2>
<p>The first is the one above: comparing designs at the same number of passages hands longer passages more transcript. Compare at the same number of seconds handed over.</p>
<p>The second came from my first version of this test, which placed every probe exactly on the minute. Time windows also start on the minute, so the probe and the cited second line up, sometimes exactly. Here the index and the rule for building queries stay the same, and only the probe times change (so the cues, and their text, differ):</p>
<table>
<thead>
<tr>
<th>Passages</th>
<th align="right">Probes on the minute</th>
<th align="right">Probes at random seconds</th>
</tr>
</thead>
<tbody><tr>
<td>LangChain 60 s</td>
<td align="right">99.0%</td>
<td align="right">50.4%</td>
</tr>
<tr>
<td>60 s windows</td>
<td align="right">76.6%</td>
<td align="right">50.5%</td>
</tr>
<tr>
<td>LangChain 120 s</td>
<td align="right">48.2%</td>
<td align="right">24.7%</td>
</tr>
<tr>
<td>120 s windows</td>
<td align="right">35.3%</td>
<td align="right">24.1%</td>
</tr>
<tr>
<td>30 s windows</td>
<td align="right">89.2%</td>
<td align="right">97.4%</td>
</tr>
<tr>
<td>Paragraphs</td>
<td align="right">75.7%</td>
<td align="right">76.5%</td>
</tr>
</tbody></table>
<p><img src="https://cdn.hashnode.com/uploads/covers/6af3543b6f6fdf3b91b95195/05627bd8-7a75-48ac-b3d8-47d6844649c7.png" alt="Same index and query rule, probe times on the minute vs random" /></p>
<p>Passages with clock-aligned boundaries moved by between -8.2 and +48.6 points, in both directions. Paragraphs did not move. If you evaluate timestamp citations, put the test moments at random times.</p>
<h2>What I would do in a pipeline</h2>
<ol>
<li>Keep every cue's start second through every step, and never estimate a second from a character offset. On the same 500-character pieces, an estimated second gave 24.7% good links and the start of the cue each piece begins in gave 89.1%. LangChain's <code>TranscriptFormat.LINES</code> gives one document per cue with its start.</li>
<li>If each passage can carry only one second, size passages from the formula: about 30 seconds for links within 30 seconds. With LangChain that is <code>chunk_size_seconds=30</code>, though its boundary seconds still cost about 11 points:</li>
</ol>
<pre><code class="language-python">from langchain_community.document_loaders import YoutubeLoader
from langchain_community.document_loaders.youtube import TranscriptFormat

loader = YoutubeLoader.from_youtube_url(
    "https://www.youtube.com/watch?v=aircAruvnKk",
    transcript_format=TranscriptFormat.CHUNKS,
    chunk_size_seconds=30,
)
docs = loader.load()  # metadata["start_seconds"] is the window boundary
</code></pre>
<ol start="3">
<li>If you can keep cue seconds inside passages, choose passage length for retrieval, not for timestamps, and compare designs at the same amount of transcript handed to the LLM. Measure how well your LLM picks the cue before relying on it.</li>
<li>Measure retrieval before tuning timestamps: how often the right passage comes back and how far away the wrong ones are. Test at random times and with questions in your users' words.</li>
</ol>
<p>The transcripts here came from my Apify Actor, <a href="https://kizukidata.com/go/youtube-transcripts/hashnode">YouTube Transcript Scraper</a>. Its paragraphs and 1,500-character chunks are the 38-second and 84-second rows above, and its <code>segments</code> field carries each cue's start second.</p>
<h2>Limits</h2>
<ul>
<li>14 videos, English only, all technical talks and lectures, 8 of them by one speaker. Probes follow video length, so the two longest videos hold 35.2% of the verbatim probes. The 322 rewritten questions take about 25 per video instead.</li>
<li>The wrong-video rate depends on how much the videos overlap in topic. It ran from 8% to 100% per video here (30-second windows, BM25; the 100% is a video with only 6 questions), and another video may also answer the question.</li>
<li>I did not check that each rewritten question has only one right moment, so some "wrong spots" may answer it equally well. 106 of the 322 questions share no content word with the target cues (median 1 word), which makes them hard for BM25. None copies a three-word run.</li>
<li>Caption type, video length and speaker overlap: all auto-generated tracks are long Karpathy videos. I would not draw conclusions about caption type from this set.</li>
<li>One LLM wrote the rewritten questions. One small embedding model, no reranker. The top-5 numbers assume the LLM picks the best passage, or the right cue.</li>
<li>The right answer is the start of one caption cue. A viewer might put "the moment" a few seconds earlier or later.</li>
</ul>
<p>The scripts, the rewritten questions and every result table are on GitHub: <a href="https://github.com/kizukidata/transcript-citation-benchmark">https://github.com/kizukidata/transcript-citation-benchmark</a></p>
<p><em>Disclosure: I maintain the YouTube Transcript Scraper Actor mentioned above. Written with help from AI. There are no affiliate links in this post.</em></p>
]]></content:encoded></item></channel></rss>