You recorded an hour-long interview. Right now it is one file on one hard drive, and every format your audience actually reads — the article, the newsletter, the captioned clip, the five posts that drive people back to it — is trapped inside it. Doing that conversion by hand costs most of a working day, and the tenth time you do it you will cut corners. This section builds the alternative: a Python pipeline (a chain of small scripts where each one's output becomes the next one's input) that takes one recording and writes a transcript, a blog draft, a newsletter, a subtitle file and a batch of social posts while you make coffee.
The audience for this is people who publish: podcasters, course creators, webinar hosts, marketers sitting on a folder of sales calls. You do not need to write Python professionally. You need to be able to run a file, paste an API key into a text file, and read an error message without panicking. Everything below is complete and runnable on Python 3.10 or newer with the official openai SDK, and it sits inside the wider AI Content Creation & Marketing Automation with Python track.
Why an assembly line beats four separate jobs
Most people repurpose content the slow way: open the recording, scrub through it, write the article, then reread the article to write the newsletter, then rewatch the video to time the captions. Each pass starts from the raw media again. The pipeline flips that. You pay the expensive step — listening to the whole recording — exactly once, in the form of a transcript, and after that every output is a cheap text-to-text transformation.
That single change has three consequences worth understanding before you write any code. First, cost collapses. Transcription is billed by audio minute, so it happens once no matter how many formats you produce; the follow-up calls are billed by token on a few thousand words of text. Second, consistency arrives for free, because every output descends from the same source text and therefore quotes the same numbers and the same phrasing. Third, and least obvious, failures become cheap. If the social posts come out flat you regenerate the social posts, not the transcript. Each stage writes its result to disk, so a stage you already paid for never runs twice.
The order of the stages is not arbitrary either. Cleaning has to happen after transcription and before generation, because a raw transcript is genuinely bad source material — the model faithfully absorbs the false starts and produces prose with the same stutter. Splitting has to happen before transcription because the upload has a hard size ceiling. Get the order right and the whole thing is about eighty lines of Python.
Four guides sit underneath this one and go deep on individual stages. If you only want the transcription half, Transcribe Audio with the Whisper API and Python covers it end to end. The rest of this page is the spine that connects them.
Prerequisites
You need Python 3.10 or newer, a working virtual environment, and one system dependency that trips up more people than any API problem: ffmpeg, the open-source tool that decodes and re-encodes audio and video. pydub is a thin, friendly wrapper around it, so if ffmpeg is missing, pydub fails with a warning rather than an obvious error.
Create and activate an environment first. If that phrase is new, work through Create a Python Virtual Environment for AI before continuing.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "openai>=1.40" "pydub>=0.25" "httpx>=0.27" "python-dotenv>=1.0"
pip freeze > requirements.txt
Now install ffmpeg itself, which pip cannot do for you:
brew install ffmpeg # macOS
sudo apt install ffmpeg # Debian or Ubuntu
winget install Gyan.FFmpeg # Windows, in PowerShell
Check it worked before going any further, because every later error becomes confusing if this one is outstanding:
ffmpeg -version
Put your API key in a .env file in the project folder:
OPENAI_API_KEY=sk-your-openai-key-here
Then add that file to .gitignore immediately, before you make a single commit:
echo ".env" >> .gitignore
A leaked key is billed to you, and public repositories are scraped continuously by people looking for exactly this. One line of .gitignore is the cheapest insurance in the whole workflow.
Finally, put a recording in the folder. Anything ffmpeg understands works: mp3, m4a, wav, mp4, mov. The examples below assume a file called episode.mp4, so change that name or rename your file.
Step 1 — Get audio out of the source file and split it
Two things stand between your recording and the transcription API. The file may be a video container with a soundtrack buried inside it, and it may be larger than the upload ceiling — currently 25 MB per request, though you should confirm that against the API reference rather than trusting a number in an article. Both problems have the same solution: load the media with pydub, shrink it, and slice it.
Shrinking comes first because it often removes the need to slice at all. Transcription models work from speech, not from stereo separation or studio-grade sample rates, so downmixing to a single channel and exporting at a modest bitrate can cut a file by an order of magnitude with no measurable effect on accuracy. A stereo, high-bitrate hour that arrives as 90 MB routinely lands under 30 MB as mono at 64 kbps.
Slicing is the fallback for anything still too big. Slice on a fixed duration rather than on file size, because a fixed duration gives you an exact millisecond offset for each piece, and you will need that offset to repair the timestamps in the next step. Ten minutes is a good default: comfortably under the ceiling at speech bitrates, and few enough pieces that a one-hour recording is six requests.
from pathlib import Path
from pydub import AudioSegment
SOURCE = Path("episode.mp4") # video or audio; ffmpeg decodes both
CHUNK_MS = 10 * 60 * 1000 # ten minutes, in milliseconds
OUT_DIR = Path("chunks")
audio = AudioSegment.from_file(SOURCE)
audio = audio.set_channels(1).set_frame_rate(16000) # mono, 16 kHz speech
OUT_DIR.mkdir(exist_ok=True)
pieces = []
for index, start_ms in enumerate(range(0, len(audio), CHUNK_MS)):
piece = audio[start_ms:start_ms + CHUNK_MS]
path = OUT_DIR / f"chunk_{index:02d}.mp3"
piece.export(path, format="mp3", bitrate="64k")
pieces.append({"path": path, "offset_ms": start_ms})
size_mb = path.stat().st_size / 1_000_000
print(f"{path} {len(piece) / 1000:>6.0f}s {size_mb:>5.1f} MB")
print(f"{len(pieces)} piece(s) ready")
Two details in that code matter more than they look. len(audio) returns the duration in milliseconds, not a number of samples, which is why slicing with audio[start:end] reads like slicing a list. And offset_ms is stored next to each path rather than recomputed later — when you come back to this script in three months, that dictionary is the only thing standing between you and subtitles that drift further out of sync with every chunk.
A fixed ten-minute cut will sometimes land mid-sentence. In practice this costs you a word or two at the boundary, which is invisible in a blog post and slightly visible in subtitles. If subtitle precision matters, overlap the pieces by two seconds and drop duplicate segments at the seams; Generate SRT Subtitles with Python and AI works through that repair in detail.
Step 2 — Transcribe and keep the timestamps
Now send each piece to the audio transcription endpoint. The call itself is short; the parameters are where the value is, and picking them badly is what leaves people with a transcript they cannot reuse.
Ask for response_format="verbose_json". The default, json, returns nothing but a block of text, and the moment you throw away timing you have permanently closed the door on subtitles, chapter markers and clip selection. Verbose JSON adds a segments list where each entry carries a start second, an end second and its own text. It costs nothing extra.
Set language when you know it. The model detects language automatically, but detection occasionally wobbles on the first few seconds of a recording — music, a cough, a bilingual greeting — and one wrong guess corrupts a whole chunk. Passing a two-letter code such as "en" removes that failure mode.
Set temperature=0. Temperature controls how much randomness the model allows itself, and for transcription you want none: you are asking what was said, not for an interpretation. If the idea is unfamiliar, What Is Temperature in an LLM API? explains it properly.
Finally, use the prompt parameter for vocabulary. It is not an instruction field — it works as a hint about what the audio is likely to contain, so feeding it product names, people's names and acronyms is the single cheapest accuracy improvement available.
import json
import os
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() # reads .env, which is in .gitignore
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
VOCAB = "Acme Analytics, Priya Raghunathan, SKU, MRR, churn"
def transcribe_piece(path: Path, offset_ms: int = 0, language: str = "en") -> list[dict]:
"""Transcribe one audio file and return timestamped segments."""
with open(path, "rb") as audio_file:
result = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
response_format="verbose_json",
timestamp_granularities=["segment"],
language=language,
temperature=0,
prompt=VOCAB,
)
offset_s = offset_ms / 1000
return [
{"start": seg.start + offset_s, "end": seg.end + offset_s, "text": seg.text.strip()}
for seg in result.segments
]
segments: list[dict] = []
for piece in pieces: # pieces came from Step 1
segments.extend(transcribe_piece(piece["path"], piece["offset_ms"]))
Path("segments.json").write_text(json.dumps(segments, indent=2), encoding="utf-8")
raw_text = " ".join(seg["text"] for seg in segments)
Path("transcript_raw.txt").write_text(raw_text, encoding="utf-8")
print(f"{len(segments)} segments, {len(raw_text.split())} words")
The offset_s addition is the load-bearing line. Every chunk's timestamps come back starting at zero, so without it your second chunk claims to begin at second zero, your third claims the same, and the subtitle file you generate later is nonsense from the ten-minute mark onward.
Notice that this script writes two files. segments.json is the machine-readable master copy with timing; transcript_raw.txt is the flat text you will clean in the next step. Keeping both means you never re-upload audio to recover something you already paid to produce.
You can also ask for timestamp_granularities=["segment", "word"], which adds a words list giving a start and end second for every individual word. It is the right choice for two jobs: karaoke-style captions where each word highlights as it is spoken, and clip hunting, where you want to find the exact second a phrase begins so you can cut a thirty-second video around it. It is the wrong choice for everything else, because it inflates the response enormously and segment-level timing is already accurate enough for subtitles. Ask for word timings when you know what you will do with them.
One more habit that pays for itself: print the segment count and word count after every run, as the script above does. A one-hour interview that returns forty segments and eight hundred words did not transcribe properly — something in the pipeline sent silence, or only the first chunk, and you want to know that now rather than after generating five formats from a truncated transcript.
Step 3 — Clean the transcript before anything reads it
Read a raw transcript aloud and you will hear why this step exists. Speech contains hesitation sounds, repeated words, abandoned sentences and almost no punctuation, and the transcription model reproduces all of it faithfully because that is its job. Hand that text to a writing model and it does not silently ignore the mess — it treats the mess as your house style and writes prose that stumbles in the same way.
Cleaning is two passes, mechanical then editorial, in that order.
The mechanical pass is a regular expression that deletes hesitation sounds and collapses the whitespace they leave behind. Keep the list short and conservative. Words like "like" and "right" are filler sometimes and meaningful the rest of the time, and a greedy list quietly rewrites what your guest said.
import re
FILLERS = re.compile(r"\b(?:u+m+|u+h+|e+r+m*|mm+-?hm+|you know|i mean)\b", re.IGNORECASE)
def strip_fillers(text: str) -> str:
"""Remove hesitation sounds and tidy the whitespace they leave behind."""
text = FILLERS.sub("", text)
text = re.sub(r"\b(\w+)(\s+\1\b)+", r"\1", text, flags=re.IGNORECASE) # "the the" -> "the"
text = re.sub(r"\s+([,.!?;:])", r"\1", text)
text = re.sub(r"\s{2,}", " ", text)
return text.strip()
The editorial pass restores punctuation, capitalisation and speaker turns using a cheap model. Two rules make it safe. Give it an explicit instruction not to summarise, reorder or invent, because a model asked vaguely to "clean up" a transcript will happily shorten it. And send the text in blocks of a few thousand characters rather than in one enormous request, so a long recording never runs into the context ceiling — the same chunking idea covered in Split Long Text into Chunks for AI APIs.
TIDY_SYSTEM = (
"You are a transcript editor. Restore punctuation, capitalisation and "
"sentence breaks. Start a new line with 'Speaker: ' when the speaker "
"changes. Do not summarise, reorder, translate or invent words. "
"Return the edited text and nothing else."
)
def tidy_block(block: str) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[
{"role": "system", "content": TIDY_SYSTEM},
{"role": "user", "content": block},
],
)
return response.choices[0].message.content.strip()
def clean_transcript(raw: str, block_chars: int = 4000) -> str:
stripped = strip_fillers(raw)
blocks = [stripped[i:i + block_chars] for i in range(0, len(stripped), block_chars)]
return "\n\n".join(tidy_block(b) for b in blocks)
Splitting on a raw character count can cut a sentence in half. The model repairs the seam well enough for prose, but if you need exact fidelity, split on segment boundaries from segments.json instead — you already know where every sentence started.
Be honest about what the speaker labels are worth. The transcription model returns text, not an identification of who is talking, so the editing pass is inferring turns from conversational cues — a question followed by an answer, a change of subject, an interruption. On a two-person interview with distinct speaking styles it is usually right. On a four-person panel where people talk over each other it will guess, and it will guess confidently. If attribution genuinely matters, because you are quoting people publicly or writing meeting minutes that someone will act on, record each participant on a separate track and transcribe the tracks separately. That is the only approach that gives you certainty, and it costs nothing beyond a little care at recording time.
One habit worth forming: read the first two hundred words of the cleaned output yourself, every time, before generating anything from it. Thirty seconds of reading catches a mis-heard product name that would otherwise be repeated identically in five published formats.
Step 4 — Fan the clean transcript out to each format
Here is the mistake almost everyone makes on their first attempt: one giant prompt asking for a blog post and a newsletter and five tweets. It returns all of them, and all of them are mediocre. The model has one attention budget, one tone, and one implicit sense of how long the answer should be, so the four outputs converge on a shapeless middle.
Call the model once per format. Each call gets three things of its own: a system prompt that assigns a role and the rules for that format, a length budget expressed both in the instruction and in max_tokens, and a temperature matched to how much invention the format tolerates. A blog post is a faithful restructuring, so keep it low. Social posts live or die on variety, so raise it.
Expressing those differences as data rather than as four copy-pasted functions is what keeps the script maintainable:
FORMATS = {
"post": {
"system": (
"You are a features editor. Turn the interview transcript into a "
"Markdown blog post: one H1, three or four H2 sections, short "
"paragraphs. Use only facts present in the transcript."
),
"instruction": "Write the post. Aim for 900 to 1200 words.",
"temperature": 0.4,
"max_tokens": 2200,
"json": False,
},
"newsletter": {
"system": (
"You are a newsletter editor. Write one plain-text email with a "
"subject line, a single clear takeaway, and a closing line that "
"points readers to the full episode."
),
"instruction": "Write the email. Keep it between 200 and 300 words.",
"temperature": 0.5,
"max_tokens": 700,
"json": False,
},
"social": {
"system": (
"You are a social media editor. Return JSON shaped as "
"{\"posts\": [{\"platform\": \"...\", \"text\": \"...\"}]}. "
"Each post stands alone and quotes the transcript accurately."
),
"instruction": "Write six posts: three short, three thread openers.",
"temperature": 0.8,
"max_tokens": 900,
"json": True,
},
}
def render(fmt_name: str, transcript: str) -> str:
spec = FORMATS[fmt_name]
kwargs = {"response_format": {"type": "json_object"}} if spec["json"] else {}
response = client.chat.completions.create(
model="gpt-4o",
temperature=spec["temperature"],
max_tokens=spec["max_tokens"],
messages=[
{"role": "system", "content": spec["system"]},
{"role": "user", "content": f"{spec['instruction']}\n\nTRANSCRIPT:\n{transcript}"},
],
**kwargs,
)
return response.choices[0].message.content.strip()
The response_format={"type": "json_object"} argument on the social call is what makes the output safe to parse with json.loads instead of hopeful string surgery. It only applies where you actually want structured data, which is why it lives in the format definition rather than in the shared function.
Adding a fifth format later is now a dictionary entry, not new code. That is the real payoff of the data-driven shape, and it is worth more than the twenty lines it saves.
Two of these formats have guides of their own. Turn a Podcast Episode into a Blog Post goes deeper on structuring a long draft that still sounds like the guest, and Summarize a YouTube Video with Python handles the case where the source is a public video rather than a file you own. For the email specifically, Generate Email Newsletters with Python and AI covers subject lines and send formatting, and Generate Twitter Threads with Python and AI turns the social JSON into a scheduled thread.
Keep the outputs anchored to what was actually said
Repurposing has a specific failure mode that ordinary copywriting does not. Every output claims to represent a real person who really said something, so an invented quote is not a stylistic problem, it is a factual one — and the person quoted may well read it. Instructions such as "use only facts present in the transcript" help, but they are a request, not a guarantee.
The cheap structural defence is to verify quotes mechanically. Any sentence the model wraps in quotation marks should exist, close to word for word, in the transcript. Checking that is a few lines of Python and catches the worst cases immediately:
import re
def unverified_quotes(draft: str, transcript: str, min_words: int = 4) -> list[str]:
"""Return quoted phrases from the draft that do not appear in the transcript."""
haystack = re.sub(r"[^a-z0-9 ]+", " ", transcript.lower())
haystack = re.sub(r"\s+", " ", haystack)
missing = []
for quote in re.findall(r"[\"“]([^\"”]{15,})[\"”]", draft):
needle = re.sub(r"[^a-z0-9 ]+", " ", quote.lower())
needle = re.sub(r"\s+", " ", needle).strip()
if len(needle.split()) >= min_words and needle not in haystack:
missing.append(quote)
return missing
for quote in unverified_quotes(open("post.md").read(), open("transcript.txt").read()):
print("CHECK THIS QUOTE:", quote)
The comparison strips punctuation and case on both sides, because a model will legitimately change a comma or capitalise the first word of a quotation. What it will not legitimately do is change the words, so anything this function prints deserves a human look before publication.
Run the check on the blog post and the social posts, where direct quotation is common. Skip it on the newsletter if your prompt asks for a paraphrase rather than quotes. And treat a flagged phrase as a prompt to verify rather than proof of a mistake — the model may have quoted accurately from a part of the recording where the transcript itself misheard a word, which is a different problem with the same fix: listen to that moment.
There is a second, quieter check worth automating: length. A model that returns 300 words when you asked for 1000 has usually run out of max_tokens mid-sentence, and the truncation is easy to miss when you are skimming. A single len(draft.split()) assertion after each call, printed alongside the target, turns a silent failure into an obvious one.
Parameter reference
These are the settings you will actually change. Everything else in the transcription call can stay at its default.
| Parameter | Type | Default | Effect |
|---|---|---|---|
response_format | string | json | json, text, srt, vtt or verbose_json. Use verbose_json to get segment timings. |
timestamp_granularities | list | ["segment"] | Add "word" for per-word timings. Requires verbose_json. |
language | string | auto-detect | ISO two-letter code such as "en". Stops detection errors on noisy openings. |
temperature | float | 0 | Randomness in the transcript. Leave at 0 unless a chunk fails to decode. |
prompt | string | empty | Vocabulary hint: names, acronyms, product terms. Not an instruction. |
CHUNK_MS | int | 600000 | Your slice length in milliseconds. Ten minutes keeps each upload well under the ceiling. |
Troubleshooting
Maximum content size limit (26214400) exceeded— the upload is larger than the API accepts. Re-export as mono at 64 kbps first; if it is still too large, lowerCHUNK_MSto five minutes and rerun Step 1.RuntimeWarning: Couldn't find ffmpeg or avconv— pydub loaded but the decoder behind it did not. Install ffmpeg for your platform and confirm withffmpeg -versionin the same terminal you run Python from.pydub.exceptions.CouldntDecodeError: Decoding failed— ffmpeg cannot read that file. Usually a partly downloaded recording or an unusual container. Re-download it, or convert it once withffmpeg -i broken.m4a fixed.mp3and point the script at the result.openai.RateLimitError: Error code: 429— you fired every chunk at the API at once. Transcribe sequentially with a short pause between calls, and add retries with increasing waits as described in Fix the 429 Rate-Limit Error in Python.openai.APIConnectionError: Connection error.— a large upload lost its connection mid-transfer. Smaller chunks fix this more reliably than a longer timeout; Fix Connection and Timeout Errors with AI APIs covers the retry pattern.json.JSONDecodeError: Expecting value: line 1 column 1 (char 0)— the social output was not JSON. Confirmresponse_format={"type": "json_object"}is set on that call and that the system prompt describes the exact shape you want, as in Write System Prompts that Control Output Format.- A silent stretch produces a repeated phrase. Transcription models sometimes loop on near-silence. Trim leading and trailing silence with pydub before uploading, and delete any segment whose text repeats the previous one word for word.
Worked example — repurpose.py
This is the whole pipeline in one file. Point it at an mp3 and it writes transcript.txt, post.md and social.json into the current folder.
"""repurpose.py — one recording in; transcript, blog post and social posts out."""
import json
import os
import re
import sys
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
from pydub import AudioSegment
load_dotenv() # .env is listed in .gitignore
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
CHUNK_MS = 10 * 60 * 1000
FILLERS = re.compile(r"\b(?:u+m+|u+h+|e+r+m*|you know|i mean)\b", re.IGNORECASE)
POST_SYSTEM = ("You are a features editor. Turn the transcript into a Markdown blog post: "
"one H1, three or four H2 sections. Use only facts in the transcript.")
SOCIAL_SYSTEM = ("You are a social media editor. Return JSON shaped as "
"{\"posts\": [{\"platform\": \"...\", \"text\": \"...\"}]}.")
def transcribe(source: Path) -> str:
"""Split if needed, transcribe every piece, return the joined raw text."""
audio = AudioSegment.from_file(source).set_channels(1).set_frame_rate(16000)
parts = []
for index, start in enumerate(range(0, len(audio), CHUNK_MS)):
piece_path = Path(f"_chunk_{index:02d}.mp3")
audio[start:start + CHUNK_MS].export(piece_path, format="mp3", bitrate="64k")
with open(piece_path, "rb") as handle:
result = client.audio.transcriptions.create(
model="whisper-1", file=handle, response_format="verbose_json",
timestamp_granularities=["segment"], language="en", temperature=0)
parts.extend(seg.text.strip() for seg in result.segments)
piece_path.unlink() # tidy up the temporary chunk
print(f" transcribed piece {index + 1}")
return " ".join(parts)
def clean(raw: str) -> str:
"""Strip fillers, then let a cheap model restore punctuation and speakers."""
text = re.sub(r"\s{2,}", " ", FILLERS.sub("", raw)).strip()
blocks = [text[i:i + 4000] for i in range(0, len(text), 4000)]
edited = []
for block in blocks:
reply = client.chat.completions.create(
model="gpt-4o-mini", temperature=0,
messages=[{"role": "system", "content": "Restore punctuation, capitalisation and "
"speaker turns. Do not summarise or invent words."},
{"role": "user", "content": block}])
edited.append(reply.choices[0].message.content.strip())
return "\n\n".join(edited)
def render(system: str, instruction: str, transcript: str, temp: float, as_json: bool) -> str:
extra = {"response_format": {"type": "json_object"}} if as_json else {}
reply = client.chat.completions.create(
model="gpt-4o", temperature=temp, max_tokens=2200,
messages=[{"role": "system", "content": system},
{"role": "user", "content": f"{instruction}\n\nTRANSCRIPT:\n{transcript}"}],
**extra)
return reply.choices[0].message.content.strip()
if __name__ == "__main__":
source = Path(sys.argv[1] if len(sys.argv) > 1 else "episode.mp3")
print(f"Transcribing {source} ...")
transcript = clean(transcribe(source))
Path("transcript.txt").write_text(transcript, encoding="utf-8")
Path("post.md").write_text(
render(POST_SYSTEM, "Write the post. 900 to 1200 words.", transcript, 0.4, False),
encoding="utf-8")
social = render(SOCIAL_SYSTEM, "Write six standalone posts.", transcript, 0.8, True)
Path("social.json").write_text(json.dumps(json.loads(social), indent=2), encoding="utf-8")
print("Wrote transcript.txt, post.md and social.json")
Run it from the terminal with the recording as an argument:
python repurpose.py episode.mp3
The json.loads then json.dumps round trip on the last line is deliberate. It fails loudly if the model returned something that is not valid JSON, which is much better than writing a broken file that only explodes next week in whatever tool consumes it. Once the script runs reliably, Schedule Python AI Jobs with GitHub Actions shows how to trigger it automatically whenever a new recording lands.
Before you run it on a long back catalogue, estimate what it will cost. Transcription bills per minute of audio and the text stages bill per token, so a two-hour recording is meaningfully more expensive than a twenty-minute one; Estimate OpenAI API Costs with Python shows how to price a batch before committing to it.
Where to go next
Start with Transcribe Audio with the Whisper API and Python if you want the transcription stage alone, with every parameter explained and a retry loop that survives a flaky connection. Once you have a clean transcript on disk, Turn a Podcast Episode into a Blog Post takes the draft further than the single call above, adding an outline pass and a fact check against the source.
For video work, Generate SRT Subtitles with Python and AI converts your segments.json into properly timed captions with sensible line breaks, and Summarize a YouTube Video with Python covers pulling audio from a public video and producing chapter summaries instead of a full article. Read them in whatever order your backlog demands; they all consume the same transcript this page taught you to produce.
Back to AI Content Creation & Marketing Automation with Python.
Related guides
- Transcribe Audio with the Whisper API and Python — the transcription stage in full, including retries and long files.
- Turn a Podcast Episode into a Blog Post — outline, draft and fact-check a long article from one transcript.
- Generate SRT Subtitles with Python and AI — turn timestamped segments into captions that stay in sync.
- Summarize a YouTube Video with Python — chapter summaries and key quotes from a public video.
- Split Long Text into Chunks for AI APIs — chunk a long transcript without cutting sentences in half.