AI Super Simplified

Make a 2-Minute Explainer Video on Windows

Turn a short script into a narrated, 1920×1080 video with bold text cards, a slow zoom and a natural AI voice. One Python script, all free tools, no editing software.

30 min first setup15–30 min per video$0Windows

What you'll make and what you need

You'll turn a short script into a 2-minute, 1920×1080 explainer video: bold text cards, a slow zoom on each card, a natural AI voiceover, and fades between cards. One Python script does all of it. Everything is free and runs on your own Windows PC.

This is the same method used for AI Super Simplified's video on Netflix's The AI Doc. It uses no stock footage or movie clips, so YouTube's Content ID has nothing to flag.

Time: about 30 minutes to set up the first time, then 15 to 30 minutes per video. Most of that is writing the script.

ToolWhat it doesCost
Python 3.12+Runs the build scriptFree
edge-tts (Python package)Creates the voiceover with Microsoft's neural voicesFree, needs internet
Pillow (Python package)Draws the text cardsFree
ffmpegAdds the zoom and fades, then joins everything into one MP4Free
Poppins fontThe bold, clean typeface on the cardsFree (Google Fonts)
YouTube StudioWhere you upload, add chapters and set the thumbnailFree

You'll use your own YouTube account and your own brand name, colors and website. The script has one settings block at the top where you change those.

One-time Windows setup

Do these five steps once. Use PowerShell for every command (Start menu → type "PowerShell" → open it).

  1. Install Python. Run the command below. If winget isn't available, download Python from python.org instead and tick "Add python.exe to PATH" in the installer.

    winget install -e --id Python.Python.3.12
    
  2. Install ffmpeg.

    winget install -e --id Gyan.FFmpeg
    
  3. Close PowerShell and open a new window, so it picks up the new programs. Then check both work. Each command should print a version number:

    python --version
    ffmpeg -version
    
  4. Make a project folder and install the two Python packages.

    mkdir $HOME\Videos\explainer
    cd $HOME\Videos\explainer
    python -m pip install edge-tts pillow
    
  5. Add the Poppins font. Go to fonts.google.com/specimen/PoppinsGet fontDownload all. Unzip the file and copy these three into your explainer folder: Poppins-Bold.ttf, Poppins-SemiBold.ttf and Poppins-Regular.ttf.

When you're done, the folder holds the three font files. You'll add script.json and make_video.py next.

Plan the video

A 2-minute video needs about 270 spoken words, split into roughly 11 segments of 8 to 13 seconds each. That comes from a simple rule: a natural voiceover runs about 150 words a minute, and each card adds 0.7 seconds of pause.

Target lengthSpoken wordsSegments
60 seconds~1356
90 seconds~2008 to 9
2 minutes~27010 to 12

A structure that works for almost any topic:

  1. Hook: what this is, and the promise ("the whole thing in two minutes")
  2. Setup: the backstory, in one or two sentences
  3. Who or what's involved
  4. The main points: one segment each, usually 3 to 5
  5. The other side: criticism, the catch, or the limits
  6. Verdict: should the viewer care, and why
  7. Call to action: where to go next

Each segment has four parts:

  • Kicker: a small label above the headline, such as IDEA 1 · DOOM. Leave it blank on the opening card.
  • Headline: 1 or 2 short lines, about 20 characters each. This is the one thing a viewer should remember with the sound off.
  • Sub-line: one line of context in smaller gray text, under about 90 characters.
  • Voiceover: 1 to 3 spoken sentences, about 20 to 35 words.

Writing rules

  • Don't put the voiceover on screen word for word. The card is the headline, the voice is the story.
  • Spell things the way they should sound. Write "A.I.", "C.E.O.s" and "dot com", because the voice can garble AI, CEOs and .com. Keep the on-screen text normal.
  • Use short sentences. A period gives the voice a natural pause.
  • Only quote someone if you've checked the exact words in a reliable source. Otherwise, paraphrase and name the person.

The script file: script.json

Write your video as a list of segments in a file named script.json, saved in the explainer folder. You can open and edit it in Notepad. The example below has five segments. Copy it, then add, remove or rewrite segments to match your plan.

