DEV Community

Cover image for One Video, Five Outputs: Repurpose YouTube Into Articles, Threads, and Shorts Scripts
Bloody Valentine
Bloody Valentine

Posted on

One Video, Five Outputs: Repurpose YouTube Into Articles, Threads, and Shorts Scripts

One Video, Five Outputs: Repurpose YouTube Into Articles, Threads, and Shorts Scripts

One good video holds an article, a thread, three shorts, and a newsletter
section. Creators know this. What stops most developers from automating it
is the boring middle step: getting the transcript into a shape an LLM (and
a video editor) can use. Part 1 covered extraction,
part 2 covered RAG. This one is the repurposing pipeline I
actually run: transcript in, five publishable outputs out.

The input is my Apify Actor
(lexiie/youtube-transcript-api),
because repurposing needs two things plain caption scrapers do poorly:
segments with timestamps (to find cut points) and ready-made SRT
(to burn into clips). Both come back in one dataset item per video.

Step 1: fetch once, reuse everywhere

import os
import requests

ACTOR = "lexiie~youtube-transcript-api"
TOKEN = os.environ["APIFY_TOKEN"]

(resp := requests.post(
    f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
    params={"token": TOKEN},
    json={"urls": ["https://www.youtube.com/watch?v=VIDEO_ID"],
          "language": "en"},
    timeout=300,
)).raise_for_status()
video = resp.json()[0]
assert video["status"] == "success", video.get("error")

text, segments, srt = video["text"], video["segments"], video["srt"]
print(len(text), "chars,", len(segments), "segments")
Enter fullscreen mode Exit fullscreen mode

Everything below reads from these three variables. Fetch once, even if you
generate ten outputs.

Output 1: article draft with real quotes

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

import openai

draft = openai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content":
         "Turn the transcript into an 800-word article. Keep the author's "
         "voice. Include at least 3 verbatim quotes marked with segment "
         "timestamps like [12:04]. No new facts beyond the transcript."},
        {"role": "user", "content": text[:12000]},
    ],
).choices[0].message.content
Enter fullscreen mode Exit fullscreen mode

The [12:04] markers are checkable: each maps back to a segment, so an
editor (or a script) can verify every quote against the source in seconds.
On manual-caption videos the quotes are near perfect. On auto captions,
budget one proofreading pass for homophones.

Output 2: X thread

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

def score(s):
    t = s["text"]
    return (len(t)                       # substance
            + 40 * ("?" in t)            # questions hook
            + 40 * ("!" in t)            # strong claims hook
            - 60 * (len(t) < 40))        # skip fragments

best = sorted(segments, key=score, reverse=True)[:7]
for s in best:
    print(f"[{s['start']:.0f}s] {s['text']}")
Enter fullscreen mode Exit fullscreen mode

Feed those seven moments to the LLM with "write a 7-post thread, one
moment per post, first post under 200 characters." The timestamps ride
along, so each post can link the exact moment with a &t= URL. Threads
with timestamp links outperform plain ones in my experience: skeptics
click through, believers don't need to.

Output 3: shorts scripts with cut points

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

hook = max(segments[:8], key=score)          # open strong
beats = sorted(segments, key=score, reverse=True)[1:4]
beats.sort(key=lambda s: s["start"])          # back into story order

total = sum(b["duration"] for b in [hook, *beats])
print(f"~{total:.0f}s of source material")
for b in [hook, *beats]:
    print(f"cut {b['start']:.1f}s -> {b['start'] + b['duration']:.1f}s | {b['text']}")
Enter fullscreen mode Exit fullscreen mode

The cut X -> Y lines are an edit decision list your editor can follow
blindly, and the total tells you upfront whether you have 30 seconds of
material or need a wider net. Prompt the LLM with these beats for the
on-screen caption text, keeping each line under 40 characters.

Output 4: subtitles, done already

This one is free: the srt field from step 1 is an upload-ready SubRip
file. Save it next to the clip and burn it in or upload it as a caption
track:

open("clip.srt", "w").write(srt)
Enter fullscreen mode Exit fullscreen mode

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

Output 5: newsletter section

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

What breaks (honest notes)

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

Links

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

Top comments (0)