Auto-Generate Blog Posts from YouTube with AI
David Boulen · 7/18/2026 · 8 min read
Auto-Generate Blog Posts from YouTube with AI
Most content teams already have everything they need for a steady stream of blog posts. The raw material is sitting in their YouTube channel, narrated by someone who knows the topic well. The bottleneck is turning spoken words into structured, SEO-ready text without paying a writer to transcribe and reformat every video by hand.
A transcript-to-blog pipeline solves that bottleneck. You fetch the transcript, pass it to an LLM with a tight prompt, apply a quick quality gate, and publish. The steps below walk through each stage with working code you can copy into your own automation.
The Repurposing Workflow Content Teams Actually Use
The practical version of this workflow has four stages: fetch, structure, generate, and review.
Fetch means getting a clean transcript from the video, not a raw auto-caption dump. Structure means detecting which parts of the transcript map to headings, examples, and conclusions. Generate means running the structured transcript through an LLM with a prompt that enforces SEO constraints. Review means a human or a rule-based checker catches factual errors and broken links before the post goes live.
According to content repurposing research compiled by Shno citing a ReferralRock survey, 46% of marketers identify content repurposing as the single best-performing content marketing strategy. The reason is straightforward: the research, examples, and argument are already done. You are changing the format, not creating new ideas from scratch.
The pipeline described here is intentionally code-first. If you prefer a no-code version, the No-Code YouTube Transcript Automation with Make and n8n walkthrough covers the same stages using visual workflow tools.
Fetching Transcripts and Detecting Structure
The first API call retrieves the transcript as a JSON array of segments, each with the spoken text, a start time in seconds, and a duration.
curl "https://getyoutubetranscriber.com/api/v2/transcript?video_url=dQw4w9WgXcQ&send_metadata=true&include_timestamp=true" \
-H "Authorization: Bearer ytt_your_key_here"
The response looks like this:
{
"video_id": "dQw4w9WgXcQ",
"language": "en",
"transcript": [
{ "text": "Welcome back. Today we are covering three ways to reduce churn.", "start": 0.0, "duration": 4.2 },
{ "text": "The first method is proactive outreach.", "start": 4.2, "duration": 3.1 }
],
"metadata": {
"title": "3 Ways to Reduce SaaS Churn",
"author_name": "ExampleChannel",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
}
}
Setting send_metadata=true pulls the video title and channel name into the same response, which saves an extra API call when you later need those for the blog post header or internal link text.
Detecting Paragraph Breaks and Topics
Auto-captions break at arbitrary points based on speech timing. Before you hand the transcript to an LLM, reassemble the segments into logical paragraphs. A practical heuristic: merge consecutive segments until you have roughly 150 words or detect a long pause (a gap of more than 1.5 seconds between start values).
In Python:
import json
def merge_segments(transcript, word_limit=150, pause_threshold=1.5):
paragraphs = []
current_text = []
current_start = transcript[0]["start"]
prev_end = transcript[0]["start"]
for seg in transcript:
gap = seg["start"] - prev_end
word_count = sum(len(t.split()) for t in current_text)
if gap > pause_threshold or word_count >= word_limit:
paragraphs.append({
"text": " ".join(current_text),
"start": current_start
})
current_text = []
current_start = seg["start"]
current_text.append(seg["text"])
prev_end = seg["start"] + seg["duration"]
if current_text:
paragraphs.append({"text": " ".join(current_text), "start": current_start})
return paragraphs
Pass the resulting list to your LLM. Each item in paragraphs now has a start value in seconds, which you will use later for timestamp links.
Prompting an LLM for SEO-Ready Drafts
The quality of the output depends almost entirely on how specific your prompt is. A vague prompt like "turn this into a blog post" produces a vague blog post. A prompt with concrete constraints produces a usable first draft.
Here is a prompt template that works reliably:
import openai
def transcript_to_blog(paragraphs, target_keyword, video_url, video_title):
transcript_text = "\n\n".join(
f"[{int(p['start'])}s] {p['text']}" for p in paragraphs
)
prompt = f"""You are a senior technical writer. Convert the following YouTube transcript into a blog post.
RULES:
- Target keyword: "{target_keyword}" — include it in the H1, the first paragraph, and at least one H2.
- Write exactly 4 H2 headings and use H3s only when a section has three or more subsections.
- Target 900-1100 words in the body (exclude headings and the intro).
- Write in second person. Short paragraphs, no more than four sentences each.
- Preserve the speaker's examples and numbers exactly. Do not invent facts.
- After each major point, add a Markdown link to the timestamp in the video:
Format: [Watch at Xs](https://www.youtube.com/watch?v=VIDEO_ID&t=Xs)
Replace VIDEO_ID with the actual video ID and X with the start time in seconds.
- End with a two-sentence conclusion that tells the reader one concrete next step.
VIDEO TITLE: {video_title}
VIDEO URL: {video_url}
TRANSCRIPT:
{transcript_text}
Return only the Markdown blog post. No preamble.
"""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.4
)
return response.choices[0].message.content
The temperature=0.4 setting keeps the output factual and consistent. Higher temperatures produce more varied prose but also more hallucinated details, which creates extra work in the review stage.
A JavaScript equivalent using the same API structure:
import OpenAI from "openai";
const client = new OpenAI();
async function transcriptToBlog(paragraphs, targetKeyword, videoUrl, videoTitle) {
const transcriptText = paragraphs
.map(p => `[${Math.floor(p.start)}s] ${p.text}`)
.join("\n\n");
const prompt = `You are a senior technical writer. Convert the following YouTube transcript into a blog post.
RULES:
- Target keyword: "${targetKeyword}" — include it in the H1 and first paragraph.
- Write exactly 4 H2 headings. Target 900-1100 words.
- Preserve all examples and numbers exactly as spoken.
- Add timestamp links in format: [Watch at Xs](${videoUrl}&t=Xs)
- End with a concrete next step for the reader.
VIDEO TITLE: ${videoTitle}
TRANSCRIPT:
${transcriptText}
Return only the Markdown blog post.`;
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
temperature: 0.4
});
return response.choices[0].message.content;
}
Controlling Heading Depth
Explicitly specifying the number of H2s in the prompt prevents the LLM from flattening everything under one heading or nesting five levels deep. Four H2s works well for a 1,000-word post. Adjust the count based on your target word length.
Adding Headings, Timestamps, and Internal Links
After the LLM returns the draft, run two post-processing passes.
Timestamp links. The prompt already instructs the LLM to embed timestamp links, but verify that the format is correct. A YouTube deep-link looks like https://www.youtube.com/watch?v=VIDEO_ID&t=120 for the 2-minute mark. These links give readers a way to verify claims against the original video, which improves trust and reduces the chance a misquoted statistic goes unnoticed.
Internal links. Scan the draft for topics that match other posts on your blog. If the generated post mentions "bulk transcript downloads," add a link to Download YouTube Transcripts in Bulk Without Getting Blocked. You can automate this with a simple keyword-to-URL map:
INTERNAL_LINKS = {
"bulk transcript": "https://getyoutubetranscriber.com/blog/download-youtube-transcripts-in-bulk-without-getting-blocked",
"125 languages": "https://getyoutubetranscriber.com/blog/get-youtube-transcripts-in-125-languages-a-guide",
# add more as your blog grows
}
def inject_internal_links(markdown, link_map):
for keyword, url in link_map.items():
if keyword.lower() in markdown.lower():
# Replace first occurrence only, case-insensitive
import re
pattern = re.compile(re.escape(keyword), re.IGNORECASE)
markdown = pattern.sub(f"[{keyword}]({url})", markdown, count=1)
return markdown
Limit injected links to two or three per post. More than that starts to read as spam and can hurt SEO.
Quality Control Before Publishing
LLMs make two categories of mistakes in this workflow: factual errors (numbers or names that differ slightly from the transcript) and formatting errors (broken Markdown, missing headings, timestamp links with wrong video IDs).
A lightweight three-check gate catches most problems before a human ever opens the draft.
Check 1: Fact consistency. Compare every number in the draft against the transcript. A regex pass that extracts digits from both texts and diffs them takes under a second and flags most hallucinations.
import re
def extract_numbers(text):
return set(re.findall(r'\b\d+(?:\.\d+)?%?\b', text))
def flag_new_numbers(draft, transcript_text):
draft_nums = extract_numbers(draft)
transcript_nums = extract_numbers(transcript_text)
suspicious = draft_nums - transcript_nums
return suspicious # numbers in the draft that never appeared in the transcript
Check 2: Link validity. Fetch each URL in the draft with a HEAD request and log any 404s. Timestamp links are especially prone to errors if the video ID was substituted incorrectly.
Check 3: Reading level. Paste the intro paragraph into a Flesch-Kincaid calculator or use a library like textstat. Aim for a score between 50 and 70 (roughly 8th-to-10th-grade reading level) for technical audiences. A score below 40 usually means the LLM copied dense jargon from the transcript without unpacking it.
After the automated checks pass, a human reviewer needs about five minutes to read the draft for tone, verify the headline against the target keyword, and confirm the CTA at the end makes sense for the topic.
According to Shno's analysis citing an Orbit Media survey of 808 marketers, bloggers who update and refine existing content see 2.5x stronger results than those who publish without revision. The same principle applies here: the pipeline gets you 80% of the way to a publishable post, and a brief human pass closes the gap.
Ship the Pipeline, Then Iterate
The workflow above produces a publishable draft from a YouTube video in under two minutes once the pipeline is running. The practical next step is to run it against three or four videos from your channel, compare the output quality, and tighten the prompt based on what the LLM consistently gets wrong.
YouTube Transcriber gives you 100 free credits with no credit card required, which is enough to test the full pipeline on a handful of videos. Visit getyoutubetranscriber.com to grab your API key and start with the transcript endpoint shown above.