{
  "title": "Netflix's AI Doc in 2 minutes",
  "segments": [
    {
      "kicker": "",
      "headline": ["Netflix's AI Doc", "in 2 minutes"],
      "sub": "The whole 104 minutes. Five big ideas.",
      "voice": "Netflix just dropped The AI Doc. Here's the whole hundred and four minutes, in two."
    },
    {
      "kicker": "THE SETUP",
      "headline": ["An Oscar winner.", "A baby on the way."],
      "sub": "Director Daniel Roher (Navalny) asks: will my son be OK?",
      "voice": "Daniel Roher won an Oscar for Navalny. Then his wife got pregnant, and he started losing sleep over the world his son would inherit. So he went and asked the people building A.I."
    },
    {
      "kicker": "IDEA 1 · DOOM",
      "headline": ["Not collapse.", "Extermination."],
      "sub": "Eliezer Yudkowsky, AI's best-known pessimist",
      "voice": "Idea one: the doom case. A.I.'s best-known pessimist, Eliezer Yudkowsky, says the risk isn't collapse. It's extermination."
    },
    {
      "kicker": "SHOULD YOU WATCH?",
      "headline": ["Yes."],
      "sub": "For the CEOs on camera, and for the family member who keeps asking.",
      "voice": "Should you watch it? Yes. For the C.E.O.s answering hard questions on camera."
    },
    {
      "kicker": "FREE PROMPT",
      "headline": ["yoursite.com"],
      "sub": "Full breakdown plus a free copy-paste prompt",
      "voice": "For the full breakdown, head to your site dot com."
    }
  ]
}

The file format is strict, so watch three things:

  • Put text in straight double quotes ("like this"). Word and Google Docs often switch to curly quotes, which breaks the file. Paste into Notepad to be safe.
  • Put a comma between segments, but not after the last one.
  • Each headline is a list of 1 or 2 lines inside square brackets.

To check the file before you build, paste it into jsonlint.com. It will point to the exact line with the problem.

The build script: make_video.py

This one file does everything: it draws each card, records each voiceover, adds the zoom and fades, joins the clips, and prints your YouTube chapter timestamps. Copy the whole block into Notepad and save it as make_video.py in the explainer folder. In Notepad's Save dialog, set Save as type to All files, or you'll end up with make_video.py.txt.

The only part you need to change is the SETTINGS block near the top: your brand name, voice and colors. It was tested end to end, including the chapter output.

"""make_video.py - turns script.json into a narrated explainer video.

Run from the project folder:   python make_video.py
Output: video.mp4 plus a build folder with the cards, audio and clips.
"""
import asyncio, json, subprocess, sys
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import edge_tts

# ---------- SETTINGS: change these to your own ----------
BRAND = "YOUR BRAND NAME"             # bottom-left of every card
VOICE = "en-US-AndrewNeural"          # list voices: edge-tts --list-voices
RATE = "+4%"                          # speech speed, e.g. "-5%" or "+10%"
BG = (15, 27, 45)                     # background (navy)
HEAD = (249, 246, 239)                # headline text (cream)
ACCENT = (242, 184, 75)               # kicker, brand and progress bar (gold)
SUB = (160, 172, 190)                 # sub-line text (gray)
RING = (30, 48, 74)                   # decorative circles
PAUSE = 0.7                           # seconds of silence after each segment
ZOOM = 0.05                           # 0.05 = slow push-in to 105 percent
# ------------------------------------------------------------

W, H, FPS = 1920, 1080, 30
HERE = Path(__file__).resolve().parent
OUT = HERE / "build"

def font(name, size):
    return ImageFont.truetype(str(HERE / f"Poppins-{name}.ttf"), size)

def wrap(draw, text, fnt, max_w):
    lines, cur = [], ""
    for word in text.split():
        test = (cur + " " + word).strip()
        if draw.textlength(test, font=fnt) <= max_w:
            cur = test
        else:
            lines.append(cur)
            cur = word
    if cur:
        lines.append(cur)
    return lines

