<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bloody Valentine</title>
    <description>The latest articles on DEV Community by Bloody Valentine (@bloody_valentine_51e5dacd).</description>
    <link>https://dev.to/bloody_valentine_51e5dacd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4101398%2F042985ff-40c9-4fa6-afac-0a4858af6d4e.png</url>
      <title>DEV Community: Bloody Valentine</title>
      <link>https://dev.to/bloody_valentine_51e5dacd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bloody_valentine_51e5dacd"/>
    <language>en</language>
    <item>
      <title>One Video, Five Outputs: Repurpose YouTube Into Articles, Threads, and Shorts Scripts</title>
      <dc:creator>Bloody Valentine</dc:creator>
      <pubDate>Sat, 05 Sep 2026 13:10:51 +0000</pubDate>
      <link>https://dev.to/bloody_valentine_51e5dacd/one-video-five-outputs-repurpose-youtube-into-articles-threads-and-shorts-scripts-2jmf</link>
      <guid>https://dev.to/bloody_valentine_51e5dacd/one-video-five-outputs-repurpose-youtube-into-articles-threads-and-shorts-scripts-2jmf</guid>
      <description>&lt;h2&gt;
  
  
  One Video, Five Outputs: Repurpose YouTube Into Articles, Threads, and Shorts Scripts
&lt;/h2&gt;

&lt;p&gt;One good video holds an article, a thread, three shorts, and a newsletter&lt;br&gt;
section. Creators know this. What stops most developers from automating it&lt;br&gt;
is the boring middle step: getting the transcript into a shape an LLM (and&lt;br&gt;
a video editor) can use. &lt;a href="https://dev.to/bloody_valentine_51e5dacd/how-to-get-youtube-transcripts-as-a-developer-4-methods-that-work-in-2026-526p"&gt;Part 1&lt;/a&gt; covered extraction,&lt;br&gt;
&lt;a href="https://dev.to/bloody_valentine_51e5dacd/rag-over-youtube-playlists-from-video-urls-to-cited-answers-in-60-lines-2n3d"&gt;part 2&lt;/a&gt; covered RAG. This one is the repurposing pipeline I&lt;br&gt;
actually run: transcript in, five publishable outputs out.&lt;/p&gt;