def make_card(i, total, seg, path):
    im = Image.new("RGB", (W, H), BG)
    d = ImageDraw.Draw(im)
    d.ellipse((1450, -250, 2250, 550), outline=RING, width=40)
    d.ellipse((-300, 700, 300, 1300), outline=RING, width=30)
    x = 140
    head = seg["headline"]
    hf = font("Bold", 128 if max(len(h) for h in head) <= 20 else 112)
    lh = hf.size * 1.12
    block = (80 if seg.get("kicker") else 0) + len(head) * lh + 30 + (124 if seg.get("sub") else 0)
    y = (H - block) / 2 - 30
    if seg.get("kicker"):
        d.rectangle((x, y + 14, x + 70, y + 22), fill=ACCENT)
        d.text((x + 95, y - 8), seg["kicker"], font=font("SemiBold", 42), fill=ACCENT)
        y += 80
    for line in head:
        d.text((x, y), line, font=hf, fill=HEAD)
        y += lh
    y += 30
    sf = font("Regular", 46)
    for line in wrap(d, seg.get("sub", ""), sf, W - 2 * x)[:2]:
        d.text((x, y), line, font=sf, fill=SUB)
        y += 62
    d.text((x, H - 110), BRAND, font=font("SemiBold", 34), fill=ACCENT)
    bw, bx = 400, W - 140 - 400
    d.rounded_rectangle((bx, H - 92, bx + bw, H - 80), 6, fill=RING)
    d.rounded_rectangle((bx, H - 92, bx + int(bw * (i + 1) / total), H - 80), 6, fill=ACCENT)
    im.save(path)

async def make_voice(text, path):
    await edge_tts.Communicate(text, VOICE, rate=RATE).save(str(path))

def duration(path):
    out = subprocess.check_output(["ffprobe", "-v", "error", "-show_entries", "format=duration",
                                   "-of", "csv=p=0", str(path)])
    return float(out)

def main():
    data = json.loads((HERE / "script.json").read_text(encoding="utf-8"))
    segs = data["segments"]
    OUT.mkdir(exist_ok=True)
    clips = []
    for i, seg in enumerate(segs):
        card, audio, clip = OUT / f"card{i:02}.png", OUT / f"voice{i:02}.mp3", OUT / f"clip{i:02}.mp4"
        print(f"[{i + 1}/{len(segs)}] {' '.join(seg['headline'])}")
        make_card(i, len(segs), seg, card)
        asyncio.run(make_voice(seg["voice"], audio))
        t = round(duration(audio) + PAUSE, 2)
        frames = int(t * FPS)
        step = ZOOM / frames
        vf = (f"scale=3840:-1,zoompan=z='min(1+{step:.6f}*on,{1 + ZOOM})':"
              f"x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d={frames}:s={W}x{H}:fps={FPS},"
              f"fade=t=in:st=0:d=0.25,fade=t=out:st={t - 0.25}:d=0.25,format=yuv420p")
        subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-loop", "1", "-i", str(card), "-i", str(audio),
                        "-filter_complex", f"[0:v]{vf}[v];[1:a]apad=pad_dur={PAUSE}[a]",
                        "-map", "[v]", "-map", "[a]", "-t", str(t), "-c:v", "libx264", "-preset", "veryfast",
                        "-crf", "20", "-c:a", "aac", "-b:a", "160k", "-ar", "48000", str(clip)], check=True)
        clips.append((clip, t, " ".join(seg["headline"])))
    (OUT / "list.txt").write_text("".join(f"file '{c.name}'\n" for c, _, _ in clips), encoding="utf-8")
    subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i",
                    str(OUT / "list.txt"), "-c", "copy", "-movflags", "+faststart", str(HERE / "video.mp4")],
                   check=True)
    print("\nDone: video.mp4\n\nYouTube chapters (paste into the description):")
    start = 0.0
    for _, t, name in clips:
        print(f"{int(start // 60)}:{int(start % 60):02} {name}")
        start += t
    print(f"\nTotal length: {int(start // 60)}:{int(start % 60):02}")