&lt;p&gt;The input is my Apify Actor&lt;br&gt;
(&lt;a href="https://apify.com/lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;lexiie/youtube-transcript-api&lt;/a&gt;),&lt;br&gt;
because repurposing needs two things plain caption scrapers do poorly:&lt;br&gt;
&lt;strong&gt;segments with timestamps&lt;/strong&gt; (to find cut points) and &lt;strong&gt;ready-made SRT&lt;/strong&gt;&lt;br&gt;
(to burn into clips). Both come back in one dataset item per video.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: fetch once, reuse everywhere
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;ACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lexiie~youtube-transcript-api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.apify.com/v2/acts/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ACTOR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/run-sync-get-dataset-items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=VIDEO_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;srt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;srt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chars,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Everything below reads from these three variables. Fetch once, even if you&lt;br&gt;
generate ten outputs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Output 1: article draft with real quotes
&lt;/h2&gt;

&lt;p&gt;The trick is structural, not clever prompting. Hand the LLM the full text&lt;br&gt;
plus an outline, and require verbatim quotes so the draft stays grounded&lt;br&gt;
instead of drifting into generic summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;

&lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Turn the transcript into an 800-word article. Keep the author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice. Include at least 3 verbatim quotes marked with segment &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamps like [12:04]. No new facts beyond the transcript.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[12:04]&lt;/code&gt; markers are checkable: each maps back to a segment, so an&lt;br&gt;
editor (or a script) can verify every quote against the source in seconds.&lt;br&gt;
On manual-caption videos the quotes are near perfect. On auto captions,&lt;br&gt;
budget one proofreading pass for homophones.&lt;/p&gt;
&lt;h2&gt;
  
  
  Output 2: X thread
&lt;/h2&gt;

&lt;p&gt;Threads want one idea per post and a hook that earns the click. Score&lt;br&gt;
segments for "standalone punch" with a dead simple heuristic, then expand&lt;br&gt;
the winners:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;# substance
&lt;/span&gt;            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# questions hook
&lt;/span&gt;            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# strong claims hook
&lt;/span&gt;            &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;        &lt;span class="c1"&gt;# skip fragments
&lt;/span&gt;
&lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed those seven moments to the LLM with "write a 7-post thread, one&lt;br&gt;
moment per post, first post under 200 characters." The timestamps ride&lt;br&gt;
along, so each post can link the exact moment with a &lt;code&gt;&amp;amp;t=&lt;/code&gt; URL. Threads&lt;br&gt;
with timestamp links outperform plain ones in my experience: skeptics&lt;br&gt;
click through, believers don't need to.&lt;/p&gt;
&lt;h2&gt;
  
  
  Output 3: shorts scripts with cut points
&lt;/h2&gt;

&lt;p&gt;A 30 to 60 second short needs a hook in the first 3 seconds, then two or&lt;br&gt;
three beats, then a payoff. That structure maps cleanly onto segments:&lt;br&gt;
the hook is your highest scoring opening segment, beats are the next&lt;br&gt;
strong moments in chronological order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# open strong
&lt;/span&gt;&lt;span class="n"&gt;beats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;          &lt;span class="c1"&gt;# back into story order
&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s of source material&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cut &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cut X -&amp;gt; Y&lt;/code&gt; lines are an edit decision list your editor can follow&lt;br&gt;
blindly, and the total tells you upfront whether you have 30 seconds of&lt;br&gt;
material or need a wider net. Prompt the LLM with these beats for the&lt;br&gt;
on-screen caption text, keeping each line under 40 characters.&lt;/p&gt;
&lt;h2&gt;
  
  
  Output 4: subtitles, done already
&lt;/h2&gt;

&lt;p&gt;This one is free: the &lt;code&gt;srt&lt;/code&gt; field from step 1 is an upload-ready SubRip&lt;br&gt;
file. Save it next to the clip and burn it in or upload it as a caption&lt;br&gt;
track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clip.srt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;srt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shorts with burned-in captions retain measurably better, and you just&lt;br&gt;
skipped the entire "transcribe the clip" step because the transcript came&lt;br&gt;
with timestamps attached. For Indonesian audiences, refetch with&lt;br&gt;
&lt;code&gt;"targetLanguage": "id"&lt;/code&gt; and you get translated SRT the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output 5: newsletter section
&lt;/h2&gt;

&lt;p&gt;Same article draft from output 1, reframed: ask the LLM for a 150-word&lt;br&gt;
section with one quote and one timestamp link, in the voice of your&lt;br&gt;
newsletter. Newsletters thrive on "watch this 40-second moment" links, and&lt;br&gt;
you now manufacture those on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks (honest notes)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Music and vlogs with thin dialogue&lt;/strong&gt; transcribe to almost nothing.
The Actor's ASR path returns these as failures instead of billing you,
so filter &lt;code&gt;status&lt;/code&gt; and move on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto captions need a proof pass&lt;/strong&gt; before publishing verbatim quotes.
Fine for threads and drafts, risky for attributed quotes in articles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long podcasts&lt;/strong&gt; (2h+) exceed a single LLM context window. Chunk by
segments (see part 2's chunking recipe) and summarize per chunk, then
summarize the summaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1 (extraction methods): &lt;a href="https://dev.to/bloody_valentine_51e5dacd/how-to-get-youtube-transcripts-as-a-developer-4-methods-that-work-in-2026-526p"&gt;https://dev.to/bloody_valentine_51e5dacd/how-to-get-youtube-transcripts-as-a-developer-4-methods-that-work-in-2026-526p&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2 (RAG with citations): &lt;a href="https://dev.to/bloody_valentine_51e5dacd/rag-over-youtube-playlists-from-video-urls-to-cited-answers-in-60-lines-2n3d"&gt;https://dev.to/bloody_valentine_51e5dacd/rag-over-youtube-playlists-from-video-urls-to-cited-answers-in-60-lines-2n3d&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Actor: &lt;a href="https://apify.com/lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;https://apify.com/lexiie/youtube-transcript-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SDKs, examples, agent skill: &lt;a href="https://github.com/Lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;https://github.com/Lexiie/youtube-transcript-api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That closes the series: extract, retrieve, repurpose. All three run&lt;br&gt;
unattended on the same transcript shape. Tell me which output earns its&lt;br&gt;
keep for you, that decides what gets automated next.&lt;/p&gt;

</description>
      <category>contentcreation</category>
      <category>ai</category>
      <category>python</category>
      <category>youtube</category>
    </item>
    <item>
      <title>RAG Over YouTube Playlists: From Video URLs to Cited Answers in 60 Lines</title>
      <dc:creator>Bloody Valentine</dc:creator>
      <pubDate>Sat, 05 Sep 2026 12:58:21 +0000</pubDate>
      <link>https://dev.to/bloody_valentine_51e5dacd/rag-over-youtube-playlists-from-video-urls-to-cited-answers-in-60-lines-2n3d</link>
      <guid>https://dev.to/bloody_valentine_51e5dacd/rag-over-youtube-playlists-from-video-urls-to-cited-answers-in-60-lines-2n3d</guid>
      <description>&lt;h1&gt;
  
  
  RAG Over YouTube Playlists: From Video URLs to Cited Answers in 60 Lines
&lt;/h1&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/bloody_valentine_51e5dacd/how-to-get-youtube-transcripts-as-a-developer-4-methods-that-work-in-2026-526p"&gt;previous post&lt;/a&gt; I covered how to extract YouTube&lt;br&gt;
transcripts four ways. This one builds the thing everyone actually wants:&lt;br&gt;
ask a question, get an answer &lt;strong&gt;cited to the exact video and timestamp&lt;/strong&gt;.&lt;br&gt;
No vector-database server, no framework. &lt;code&gt;sentence-transformers&lt;/code&gt; handles&lt;br&gt;
embeddings, numpy handles retrieval, and transcripts with timestamps do&lt;br&gt;
the heavy lifting.&lt;/p&gt;

&lt;p&gt;The full script is ~60 lines. Every step runs on your laptop except the&lt;br&gt;
transcript fetch, which goes through my Apify Actor&lt;br&gt;
(&lt;a href="https://apify.com/lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;lexiie/youtube-transcript-api&lt;/a&gt;).&lt;br&gt;
Playlist in, timestamped segments out.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 0: install
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests sentence-transformers numpy openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;APIFY_TOKEN&lt;/code&gt; from Apify Console → Settings → Integrations. You only need&lt;br&gt;
an OpenAI key for the final answer step. Retrieval works without it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: fetch transcripts (one item per video)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;ACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lexiie~youtube-transcript-api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.apify.com/v2/acts/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ACTOR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/run-sync-get-dataset-items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/playlist?list=PL...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; videos transcribed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note the filter: private videos, removed uploads, and caption-less videos&lt;br&gt;
(without the ASR fallback) come back as &lt;code&gt;status: failed&lt;/code&gt; with a structured&lt;br&gt;
&lt;code&gt;error.code&lt;/code&gt;. They're free, and they never kill the run. For caption-less videos&lt;br&gt;
worth transcribing, add &lt;code&gt;"enableAsrFallback": True&lt;/code&gt; (GPU transcription,&lt;br&gt;
flat ~$0.10/video, takes minutes, so bump the timeout to 900s).&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: chunk by segments, keep timestamps
&lt;/h2&gt;

&lt;p&gt;Caption segments are natural chunk boundaries. Each already carries&lt;br&gt;
&lt;code&gt;start&lt;/code&gt;/&lt;code&gt;duration&lt;/code&gt;. Group a few per chunk so embeddings have context, and&lt;br&gt;
carry the video ID plus offsets for citations later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chunk_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per_chunk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;segs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;videoId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;videoId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;videoId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;segs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;per_chunk&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;per_chunk&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;chunk_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; chunks from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; videos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 10-video playlist typically yields a few hundred chunks. Small enough&lt;br&gt;
to hold in memory, big enough to need retrieval.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: embed once, retrieve with numpy
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 80MB, runs on CPU
&lt;/span&gt;&lt;span class="n"&gt;embs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;normalize_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;normalize_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embs&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No server, no index to maintain. This holds up to tens of thousands of&lt;br&gt;
chunks. Past that, reach for FAISS or a hosted vector DB, but most&lt;br&gt;
playlist corpora never get there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: answer with citations
&lt;/h2&gt;

&lt;p&gt;Stuff the retrieved chunks into an LLM prompt with their source attached,&lt;br&gt;
and require citations in the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;videoId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; @ &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer from the context only. Cite every claim as &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[videoId @ seconds]. Say when the context is insufficient.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Q: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;

&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What does the author recommend for rate limiting?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The citation format doubles as a deep link. Render it for users as&lt;br&gt;
&lt;code&gt;https://www.youtube.com/watch?v={videoId}&amp;amp;t={seconds}s&lt;/code&gt; and they land on&lt;br&gt;
the exact moment. That single trick is what separates "AI answered" from&lt;br&gt;
"AI answered and I can verify it in one click."&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it fresh
&lt;/h2&gt;

&lt;p&gt;Playlists grow. Re-run the fetch weekly and skip videos you already have&lt;br&gt;
(match on &lt;code&gt;videoId&lt;/code&gt; against your stored chunks). You only pay per&lt;br&gt;
&lt;strong&gt;successful&lt;/strong&gt; transcript (~$0.003 captions), so a weekly sync of a&lt;br&gt;
slow-moving playlist costs cents. Failures are free, so new private or&lt;br&gt;
removed videos in the playlist don't cost you anything either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd add next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate intros/outros&lt;/strong&gt;: channels repeat the same 30s intro; hash
first-chunk texts per channel and drop near-duplicates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rerank&lt;/strong&gt;: a cross-encoder over the top-20 before the top-5 improves
answers noticeably on dense topics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-playlist&lt;/strong&gt;: tag chunks with &lt;code&gt;playlistId&lt;/code&gt; (the Actor returns it)
and filter retrieval per collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Actor: &lt;a href="https://apify.com/lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;https://apify.com/lexiie/youtube-transcript-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SDKs, examples, agent skill: &lt;a href="https://github.com/Lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;https://github.com/Lexiie/youtube-transcript-api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in the series: repurposing video into articles and shorts scripts with&lt;br&gt;
the SRT output. Tell me what corpus you're pointing this at. Curious what&lt;br&gt;
breaks at your scale.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>youtube</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Get YouTube Transcripts as a Developer (4 Methods That Work in 2026)</title>
      <dc:creator>Bloody Valentine</dc:creator>
      <pubDate>Sat, 05 Sep 2026 12:37:12 +0000</pubDate>
      <link>https://dev.to/bloody_valentine_51e5dacd/how-to-get-youtube-transcripts-as-a-developer-4-methods-that-work-in-2026-526p</link>
      <guid>https://dev.to/bloody_valentine_51e5dacd/how-to-get-youtube-transcripts-as-a-developer-4-methods-that-work-in-2026-526p</guid>
      <description>&lt;h2&gt;
  
  
  How to Get YouTube Transcripts as a Developer (4 Methods That Work in 2026)
&lt;/h2&gt;

&lt;p&gt;YouTube transcripts unlock a lot: AI video summarizers, searchable course&lt;br&gt;
databases, RAG over video libraries, dataset generation for fine-tuning,&lt;br&gt;
repurposing videos into articles. But getting transcripts programmatically&lt;br&gt;
is full of sharp edges: disabled captions, rate limits, datacenter IP&lt;br&gt;
blocks, and YouTube's ever-changing frontend.&lt;/p&gt;

&lt;p&gt;This guide walks through every practical method with working code. Method 4&lt;br&gt;
is the managed service I run. Skip ahead if you just want the API call.&lt;br&gt;
The DIY methods below are real and will serve you well for small jobs.&lt;/p&gt;
&lt;h2&gt;
  
  
  What you're actually fetching
&lt;/h2&gt;

&lt;p&gt;YouTube stores captions as timed tracks in two flavors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual captions&lt;/strong&gt;: uploaded by creators, best accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-generated captions&lt;/strong&gt;: YouTube's speech recognition, most videos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each track is text plus timing (&lt;code&gt;text/start/duration&lt;/code&gt;), servable as SRT,&lt;br&gt;
VTT, or YouTube's timedtext XML. Everything below ultimately resolves to&lt;br&gt;
that shape.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 1: youtube-transcript-api (Python)
&lt;/h2&gt;

&lt;p&gt;The standard open-source library. Start here for scripts and prototypes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;youtube-transcript-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;youtube_transcript_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;

&lt;span class="n"&gt;video_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dQw4w9WgXcQ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# the ID from the watch URL
&lt;/span&gt;
&lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns a list of dicts, one &lt;code&gt;{'text', 'start', 'duration'}&lt;/code&gt; per segment.&lt;br&gt;
For other languages, list what's available first, then fetch or translate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_transcripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_generated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_transcript&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# free, server-side
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handle the caption-less case explicitly instead of catching bare&lt;br&gt;
&lt;code&gt;Exception&lt;/code&gt;. You'll need these codes downstream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;youtube_transcript_api._errors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TranscriptsDisabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NoTranscriptFound&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;TranscriptsDisabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;captions disabled: needs audio transcription (Method 3)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;NoTranscriptFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no track in the requested languages, try others first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And format for output with the built-in formatters rather than&lt;br&gt;
hand-rolling SRT timestamps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;youtube_transcript_api.formatters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextFormatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SRTFormatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WebVTTFormatter&lt;/span&gt;

&lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextFormatter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;format_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;srt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SRTFormatter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;format_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;vtt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebVTTFormatter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;format_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Method 2: yt-dlp subtitles (CLI)
&lt;/h2&gt;

&lt;p&gt;If you live in the terminal, &lt;code&gt;yt-dlp&lt;/code&gt; pulls subtitle files without a line&lt;br&gt;
of Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# auto-generated subs, no media download&lt;/span&gt;
yt-dlp &lt;span class="nt"&gt;--write-auto-sub&lt;/span&gt; &lt;span class="nt"&gt;--skip-download&lt;/span&gt; &lt;span class="s2"&gt;"https://youtube.com/watch?v=VIDEO_ID"&lt;/span&gt;

&lt;span class="c"&gt;# manual subs as SRT&lt;/span&gt;
yt-dlp &lt;span class="nt"&gt;--write-sub&lt;/span&gt; &lt;span class="nt"&gt;--sub-format&lt;/span&gt; srt &lt;span class="nt"&gt;--skip-download&lt;/span&gt; &lt;span class="s2"&gt;"https://youtube.com/watch?v=VIDEO_ID"&lt;/span&gt;

&lt;span class="c"&gt;# see what's on offer first&lt;/span&gt;
yt-dlp &lt;span class="nt"&gt;--list-subs&lt;/span&gt; &lt;span class="s2"&gt;"https://youtube.com/watch?v=VIDEO_ID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great for one-off downloads. Weak for pipelines: you parse files off disk,&lt;br&gt;
and there's no fallback when neither track exists.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: Whisper fallback for caption-less videos
&lt;/h2&gt;

&lt;p&gt;When captions don't exist, someone has to listen to the audio. The DIY&lt;br&gt;
pipeline is download-then-transcribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;yt-dlp openai-whisper torch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yt_dlp&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;yt_dlp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;YoutubeDL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bestaudio/best&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quiet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ydl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ydl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=VIDEO_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requested_downloads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filepath&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# or --extract-audio to wav
&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# base = fast/rough, small+ = better
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word_timestamps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honest costs of this path: a GPU (or very patient CPU), model downloads,&lt;br&gt;
audio storage, and minutes of compute per video. Fine occasionally,&lt;br&gt;
painful at 100 videos/day. This is exactly the step a managed service&lt;br&gt;
should absorb for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problems everyone hits (with fixes)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Rate limits and IP blocks.&lt;/strong&gt; A fresh datacenter IP (AWS/GCP/most VPS)&lt;br&gt;
works for a while, then YouTube starts returning 429s and bot-check pages.&lt;br&gt;
I measured a block after ~100–200 requests in a few hours from a cloud IP.&lt;br&gt;
Fix: add delays between requests for small jobs; for bulk workloads, route&lt;br&gt;
through residential proxies (real ISP IPs), not more cloud boxes. I run my&lt;br&gt;
own bulk jobs on &lt;a href="https://www.webshare.io/?referral_code=uvuuj26bwmna" rel="noopener noreferrer"&gt;WebShare residential proxies&lt;/a&gt;.&lt;br&gt;
They're cheap per-GB and work out of the box (signing up through that link&lt;br&gt;
supports this project at no extra cost to you).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;vid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;video_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;transcripts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;transcripts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# record it, don't crash the batch
&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. One bad video kills the batch.&lt;/strong&gt; Private, removed, or caption-less&lt;br&gt;
videos raise, so isolate per-video errors (as above) and keep going.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Dirty text.&lt;/strong&gt; Auto captions come with &lt;code&gt;[Music]&lt;/code&gt;/&lt;code&gt;[Applause]&lt;/code&gt; artifacts&lt;br&gt;
and shaky casing. Minimal cleanup before embedding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\[.*?\]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\s+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-world example: RAG over a playlist
&lt;/h2&gt;

&lt;p&gt;This is where DIY glues everything together: fetch every video, chunk by&lt;br&gt;
segment timestamps so citations link back to the exact moment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=VID1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=VID2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;vid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YouTubeTranscriptApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;  &lt;span class="c1"&gt;# failed video ≠ failed pipeline
&lt;/span&gt;    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;videoId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
               &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# chunks → embed → vector DB. Re-run weekly; new videos append.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, until a playlist is half caption-less videos, or your cloud IP&lt;br&gt;
gets blocked mid-run, or you need it to run unattended every night.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 4: the managed shortcut (my Actor)
&lt;/h2&gt;

&lt;p&gt;I packaged the whole guide above into one API call: caption fetching&lt;br&gt;
with language fallback, free server-side translation, playlist expansion,&lt;br&gt;
per-video error isolation, plus a GPU Whisper fallback for caption-less&lt;br&gt;
videos:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://apify.com/lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;lexiie/youtube-transcript-api&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/lexiie~youtube-transcript-api/run-sync-get-dataset-items?token=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], "language": "en"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One dataset item per video, same 4-format shape from both engines&lt;br&gt;
(&lt;code&gt;transcriptType&lt;/code&gt;: &lt;code&gt;MANUAL&lt;/code&gt; | &lt;code&gt;AUTO_GENERATED&lt;/code&gt; | &lt;code&gt;ASR_GENERATED&lt;/code&gt;), with&lt;br&gt;
&lt;code&gt;playlistId&lt;/code&gt;/&lt;code&gt;videoIndex&lt;/code&gt; on playlist runs and a structured &lt;code&gt;error.code&lt;/code&gt;&lt;br&gt;
on failures. Thin clients so you never touch HTTP:&lt;br&gt;
&lt;a href="https://github.com/Lexiie/youtube-transcript-api/tree/master/sdk/python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;&lt;br&gt;
(&lt;code&gt;pip install requests&lt;/code&gt; only) and&lt;br&gt;
&lt;a href="https://github.com/Lexiie/youtube-transcript-api/tree/master/sdk/node" rel="noopener noreferrer"&gt;Node&lt;/a&gt;&lt;br&gt;
(native &lt;code&gt;fetch&lt;/code&gt; only), plus an &lt;a href="https://github.com/Lexiie/youtube-transcript-api/tree/master/skill" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt;&lt;br&gt;
and copy-paste &lt;a href="https://github.com/Lexiie/youtube-transcript-api/tree/master/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt;&lt;br&gt;
for playlists, translation, ASR fallback, and partial failure.&lt;/p&gt;

&lt;p&gt;Pricing is pay-per-event, failures free: caption transcripts ~$0.003 each&lt;br&gt;
(tiered down to ~$0.0015), AI fallback a flat ~$0.10 per video regardless&lt;br&gt;
of duration. Per-minute STT APIs bill $0.12–$4.00 per audio hour by comparison.&lt;br&gt;
(Exact rates on the Store Pricing tab.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Build vs. buy, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DIY (Methods 1–3)&lt;/strong&gt; if you're learning, processing a handful of videos a&lt;br&gt;
month, or have requirements no API meets. &lt;strong&gt;The Actor&lt;/strong&gt; if you need&lt;br&gt;
unattended reliability, caption-less videos transcribed instead of&lt;br&gt;
erroring, or playlist-scale runs without babysitting proxies and GPUs.&lt;/p&gt;

&lt;p&gt;Either way, transcripts turn YouTube from a watch-only archive into&lt;br&gt;
queryable data. Happy building, and if you ship something on it (RAG,&lt;br&gt;
summarizer, research corpus), tell me what broke. That's how the roadmap&lt;br&gt;
gets written.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actor: &lt;a href="https://apify.com/lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;https://apify.com/lexiie/youtube-transcript-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code, SDKs, skill, examples: &lt;a href="https://github.com/Lexiie/youtube-transcript-api" rel="noopener noreferrer"&gt;https://github.com/Lexiie/youtube-transcript-api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>youtube</category>
      <category>api</category>
      <category>rag</category>
      <category>python</category>
    </item>
    <item>
      <title>How I Turned My Own Project Into an Apify Actor</title>
      <dc:creator>Bloody Valentine</dc:creator>
      <pubDate>Sun, 30 Aug 2026 12:49:07 +0000</pubDate>
      <link>https://dev.to/bloody_valentine_51e5dacd/how-i-turned-my-own-project-into-an-apify-actor-328m</link>
      <guid>https://dev.to/bloody_valentine_51e5dacd/how-i-turned-my-own-project-into-an-apify-actor-328m</guid>
      <description>&lt;p&gt;&lt;strong&gt;I started building &lt;a href="https://instadl.ink" rel="noopener noreferrer"&gt;https://instadl.ink&lt;/a&gt; — a fast, no-storage media extractor for Instagram &amp;amp; TikTok. Paste a public URL → get direct CDN links. No permanent video storage, just JSON. When I wanted to monetize the API on Apify, every All-in-One downloader there was a yt-dlp wrapper. So I expanded instadl.ink into an 8-platform Enriched Extractor: direct CDN + views/likes for YouTube, TikTok, Instagram, X, Facebook, Reddit, Pinterest, Vimeo — batch 100, 512 MB, $3/1K. No yt-dlp. No ffmpeg. No 4GB RAM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;qbitlabs&lt;/code&gt; charges &lt;code&gt;$0.05&lt;/code&gt; per &lt;code&gt;HD&lt;/code&gt; video (&lt;code&gt;$50/1K&lt;/code&gt;) for &lt;code&gt;1000+&lt;/code&gt; sites. &lt;code&gt;easyapi&lt;/code&gt; does &lt;code&gt;1 URL/run&lt;/code&gt; at &lt;code&gt;$2.99/1K&lt;/code&gt; + usage. &lt;code&gt;rover&lt;/code&gt; bundles &lt;code&gt;Cobalt&lt;/code&gt; + &lt;code&gt;ffmpeg&lt;/code&gt; for &lt;code&gt;21&lt;/code&gt; platforms but needs &lt;code&gt;4GB&lt;/code&gt; and &lt;code&gt;50&lt;/code&gt; concurrent. We needed the opposite: &lt;strong&gt;one run, 100 URLs, enriched stats, no bytes stored.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live on Apify:&lt;/strong&gt; &lt;code&gt;lexiie/all-in-one-media-extractor&lt;/code&gt; (expanded from &lt;strong&gt;instadl.ink&lt;/strong&gt;) — &lt;code&gt;Build 0.0.11&lt;/code&gt; &lt;code&gt;PPE+Usage $3.00/1,000&lt;/code&gt; &lt;code&gt;failed = free&lt;/code&gt; — &lt;code&gt;YT dQw4w9WgXcQ&lt;/code&gt; &lt;code&gt;view 1.8B&lt;/code&gt; in &lt;code&gt;366ms&lt;/code&gt;, &lt;code&gt;IG reel Dcm8usVMMcG&lt;/code&gt; &lt;code&gt;like 0 comment 397&lt;/code&gt; without proxy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bill that pushed us off yt-dlp
&lt;/h2&gt;

&lt;p&gt;We started like everyone else: wrapping &lt;code&gt;instadl&lt;/code&gt; (IG + TikTok) with &lt;code&gt;yt-dlp&lt;/code&gt; for YouTube. Local test was fine. On Apify it wasn’t:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Build &lt;code&gt;616MB&lt;/code&gt; vs &lt;code&gt;83MB&lt;/code&gt;:&lt;/strong&gt; &lt;code&gt;yt-dlp&lt;/code&gt; + &lt;code&gt;ffmpeg&lt;/code&gt; base is &lt;code&gt;83MB&lt;/code&gt;, but &lt;code&gt;4K&lt;/code&gt; merge needs &lt;code&gt;ffmpeg&lt;/code&gt; + &lt;code&gt;4GB RAM&lt;/code&gt; for &lt;code&gt;60s&lt;/code&gt; video. Our &lt;code&gt;Node&lt;/code&gt; + &lt;code&gt;undici&lt;/code&gt; &lt;code&gt;Build 0.0.11&lt;/code&gt; is &lt;code&gt;616MB&lt;/code&gt; &lt;em&gt;including&lt;/em&gt; &lt;code&gt;sharp&lt;/code&gt; for logo — &lt;code&gt;yt-dlp&lt;/code&gt; version was &lt;code&gt;~900MB&lt;/code&gt; before we stripped it.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The math that kills &lt;code&gt;PPE Only $3&lt;/code&gt;:&lt;/strong&gt; &lt;code&gt;1K&lt;/code&gt; videos × &lt;code&gt;50MB&lt;/code&gt; avg = &lt;code&gt;50GB&lt;/code&gt; &lt;code&gt;external transfer $0.2/GB = $10&lt;/code&gt; + &lt;code&gt;KVS&lt;/code&gt; &lt;code&gt;50GB&lt;/code&gt; store &lt;code&gt;$0.05/1K writes&lt;/code&gt; + &lt;code&gt;20 CU&lt;/code&gt; &lt;code&gt;ffmpeg&lt;/code&gt; &lt;code&gt;$4&lt;/code&gt; → &lt;code&gt;cost $14&lt;/code&gt; → &lt;code&gt;profit = 0.8×$3 - $14 = -$12&lt;/code&gt;. That’s why &lt;code&gt;qbitlabs&lt;/code&gt; &lt;em&gt;must&lt;/em&gt; charge &lt;code&gt;+$0.03/GB&lt;/code&gt; after &lt;code&gt;1GB&lt;/code&gt; and &lt;code&gt;khadinakbar&lt;/code&gt; caps at &lt;code&gt;250MB&lt;/code&gt;. We didn’t want to charge for bytes we don’t need.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The workaround:&lt;/strong&gt; Don’t download. Return &lt;strong&gt;temporary CDN&lt;/strong&gt; (&lt;code&gt;googlevideo expire ~4h&lt;/code&gt;, &lt;code&gt;scontent 86.400s&lt;/code&gt;, &lt;code&gt;fbcdn&lt;/code&gt;, &lt;code&gt;tiktokcdn&lt;/code&gt;) and let &lt;code&gt;browser → CDN&lt;/code&gt; or &lt;code&gt;agent → CDN&lt;/code&gt; fetch when needed. For &lt;code&gt;RAG&lt;/code&gt;/&lt;code&gt;UGC&lt;/code&gt; you need the &lt;code&gt;url&lt;/code&gt; + &lt;code&gt;stats&lt;/code&gt;, not the &lt;code&gt;mp4&lt;/code&gt; in &lt;code&gt;KVS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That decision forced us HTTP-only. No &lt;code&gt;yt-dlp&lt;/code&gt; binary, no &lt;code&gt;Cobalt&lt;/code&gt; server, no &lt;code&gt;ffmpeg&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “enriched extractor” actually means (vs URL-only)
&lt;/h2&gt;

&lt;p&gt;URL-only (&lt;code&gt;burbn&lt;/code&gt;/&lt;code&gt;easyapi&lt;/code&gt;) gives you &lt;code&gt;https://...mp4&lt;/code&gt; and stops. For &lt;code&gt;RAG&lt;/code&gt; you then need 3 more scrapers for &lt;code&gt;views&lt;/code&gt;/&lt;code&gt;likes&lt;/code&gt; to rank, and for &lt;code&gt;UGC&lt;/code&gt; you need &lt;code&gt;follower&lt;/code&gt; counts to filter influencers.&lt;/p&gt;

&lt;p&gt;We return &lt;strong&gt;one item with 3 blocks&lt;/strong&gt; — &lt;code&gt;metadata&lt;/code&gt; / &lt;code&gt;stats&lt;/code&gt; / &lt;code&gt;media&lt;/code&gt; (nested, pruned if &lt;code&gt;undefined&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tiktok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Calm dog..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"uploader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kings.deals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"thumbnail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"viewCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;860465&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"likeCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5047&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"commentCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"shareCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;167&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"followers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://v16m.tiktokcdn-us.com/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video/mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same shape for all 8:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;YouTube&lt;/strong&gt; &lt;code&gt;dQw4w9WgXcQ&lt;/code&gt; → &lt;code&gt;8&lt;/code&gt; qualities &lt;code&gt;2160p-720p&lt;/code&gt; &lt;code&gt;366ms&lt;/code&gt; → &lt;code&gt;view 1.8B&lt;/code&gt; &lt;code&gt;like 16M&lt;/code&gt; &lt;code&gt;tags&lt;/code&gt; &lt;code&gt;channelId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TikTok&lt;/strong&gt; &lt;code&gt;@moh.shofwan 7645528079783267605&lt;/code&gt; → &lt;code&gt;view 860K like 5K comment 23 share 167 followers 0&lt;/code&gt; + &lt;code&gt;mp4 HD/SD&lt;/code&gt; + &lt;code&gt;mp3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Instagram&lt;/strong&gt; &lt;code&gt;Dcm8usVMMcG&lt;/code&gt; → &lt;code&gt;like 0 comment 397 view 4&lt;/code&gt; + &lt;code&gt;caption&lt;/code&gt; &lt;code&gt;payal_rajput_tv...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;X&lt;/strong&gt; &lt;code&gt;SpaceX/status/184...&lt;/code&gt; → &lt;code&gt;view/like/retweet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Facebook&lt;/strong&gt; &lt;code&gt;watch?v=101532...&lt;/code&gt; → &lt;code&gt;HD/SD mp4&lt;/code&gt; &lt;code&gt;How to share...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vimeo&lt;/strong&gt; &lt;code&gt;22439234&lt;/code&gt; &lt;code&gt;The Mountain&lt;/code&gt; → &lt;code&gt;progressive mp4&lt;/code&gt; or &lt;code&gt;dash&lt;/code&gt; + &lt;code&gt;plays/likes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Reddit&lt;/strong&gt; &lt;code&gt;r/pics&lt;/code&gt; → &lt;code&gt;fallback_url&lt;/code&gt; + &lt;code&gt;ups 450&lt;/code&gt; &lt;code&gt;num_comments 23&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Pinterest&lt;/strong&gt; &lt;code&gt;pin/112426...&lt;/code&gt; → &lt;code&gt;orig&lt;/code&gt; image + &lt;code&gt;repin&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No second call. &lt;code&gt;Dataset&lt;/code&gt; &lt;code&gt;views/overview&lt;/code&gt; shows &lt;code&gt;inputUrl, platform, type, webpageUrl&lt;/code&gt; — &lt;code&gt;fields&lt;/code&gt; &lt;code&gt;metadata&lt;/code&gt;/&lt;code&gt;stats&lt;/code&gt;/&lt;code&gt;media&lt;/code&gt; nested.&lt;/p&gt;

&lt;h2&gt;
  
  
  From instadl.ink to 8 platforms — what broke
&lt;/h2&gt;

&lt;p&gt;I started with &lt;strong&gt;instadl.ink&lt;/strong&gt; — my own fast, no-storage extractor for Instagram &amp;amp; TikTok. To make it monetizable on Apify, I expanded it to 8 platforms. Each new one broke at least once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;YouTube&lt;/strong&gt; — lightweight &lt;code&gt;innertube&lt;/code&gt; &lt;code&gt;POST /youtubei/v1/player&lt;/code&gt; returns &lt;code&gt;streamingData&lt;/code&gt; &lt;strong&gt;without&lt;/strong&gt; &lt;code&gt;signatureCipher&lt;/code&gt; (no decipher). &lt;code&gt;WEB&lt;/code&gt; would need &lt;code&gt;poToken&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TikTok&lt;/strong&gt; — returns &lt;code&gt;gzip&lt;/code&gt; &lt;strong&gt;without&lt;/strong&gt; &lt;code&gt;content-encoding&lt;/code&gt; header, so manual decompression + retry with new device.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Instagram&lt;/strong&gt; — &lt;code&gt;embed&lt;/code&gt; &lt;code&gt;likeCount&lt;/code&gt; regex failed because HTML escaped → fix to permissive &lt;code&gt;edge_liked_by&lt;/code&gt; pattern. &lt;code&gt;view 4&lt;/code&gt; is real from embed (&lt;code&gt;null&lt;/code&gt; for many reels is normal).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;X&lt;/strong&gt; — &lt;code&gt;syndication&lt;/code&gt; token. &lt;code&gt;404&lt;/code&gt; for &lt;code&gt;Nasa 174...&lt;/code&gt; because ID doesn’t exist, not code.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Facebook&lt;/strong&gt; — &lt;code&gt;login&lt;/code&gt; wall &lt;code&gt;not logged in&lt;/code&gt; → &lt;code&gt;PRIVATE_CONTENT&lt;/code&gt; free.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vimeo&lt;/strong&gt; — &lt;code&gt;player.vimeo.com&lt;/code&gt; cert hijack in local ISP → &lt;code&gt;ProxyAgent&lt;/code&gt; → &lt;code&gt;origin&lt;/code&gt; ✅, but &lt;code&gt;progressive&lt;/code&gt; empty for some (dash only) → fallback &lt;code&gt;oembed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Batch&lt;/strong&gt; — &lt;code&gt;100&lt;/code&gt; vs &lt;code&gt;1&lt;/code&gt; (&lt;code&gt;easyapi&lt;/code&gt;/&lt;code&gt;qbitlabs&lt;/code&gt; &lt;code&gt;1 URL/run&lt;/code&gt;) — &lt;code&gt;5 concurrent&lt;/code&gt; &lt;code&gt;300ms&lt;/code&gt; cache &lt;code&gt;5m&lt;/code&gt; → &lt;code&gt;100 URLs ≈ 30s&lt;/code&gt; vs &lt;code&gt;100 runs&lt;/code&gt; &lt;code&gt;100×&lt;/code&gt; overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing that doesn’t punish batch
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PPE+Usage ON&lt;/code&gt; → &lt;code&gt;video-extracted $0.003&lt;/code&gt; (&lt;code&gt;$3/1K&lt;/code&gt;) event covers &lt;code&gt;extractor&lt;/code&gt;, &lt;code&gt;CU&lt;/code&gt;/&lt;code&gt;proxy&lt;/code&gt;/&lt;code&gt;transfer&lt;/code&gt; billed separate to user. &lt;code&gt;failed/private = free&lt;/code&gt; (so &lt;code&gt;IG&lt;/code&gt; &lt;code&gt;PRIVATE_CONTENT&lt;/code&gt; doesn’t cost). &lt;code&gt;PPE Only&lt;/code&gt; would be &lt;code&gt;0.8*3 - costs&lt;/code&gt; and we’d be &lt;code&gt;-$12&lt;/code&gt; for &lt;code&gt;bytes&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;10 URLs&lt;/code&gt; → &lt;code&gt;Max $0.03&lt;/code&gt; (set &lt;code&gt;Minimum $0.01&lt;/code&gt; in Store &lt;code&gt;Monetization → Additional options&lt;/code&gt; so user can’t &lt;code&gt;Max $0.001&lt;/code&gt; free ride)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;100 URLs&lt;/code&gt; &lt;code&gt;720p&lt;/code&gt; &lt;code&gt;best&lt;/code&gt; → &lt;code&gt;Max $0.30&lt;/code&gt; + &lt;code&gt;~$1&lt;/code&gt; compute (&lt;code&gt;5 CU&lt;/code&gt;) + &lt;code&gt;0.1GB&lt;/code&gt; &lt;code&gt;proxy $0.80&lt;/code&gt; (only if &lt;code&gt;RESIDENTIAL&lt;/code&gt;) → &lt;code&gt;$1.30&lt;/code&gt; vs &lt;code&gt;qbitlabs $5&lt;/code&gt; for &lt;code&gt;HD&lt;/code&gt; (&lt;code&gt;$0.05&lt;/code&gt; per &lt;code&gt;HD&lt;/code&gt; = &lt;code&gt;$50/1K&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Discounts: BRONZE $0.003 → SILVER $0.0025 → GOLD $0.0015&lt;/code&gt; — same &lt;code&gt;easyapi&lt;/code&gt; tier, but we &lt;code&gt;batch 100&lt;/code&gt; so &lt;code&gt;GOLD&lt;/code&gt; &lt;code&gt;100 URLs&lt;/code&gt; &lt;code&gt;$0.15&lt;/code&gt; cheaper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Store&lt;/code&gt; now single event &lt;code&gt;video-extracted&lt;/code&gt; &lt;code&gt;PPE+Usage&lt;/code&gt; &lt;code&gt;$3/1K&lt;/code&gt;. If you want &lt;code&gt;2 events&lt;/code&gt; &lt;code&gt;basic $1.50&lt;/code&gt; / &lt;code&gt;enriched $3&lt;/code&gt; later, just add &lt;code&gt;enriched&lt;/code&gt; flag back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we intentionally don’t do (and why that’s a feature)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;No private bypass&lt;/strong&gt; — &lt;code&gt;PRIVATE_CONTENT&lt;/code&gt; &lt;code&gt;free&lt;/code&gt;, pass &lt;code&gt;cookies&lt;/code&gt; only if &lt;strong&gt;you own&lt;/strong&gt; the content. &lt;code&gt;InstagramPrivSniffer&lt;/code&gt; (collab intended) and &lt;code&gt;jatin-dot-py&lt;/code&gt; (&lt;code&gt;polaris_timeline_connection&lt;/code&gt; &lt;code&gt;28%&lt;/code&gt; bypass, patched &lt;code&gt;16 Oct 2025&lt;/code&gt;) are &lt;code&gt;ToS&lt;/code&gt; violations on &lt;code&gt;Apify&lt;/code&gt; — Store would &lt;code&gt;takedown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No bytes&lt;/strong&gt; — &lt;code&gt;maxFileSizeMB&lt;/code&gt; just skips &lt;code&gt;&amp;gt;250MB&lt;/code&gt; for agents; we don’t merge &lt;code&gt;HLS&lt;/code&gt;. If you need &lt;code&gt;bytes&lt;/code&gt;, use downloader with &lt;code&gt;R2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No 1000+ sites&lt;/strong&gt; — &lt;code&gt;qbitlabs&lt;/code&gt; &lt;code&gt;1000+&lt;/code&gt; is &lt;code&gt;yt-dlp&lt;/code&gt; list, we do &lt;code&gt;8&lt;/code&gt; core social + &lt;code&gt;enriched&lt;/code&gt; &lt;code&gt;batch 100&lt;/code&gt; without &lt;code&gt;yt-dlp&lt;/code&gt;. For &lt;code&gt;SoundCloud&lt;/code&gt;/&lt;code&gt;Twitch&lt;/code&gt; we’ll add HTTP-only next, not &lt;code&gt;yt-dlp&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try the exact URLs we tested (no proxy needed for 3)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/lexiie~all-in-one-media-extractor/runs?token=TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"urls":["https://www.youtube.com/watch?v=dQw4w9WgXcQ","https://www.tiktok.com/@moh.shofwan/video/7645528079783267605","https://www.instagram.com/reel/Dcm8usVMMcG/"],"quality":"720p","audioOnly":false}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Dataset&lt;/code&gt; &lt;code&gt;0.0.11&lt;/code&gt; &lt;code&gt;0R4ECgZIOkkdbjVUb&lt;/code&gt; &lt;code&gt;SUCCEEDED&lt;/code&gt; &lt;code&gt;512 MB&lt;/code&gt; &lt;code&gt;apify/actor-node:20&lt;/code&gt; returns &lt;code&gt;metadata&lt;/code&gt;/&lt;code&gt;stats&lt;/code&gt;/&lt;code&gt;media&lt;/code&gt; nested — &lt;code&gt;overview&lt;/code&gt; view shows &lt;code&gt;inputUrl, platform, type, webpageUrl&lt;/code&gt; — &lt;code&gt;latencyMs&lt;/code&gt; &lt;code&gt;366ms&lt;/code&gt; &lt;code&gt;YT&lt;/code&gt;, &lt;code&gt;852ms&lt;/code&gt; &lt;code&gt;IG&lt;/code&gt;, &lt;code&gt;2838ms&lt;/code&gt; &lt;code&gt;TT&lt;/code&gt; (&lt;code&gt;3 retries&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Storage&lt;/code&gt; &lt;code&gt;inputUrl&lt;/code&gt; &lt;code&gt;success&lt;/code&gt; &lt;code&gt;platform&lt;/code&gt; &lt;code&gt;webpageUrl&lt;/code&gt; &lt;code&gt;metadata.title&lt;/code&gt; &lt;code&gt;stats.viewCount&lt;/code&gt; &lt;code&gt;media[0].url&lt;/code&gt; &lt;code&gt;latencyMs&lt;/code&gt; — &lt;code&gt;flat&lt;/code&gt; for &lt;code&gt;dataset&lt;/code&gt; but clean for &lt;code&gt;LLM&lt;/code&gt; &lt;code&gt;RAG&lt;/code&gt; (no &lt;code&gt;KVS&lt;/code&gt; file to fetch).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you need &lt;code&gt;1000+&lt;/code&gt; sites, use &lt;code&gt;qbitlabs&lt;/code&gt; (&lt;code&gt;$0.05 HD&lt;/code&gt; = &lt;code&gt;$50/1K&lt;/code&gt;). If you need &lt;code&gt;8&lt;/code&gt; core social + &lt;code&gt;enriched&lt;/code&gt; + &lt;code&gt;batch 100&lt;/code&gt; + &lt;code&gt;direct CDN&lt;/code&gt; without &lt;code&gt;yt-dlp&lt;/code&gt;/&lt;code&gt;ffmpeg&lt;/code&gt;/&lt;code&gt;4GB&lt;/code&gt;, use this. &lt;code&gt;Build 0.0.11&lt;/code&gt; live at &lt;code&gt;https://console.apify.com/actors/GdXcUau5zytXfvdvS&lt;/code&gt; — star it and request &lt;code&gt;SoundCloud&lt;/code&gt;/&lt;code&gt;Twitch&lt;/code&gt; next — we’ll add without &lt;code&gt;yt-dlp&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>saas</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