if __name__ == "__main__":
    sys.exit(main())

Run it, check it, tune it

Run it. In PowerShell:

cd $HOME\Videos\explainer
python make_video.py

It prints each segment as it builds. A 2-minute video takes about 2 to 5 minutes, depending on your PC. When it finishes you'll have:

  • video.mp4, the finished video
  • a build folder holding each card image, voice clip and video clip
  • your YouTube chapter timestamps, printed in the window

Check it before you upload.

  • Open the build folder and flip through the card00.png, card01.png images. Look for text running off the edge or crowding the circles.
  • Watch the whole video once with sound. Listen for mispronounced words and change their spelling in voice.
  • If you change the script, just run it again. It overwrites the old files.

Tune it.

To changeEdit thisExample
VoiceVOICEen-US-GuyNeural, en-US-JennyNeural, en-GB-RyanNeural
Speaking speedRATE"-5%" slower, "+10%" faster
ColorsBG, HEAD, ACCENT, SUB, RINGRGB values, e.g. (255, 0, 0) is red
Pause between cardsPAUSE0.5 tighter, 1.0 more breathing room
Zoom strengthZOOM0.03 subtle, 0.08 stronger, 0 off
Brand textBRANDYour channel or site name

To hear every available voice, run edge-tts --list-voices. To try one before a full build:

edge-tts --voice en-US-GuyNeural --text "Testing one two three" --write-media test.mp3

Headlines that don't fit. Keep each line to about 20 characters. Past that, the script drops the font size a notch, and a very long line can still run off the edge. Shorten the words or split them across two lines.

Upload to YouTube

Upload through YouTube Studio in your browser. It takes about 5 minutes.

  1. Go to studio.youtube.comCreateUpload videos → choose video.mp4.
  2. Title: say what it is and promise the payoff, under about 70 characters. Example: Netflix's The AI Doc in 2 Minutes: 3 AI CEOs, 5 Big Ideas.
  3. Description: two sentences on what the video covers, then the chapter list the script printed, then your link. Chapters only appear if the first one starts at 0:00 and every chapter is at least 10 seconds long. Fold any shorter segment into its neighbor.
  4. Thumbnail: upload a 1280×720 JPG or PNG under 2 MB. The quickest option is build\card00.png. A custom thumbnail needs a phone-verified channel (YouTube will prompt you).
  5. Disclosure: if you summarize someone else's film, show or book, add a line such as "Independent summary and commentary. Contains no footage from the film." Also mention any business link you have to the topic.
  6. Altered or synthetic content: YouTube asks this during upload. An AI voice reading your own script, over text cards, generally isn't the realistic content it's asking about. Answer honestly for your video, and when in doubt, say yes.
  7. Visibility: choose Unlisted first, watch it once on YouTube, then switch it to Public.

Use only material you own. No movie clips, songs or other people's photos. That's what keeps Content ID from claiming or blocking the video. Link to the official trailer in the description instead.

Troubleshooting

ProblemFix
python or ffmpeg is "not recognized"Close PowerShell and open a new window. If it still fails, reinstall and make sure it's added to PATH.
No module named edge_tts or PILRun python -m pip install edge-tts pillow again.
cannot open resourceA Poppins .ttf file is missing from the folder, or its name doesn't match exactly.
JSONDecodeErrorscript.json has curly quotes, a missing comma, or a comma after the last segment. Check it at jsonlint.com.
Voice step fails with an SSL or connection erroredge-tts needs internet access to Microsoft's servers. Try home Wi-Fi without a VPN; some work or school networks block it.
The file saved as make_video.py.txtRename it, or turn on View → File name extensions in File Explorer to see the real name.
Build is very slowNormal on older PCs, which can take 10 minutes or more for a 2-minute video. The zoom step does most of the work. Leave it running.

If you get an error that isn't listed here, paste the full message into Claude or ChatGPT along with this guide. It's usually a one-line fix.

Get the next one

More walkthroughs like this, free.

One plain-English AI briefing a day. Unsubscribe anytime.