Learning how to make AI video used to mean wrestling with five-second clips that fell apart at the edges. That ceiling moved this month. Kling AI launched Kling 4.0 on October 15, 2026, and its own comparison page describes single-pass generation of up to 30 seconds, twice the 15-second window of Kling 3.0. ByteDance’s Seed team released Seedance 2.5 on July 31, 2026, with continuous scenes of up to 30 seconds and generated audio.
Longer clips fix one problem and expose another. When a model hands you 30 seconds in one go, the hard part shifts from generating footage to directing it. You have to keep the same face across shots, match frame rates between tools, and cut everything together so it plays like one piece. This tutorial shows how to make AI video from first idea to finished export in 13 steps and about 45 minutes of hands-on time.
You will write a shot list, turn it into prompts with a short Python script, run text to video AI drafts, upgrade the keepers with image to video AI, and assemble the result with FFmpeg. If you still need to choose a tool before you start, read our ranking of the best AI video generators first. The workflow below works with any model that exports an MP4.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What You Will Build and What You Need
The project is a vertical short film called lighthouse-keeper. It has three shots, a crossfade between each, a music bed, and loudness set for social platforms. The sample runs 15 seconds, and the same folder scales to 60 seconds when you add more entries to the shot list. Every script in this guide was run end to end before publication, using synthetic test clips in place of paid generations, so the commands and outputs you see are real.
Most guides on how to make AI video stop at pasting one sentence into a web form. That produces a clip, not a video. The difference is a repeatable pipeline: a written plan, prompts built from that plan, consistent reference images, clips conformed to one format, and a final pass that checks the export against your target. The scripts here are small on purpose, so you can read all of them in a few minutes and change what you need.
| Steps | What you do | Active time |
|---|---|---|
| 1-2 | Write the brief, set up the folder and tools | 5 min |
| 3 | Pick a generator for each shot | 3 min |
| 4 | Write the shot list in YAML | 5 min |
| 5 | Generate prompts with a script | 4 min |
| 6-7 | Make reference stills, run text-to-video drafts | 9 min |
| 8 | Re-render keepers with image-to-video | 7 min |
| 9-10 | Chain shots from last frames, normalize clips | 4 min |
| 11-12 | Stitch, add music, fix loudness | 5 min |
| 13 | Quality check and export | 3 min |
Those times cover your own work. Render queues add more, and they vary by service and by how busy the service is that hour. Plan on running the generation steps while you do something else.
Prerequisites and versions
- Python 3.11 or newer (the scripts were tested on Python 3.12)
- FFmpeg 6.0 or newer, which includes
ffprobe(tested on FFmpeg 6.1) - PyYAML 6.0 or newer, installed with pip
- Windows 11, macOS 14 or newer, or Ubuntu 22.04 or newer, any terminal
- About 8 GB of free disk space for raw and conformed clips
- At least one AI video generator account (see below), plus an image generator for reference stills
- A music track you have the rights to use, saved as MP3 or WAV (optional, but the assembly script accepts one)
Accounts and credits
You do not need to pay for everything on day one. Third-party pricing pages listed Kling’s Basic tier as free in October 2026, though they did not agree on how many credits it includes. Dreamina, the CapCut-hosted home of Seedance, advertised a first month of its Basic plan for $0.29 to eligible US users, renewing at $15 per month. Google lists Veo 3.1 inside Flow, and pricing trackers showed the Google AI Pro plan at $19.99 per month. Offers like these change often, so confirm what your plan includes before you queue 20 generations.
Steps 1 and 2: Write the Brief and Set Up the Folder
Step 1: write a five-line brief. Open a text file and answer five questions. Where will this play (a vertical feed, a landscape site, a presentation)? How long is the finished piece? What is the story in one sentence? What mood and look should every shot share? What must never change from shot to shot, such as a face, a jacket, or a color palette?
The last question matters most when you are learning how to make AI video that holds together. Every AI video generator treats each request as a fresh roll of the dice, so anything you do not pin down will drift. For our project the brief reads: vertical feed, 15 seconds, a lighthouse keeper spots something on the horizon and smiles, muted teal and amber film look, same man in a yellow rain jacket throughout.
Step 2: create the project folder and install the tools. The folder layout separates raw generations from conformed clips, so you can always redo a later step without regenerating anything. Run the block that matches your system.
# macOS (Homebrew)
brew install ffmpeg [email protected]
# Ubuntu / Debian
sudo apt update && sudo apt install -y ffmpeg python3 python3-venv python3-pip
# Windows 11 (PowerShell)
winget install Gyan.FFmpeg
winget install Python.Python.3.12
# Project folder and Python environment (all systems)
mkdir ai-video-kit && cd ai-video-kit
mkdir raw norm refs prompts out music
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "pyyaml>=6.0"
# Verify
ffmpeg -version | head -1
ffprobe -version | head -1
python3 --version
You should see version lines similar to the output below. If ffmpeg reports a version older than 6.0, upgrade before continuing, because the crossfade filters used later behave differently in very old builds.
ffmpeg version 6.1.1-3ubuntu5 Copyright (c) 2000-2023 the FFmpeg developers
ffprobe version 6.1.1-3ubuntu5 Copyright (c) 2007-2023 the FFmpeg developers
Python 3.12.3
Each folder has one job. raw/ holds exactly what the generators gave you, untouched. norm/ holds the same clips conformed to one size and frame rate. refs/ holds reference stills and extracted last frames, prompts/ holds the generated prompt files, out/ holds the final export, and music/ holds your audio bed. Keep raw files forever. Storage is cheap, and regenerating a clip you already paid for is not.
Step 3: Pick a Generator for Each Shot
No single model wins every shot, and you do not have to pick one for the whole film. A sensible setup is one main generator plus one backup for the shots the main one fumbles. The table lists the newest model from each major line as of October 22, 2026, using only what the makers and their pricing pages state.
| Model | Newest verified detail | Strength, per the maker | Where you run it |
|---|---|---|---|
| Kling 4.0 | Officially launched October 15, 2026, replacing Kling 3.0 as the newest generation | Up to 30 seconds in one pass, up to 15 combined reference assets, finer keyframe control | Kling AI (Kuaishou) |
| Seedance 2.5 | Advertised on Dreamina alongside Seedance 2.0 | Video and audio generation, continuous scenes up to 30 seconds, multimodal references | Dreamina (CapCut), also hosted by Runway |
| Veo 3.1 | Google DeepMind’s current Veo model | Text-to-video, image-to-video, text-to-audio+video, realistic physics, first and last frame, scene extension | Google Flow and eligible Google AI plans |
| Runway Gen-4.5 | Runway’s own flagship, requires the Standard plan or higher | Text-to-video and image-to-video at 12 credits per second | Runway web and Runway Dev API |
| Luma Ray3.2 | Released June 2026, replaced the older Dream Machine and Ray2 models | Frame-by-frame control, continuity and cinematic direction | Luma app |
Read the Veo 3.1 row on Google DeepMind’s Veo page if you want the maker’s own feature list. Two details from that list shape the workflow below. First, Veo supports first-and-last-frame control, which lets you pin both ends of a clip. Second, it generates audio together with video, which saves a step when a shot depends on sound.
One naming trap to avoid: if a tutorial tells you to open Dream Machine, it predates June 2026. Luma’s own information page says the Dream Machine name is deprecated and replaced by Ray, so search for Ray3.2 instead. Hosting platforms also lag the model makers. Runway’s pricing page, for example, listed Kling 3.0 and Seedance 2.5 among its hosted models, not Kling 4.0, even after the Kling 4.0 launch. Check the version label on the generation screen before you spend credits.
Text-to-video versus image-to-video
Text to video AI builds a whole scene from your words. Image to video AI starts from a still you supply and animates it. The first gives the model freedom, the second gives you control. For a film with a recurring character, the second wins, because the still carries the face, clothing and light that a sentence can only approximate.
This guide uses both in sequence. Cheap text-to-video drafts test the idea and the camera move. Once a shot works, you make or pick a still that matches it and re-render the keeper with image-to-video. If you want a wider comparison of tools built for the first mode, our guide to text-to-video generator tools covers more of the field than this tutorial needs.
Match the job to the model like this. For shots that depend on sound, such as footsteps, dialogue or crashing waves, start with a model that generates audio alongside video, which covers Veo 3.1 and Seedance 2.5 in the table. For long unbroken takes, start with Kling 4.0 or Seedance 2.5. For tight control over camera and continuity, try Luma Ray3.2 or Runway Gen-4.5. Write your choice next to each shot in the next step.
Step 4: Write the Shot List in YAML
A shot list is a plain file that says what happens, how the camera moves, where it happens, and what you should hear. YAML works well because it is readable, it diffs cleanly, and Python parses it in one line. Create shots.yaml in the project folder with the content below.
project: lighthouse-keeper
format:
width: 1080
height: 1920
fps: 24
style: "35mm film look, soft grain, muted teal and amber palette, overcast coastal light"
character: "a weathered lighthouse keeper in his sixties, grey beard, yellow rain jacket, wool cap"
shots:
- id: s01
duration: 5
camera: "slow push-in, eye level"
subject: "climbing a spiral staircase inside a lighthouse"
action: "he pauses, wipes his hands on a rag, then keeps climbing"
setting: "narrow whitewashed stairwell, small round windows with sea light"
audio: "boots on iron steps, distant waves, a faint gull"
- id: s02
duration: 5
camera: "static wide shot from the gallery railing"
subject: "stepping onto the outdoor gallery at dawn"
action: "he lifts a brass telescope and scans the horizon"
setting: "storm clouds breaking over a grey sea, wet railing"
audio: "wind, rolling surf, a metal creak"
- id: s03
duration: 6
camera: "handheld close-up, shallow depth of field"
subject: "in close-up as he lowers the telescope"
action: "his eyes widen, then he smiles and nods once"
setting: "warm light catching the rain on his jacket"
audio: "wind drops, one soft exhale, low strings begin"
Three decisions in this file deserve a closer look. First, the character line sits at the top and gets pasted into every text-to-video prompt word for word. Repeating identical wording is the cheapest consistency trick there is. Second, each shot holds one action. A shot where the keeper climbs, pauses, opens a door and turns around will fail at least one of those beats in most generators.
Third, durations are short, 5 to 6 seconds, even though two of the five models in the table can go to 30 seconds. Short shots cost less per retake, give you more cut points, and let you swap one weak shot without touching the rest. Use the 30-second ceiling for the rare shot that truly needs an unbroken move, like a long walk-and-talk or a slow reveal.
The audio field does double duty. For models that generate sound, it feeds the prompt. For models that do not, it becomes your checklist when you add effects and music later. Describe sound the way a sound editor would: the thing making the noise and its character, not a vague mood.
Step 5: Build Your AI Video Prompts With a Script
Good AI video prompts follow a fixed order: camera, subject, action, setting, style and sound. Models read the opening words with the most weight, so leading with the camera move tends to get it obeyed. Typing that structure by hand for 12 shots invites typos and inconsistencies. A short script removes both problems.
Save the following as build_prompts.py. It reads shots.yaml, refuses to run if a field is missing, writes two prompt files per shot, and prints a cost estimate for the whole film. The two variants matter. The t2v prompt describes the full scene. The i2v prompt describes only motion, because the reference image already supplies the subject and setting.
#!/usr/bin/env python3
"""Turn shots.yaml into text-to-video and image-to-video prompts."""
import sys
from pathlib import Path
import yaml
RATE_USD_PER_SEC = float(sys.argv[1]) if len(sys.argv) > 1 else 0.0068
MAX_WORDS = 110
REQUIRED = ("id", "duration", "camera", "subject", "action", "setting", "audio")
cfg = yaml.safe_load(Path("shots.yaml").read_text())
out = Path("prompts")
out.mkdir(exist_ok=True)
total = 0
report = ["# Prompt sheet: " + cfg["project"], ""]
for shot in cfg["shots"]:
missing = [k for k in REQUIRED if not shot.get(k)]
if missing:
sys.exit(f"{shot.get('id', '?')}: missing {', '.join(missing)}")
t2v = (
f"{shot['camera'].capitalize()}. {cfg['character'].capitalize()}: "
f"{shot['subject']}. {shot['action'].capitalize()}. "
f"{shot['setting'].capitalize()}. Style: {cfg['style']}. "
f"Sound: {shot['audio']}."
)
i2v = (
f"{shot['action'].capitalize()}. Camera: {shot['camera']}. "
f"Keep the subject, clothing and lighting from the reference image. "
f"Sound: {shot['audio']}."
)
for name, text in (("t2v", t2v), ("i2v", i2v)):
words = len(text.split())
flag = " <-- long" if words > MAX_WORDS else ""
(out / f"{shot['id']}.{name}.txt").write_text(text + "\n")
report.append(f"## {shot['id']} {name} ({words} words){flag}")
report.append(text)
report.append("")
total += shot["duration"]
cost = total * RATE_USD_PER_SEC
report.append(f"Total runtime: {total}s. One pass at ${RATE_USD_PER_SEC}/s = ${cost:.2f}. "
f"Budget for 3 takes per shot: ${cost * 3:.2f}.")
Path("prompts/prompts.md").write_text("\n".join(report) + "\n")
print("\n".join(report))
Run it with the per-second rate you expect to pay. The default is the roughly $0.0068 per second effective 720p rate that Dreamina publishes for Seedance 2.5, and you can pass any number. Here the argument is the same value written out.
python3 build_prompts.py 0.0068
The output starts with a prompt sheet. Each prompt shows its word count, and the script flags anything over 110 words, since long prompts tend to bury the camera instruction.
# Prompt sheet: lighthouse-keeper
## s01 t2v (69 words)
Slow push-in, eye level. A weathered lighthouse keeper in his sixties, grey beard, yellow rain jacket, wool cap: climbing a spiral staircase inside a lighthouse. He pauses, wipes his hands on a rag, then keeps climbing. Narrow whitewashed stairwell, small round windows with sea light. Style: 35mm film look, soft grain, muted teal and amber palette, overcast coastal light. Sound: boots on iron steps, distant waves, a faint gull.
...
Total runtime: 16s. One pass at $0.0068/s = $0.11. Budget for 3 takes per shot: $0.33.
Note the budget line at the end. The total counts the planned shot durations (5 + 5 + 6 = 16 seconds), while the finished film runs about 15 seconds because crossfades overlap. Multiplying by three takes per shot is the habit that keeps your estimates honest, and the cost section later shows why.
Steps 6 and 7: Make Reference Stills, Then Run Text-to-Video Drafts
Step 6: create reference stills. Use any image generator to make one clean portrait of your character, then one start frame per shot with the same character in the right setting. Paste the character line from your YAML into every image prompt. Save the files into refs/ as s01_start.png, s02_start.png and so on. If you want a deeper walkthrough of building an image pipeline with quality checks, our AI image editing workflow guide covers batching and review.
Pick stills the way a cinematographer picks a frame. Leave empty space in the direction of travel, avoid hands near the edges, and skip anything with readable text, which video models still garble. A start frame that already looks like the film you want is worth more than any prompt wording.
Step 7: run text-to-video drafts. Open your main generator and paste the contents of each .t2v.txt file. Choose the lowest resolution and the shortest duration the tool offers, set the aspect ratio to 9:16, and generate two or three takes per shot. The goal at this stage is to answer three questions cheaply. Does the camera move as written? Does the action happen in the right order? Does the character look roughly right?
Save each take with a letter suffix, like s01_a.mp4, s01_b.mp4, then copy your pick to raw/s01.mp4. Keeping the suffixed originals gives you a record of what you rejected and why. Score every take against this short checklist before you keep it:
- Face and clothing: does the character match the previous shot?
- Hands and props: count fingers, check that held objects keep their shape
- Physics: do water, cloth and footsteps move the way they should?
- Camera: did the push-in, pan or static frame come out as written?
- Artifacts: flicker, melting edges, sudden cuts inside the clip, garbled signage
Do not polish at this stage. Treat your first AI video prompts as hypotheses, not finished work. A draft that gets the blocking right and the face wrong is a good draft, because the next step fixes the face. A draft with the wrong action needs a rewritten prompt, and that is exactly what drafts are for. Edit the YAML, rerun the script, and try again. Revising AI video prompts in one file beats retyping them in five browser tabs.
Step 8: Image to Video AI for the Final Takes
Now swap to the reference stills. Upload s01_start.png to your generator’s image-to-video mode, paste the matching .i2v.txt prompt, and raise the resolution to the level you plan to publish at. This is where the character locks in. Runway’s primer on how AI video generation works explains the tradeoff plainly: “Text-to-video builds the entire scene from a written description, giving the model full creative latitude over what it invents.” Image-to-video takes that latitude away on purpose.
The image-to-video prompt should read like a director’s note to an actor who is already on set. Describe what moves and how the camera behaves. Do not re-describe the man, his jacket or the staircase, because repeating those details can pull the model away from the pixels you gave it. That is why the script builds a separate i2v prompt with a line telling the model to keep the subject, clothing and lighting from the reference image.
If your generator supports an end frame, use it for shots that need a precise landing. Veo 3.1 lists first-and-last-frame control as one of its features, and Kling 4.0 is described as offering more keyframe control than 3.0, with some reports putting the limit at 10 keyframes. Supplying the next shot’s start frame as this shot’s end frame is a clean way to make two clips meet.
Generate one or two takes per shot and compare them to your draft. A good result keeps the framing of the still, moves the way the prompt says, and holds identity for the whole clip. A common failure is a slow zoom the model adds on its own. If you see it, add static camera or locked-off frame to the prompt and rerun.
Download each approved clip and overwrite the draft in raw/, keeping names as s01.mp4, s02.mp4 and s03.mp4. Check one thing before moving on: does the clip have an audio track? Some generators export silent video by default or by plan. The next steps handle both cases, but you should know which one you have.
Seedance fans have one more option. Runway’s pricing page announced that Seedance 2.5 now supports 1080p on that platform, which gives you a second route to the same model if your Dreamina quota runs out. The model is the same family either way, though pricing, queue times and available controls differ by host.
Steps 9 and 10: Chain Shots From Last Frames and Normalize Clips
Step 9: chain shots when the action continues. If shot two starts where shot one ends, such as the keeper stepping out of the stairwell onto the gallery, use the last frame of the approved clip as the start frame for the next generation. That guarantees the characters, light and position match at the cut. Save the following as last_frame.sh and make it executable.
#!/usr/bin/env bash
# Usage: ./last_frame.sh raw/s01.mp4 refs/s02_start.png
set -euo pipefail
ffmpeg -y -loglevel error -sseof -0.1 -i "$1" -update 1 -frames:v 1 -q:v 2 "$2"
echo "saved $2"
chmod +x last_frame.sh
./last_frame.sh raw/s01.mp4 refs/s02_start.png
The script seeks 0.1 seconds before the end of the file and writes one high-quality PNG. Upload that image as the start frame for shot two. Chain sparingly. Each generation adds small errors, and by the fourth or fifth link in a chain the face, color or sharpness has usually drifted. Use chaining for two or three consecutive shots, then cut to a fresh reference still to reset.
Step 10: normalize every clip. Generators export at different sizes, frame rates and audio layouts. One model might hand you 24 fps with a stereo track, another 30 fps with none, a third landscape when you wanted vertical. Mixed inputs make crossfades fail or stutter, so conform everything first with normalize.py.
#!/usr/bin/env python3
"""Conform every clip in raw/ to one size, frame rate and audio layout."""
import json
import subprocess
from pathlib import Path
import yaml
cfg = yaml.safe_load(Path("shots.yaml").read_text())["format"]
W, H, FPS = cfg["width"], cfg["height"], cfg["fps"]
Path("norm").mkdir(exist_ok=True)
def has_audio(path):
probe = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "a", "-show_entries",
"stream=index", "-of", "json", str(path)],
capture_output=True, text=True, check=True)
return bool(json.loads(probe.stdout)["streams"])
for clip in sorted(Path("raw").glob("*.mp4")):
target = Path("norm") / clip.name
vf = (f"scale={W}:{H}:force_original_aspect_ratio=increase,"
f"crop={W}:{H},fps={FPS},format=yuv420p,setsar=1")
cmd = ["ffmpeg", "-y", "-loglevel", "error", "-i", str(clip)]
if not has_audio(clip):
cmd += ["-f", "lavfi", "-i", "anullsrc=r=48000:cl=stereo", "-shortest"]
cmd += ["-vf", vf, "-c:v", "libx264", "-crf", "16", "-preset", "medium",
"-c:a", "aac", "-ar", "48000", "-ac", "2", "-b:a", "192k",
"-movflags", "+faststart", str(target)]
subprocess.run(cmd, check=True)
print("normalized", target)
The video filter scales each clip until it fills the target frame, then crops the overflow, forces the frame rate, sets the pixel format that every player understands, and resets the sample aspect ratio. If a clip has no audio, the script adds a silent stereo track so that the later audio crossfade has something to work with. Crop beats padding for most social formats, but if your subject sits near an edge, switch the filter to pad with black bars instead.
python3 normalize.py
normalized norm/s01.mp4
normalized norm/s02.mp4
normalized norm/s03.mp4
In testing, a 1280×720 landscape clip at 25 fps and a 720×1280 clip at 30 fps both came out of this step as 1080×1920 at 24 fps. If your source clips are already 1080×1920, nothing visible changes, and the re-encode at CRF 16 stays visually lossless for practical purposes.
Steps 11 and 12: Stitch, Score and Fix Loudness
Step 11: stitch with crossfades. Hard cuts suit action and dialogue. Short dissolves suit atmospheric pieces like ours, where time is passing. The assembly script chains FFmpeg’s xfade filter across all clips in norm/ in filename order, with a 0.5-second fade. It reads each clip’s real duration with ffprobe so the offsets stay right even when a generator returns a clip slightly off from 5 seconds.
#!/usr/bin/env python3
"""Crossfade normalized clips, mix optional music, normalize loudness."""
import subprocess
import sys
from pathlib import Path
FADE = 0.5
music = Path(sys.argv[1]) if len(sys.argv) > 1 else None
clips = sorted(Path("norm").glob("*.mp4"))
if len(clips) < 2:
sys.exit("need at least two clips in norm/")
def duration(path):
out = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=nw=1:nk=1", str(path)],
capture_output=True, text=True, check=True)
return float(out.stdout)
durs = [duration(c) for c in clips]
cmd = ["ffmpeg", "-y", "-loglevel", "error"]
for c in clips:
cmd += ["-i", str(c)]
if music:
cmd += ["-stream_loop", "-1", "-i", str(music)]
parts, vlast, alast, elapsed = [], "0:v", "0:a", durs[0]
for i in range(1, len(clips)):
offset = round(elapsed - FADE, 3)
parts.append(f"[{vlast}][{i}:v]xfade=transition=fade:duration={FADE}:offset={offset}[v{i}]")
parts.append(f"[{alast}][{i}:a]acrossfade=d={FADE}[a{i}]")
vlast, alast = f"v{i}", f"a{i}"
elapsed += durs[i] - FADE
if music:
m = len(clips)
parts.append(f"[{m}:a]volume=0.22,atrim=0:{elapsed:.3f},asetpts=PTS-STARTPTS[bed]")
parts.append(f"[{alast}][bed]amix=inputs=2:duration=first:normalize=0[mix]")
alast = "mix"
parts.append(f"[{alast}]loudnorm=I=-14:TP=-1.5:LRA=11[aout]")
cmd += ["-filter_complex", ";".join(parts), "-map", f"[{vlast}]", "-map", "[aout]",
"-c:v", "libx264", "-crf", "17", "-preset", "slow", "-pix_fmt", "yuv420p",
"-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", "out/final.mp4"]
Path("out").mkdir(exist_ok=True)
subprocess.run(cmd, check=True)
print(f"wrote out/final.mp4 ({elapsed:.1f}s expected)")
The offset math is the part that trips people up. Each crossfade starts half a second before the previous clip ends, so the running offset is the total elapsed time minus the fade length. Every crossfade also shortens the film by half a second. Three clips of 5, 5 and 6 seconds with two 0.5-second fades therefore total 15 seconds, which matches the script’s own readout.
Step 12: add music and fix loudness. Pass an audio file as the first argument and the script mixes it under the clip audio at 22 percent volume, trims it to the film length, and loops it if it is too short. Without an argument, it skips the music and just normalizes the clip audio. The last filter, loudnorm, aims for -14 LUFS with a true-peak ceiling of -1.5 dB, a common target for streaming and social platforms.
python3 assemble.py music/bed.mp3
# or, with no music bed:
python3 assemble.py
wrote out/final.mp4 (15.0s expected)
Keep the music quiet and simple. AI-generated ambience and effects already fill the frequency range, so a loud, busy track fights them. If a shot has important sound design, like the soft exhale in shot three, lower the bed further or cut it in with a short fade. Always confirm you hold the rights to the track you use, whether you licensed it, composed it or generated it with a service whose terms allow publishing.
If you need more control than one global music level offers, the next move is a sidechain compressor that ducks the music whenever the clip audio gets loud. FFmpeg ships sidechaincompress for exactly this, and it is a small change in the same filter graph once you are comfortable with the structure above.
Step 13: Quality Check and Export
Eyes are the final judge, but a script catches what eyes skip. Save qc.py. It reads the export with ffprobe and compares size, frame rate, pixel format and audio presence against the format block in your YAML. It exits with an error code if anything is off, so you can wire it into a larger build later.
#!/usr/bin/env python3
"""Fail loudly if the export does not match the shots.yaml format."""
import json
import subprocess
import sys
from pathlib import Path
import yaml
cfg = yaml.safe_load(Path("shots.yaml").read_text())["format"]
target = Path(sys.argv[1] if len(sys.argv) > 1 else "out/final.mp4")
raw = subprocess.run(
["ffprobe", "-v", "error", "-show_streams", "-show_format", "-of", "json", str(target)],
capture_output=True, text=True, check=True).stdout
info = json.loads(raw)
video = next(s for s in info["streams"] if s["codec_type"] == "video")
audio = [s for s in info["streams"] if s["codec_type"] == "audio"]
num, den = map(int, video["r_frame_rate"].split("/"))
checks = {
"size": (f"{video['width']}x{video['height']}", f"{cfg['width']}x{cfg['height']}"),
"fps": (round(num / den), cfg["fps"]),
"audio track": (bool(audio), True),
"pixel format": (video["pix_fmt"], "yuv420p"),
}
failed = 0
for name, (got, want) in checks.items():
ok = got == want
failed += not ok
print(f"{'PASS' if ok else 'FAIL'} {name}: got {got}, want {want}")
print(f"duration: {float(info['format']['duration']):.2f}s")
sys.exit(1 if failed else 0)
python3 qc.py
PASS size: got 1080x1920, want 1080x1920
PASS fps: got 24, want 24
PASS audio track: got True, want True
PASS pixel format: got yuv420p, want yuv420p
duration: 15.10s
Next, measure loudness. The ebur128 filter prints integrated loudness without writing a file, and it should land close to the -14 LUFS target.
ffmpeg -hide_banner -nostats -i out/final.mp4 -af ebur128=peak=true -f null - 2>&1 | grep -A8 Summary
Integrated loudness:
I: -14.1 LUFS
Threshold: -24.3 LUFS
Loudness range:
LRA: 7.9 LU
Then watch the film once on your phone with the sound on, because that is where most people will see it. Look for the usual AI tells at the cut points: a face that changes slightly between shots, a hand with the wrong number of fingers, lighting that jumps. If one shot fails, regenerate that shot only, drop the new file into raw/, and rerun steps 10 through 13. This is the payoff for keeping every stage separate.
Before you publish, label the clip. Most large platforms ask creators to disclose realistic synthetic footage, and some viewers feel misled if you do not. Add a line in the caption or description, keep your prompt sheet and reference stills as a provenance record, and avoid generating real, identifiable people without their consent. Platform rules change, so check the current policy where you post.
What 60 Seconds of AI Video Really Costs
Prices move weekly in this market, so treat the figures below as a snapshot of what was listed in October 2026. They come from Dreamina’s published effective rates for Seedance 2.5, from a third-party tracker (useapi.net) for Google’s Veo 3.1 Lite variant, and from Runway’s credit rates. The table assumes a 60-second finished film built from 5-second shots, with one take each in the second column and three takes each in the third.
| Route | Rate used | 60 s, one take | 180 s generated (3 takes) |
|---|---|---|---|
| Seedance 2.5 on Dreamina, 720p | About $0.0068 per second | Not listed | Not listed |
| Veo 3.1 Lite, 720p | $0.05 per second | $3.00 | $9.00 |
| Veo 3.1 Lite, 1080p | $0.08 per second | $4.80 | Not listed |
| Runway Gen-4.5 | 12 credits per second | 720 credits | 2,160 credits |
Two cautions apply. Dreamina’s published rate is an effective per-second figure at 720p, and its Seedance pricing page also carries the $0.29 first-month Basic offer for eligible US users, which is an introductory deal, not a permanent price. Runway’s Standard plan includes 625 monthly credits and Gen-4.5 costs 12 credits per second, which means a single pass over a 60-second film would not fit inside one month of that plan, and three takes per shot would take well over three months of it.
Runway’s developer platform reportedly prices credits at $0.01 each, which would put one 60-second pass at about $7.20 and a three-take run at about $21.60. Treat that as a rough guide until you check the current rate. Runway’s help article on creating with Gen-4.5 is the place to confirm credit costs for the plan you use.
The numbers teach a few practical lessons about any AI video generator you pick. Drafting on a cheap, fast route and finalizing on a better one cuts the bill without cutting quality. Resolution is the biggest multiplier, so render drafts low and only the approved takes high. And the takes multiplier is real: expect to generate roughly three seconds for every second that survives, more for complex action and less for static shots.
Subscription value also depends on how you work. If you generate in bursts once a month, a short paid plan or a pay-as-you-go API route can beat a standing subscription. If you generate daily, a plan with a monthly credit pool usually wins. Either way, log every generation with its model, settings and cost in a spreadsheet for the first month, and you will know within weeks which tool earns its place.
Common Pitfalls That Waste Credits
These six mistakes account for most wasted generations in a first project. Each one is cheap to avoid once you know it.
- Describing too many actions in one shot. Models follow the first beat and blur the rest. One action per shot, and use the shot list to split anything bigger.
- Changing the character wording between prompts. A grey beard in one prompt and silver stubble in the next gives you two different men. Paste the same
characterline every time, which the script does for you. - Rendering finals before the draft works. High resolution multiplies cost without fixing a bad idea. Settle the action in cheap drafts first.
- Long chains of last-frame handoffs. Quality erodes with each link. Reset with a fresh reference still every two or three shots.
- Mixing frame rates and sizes in the final cut. Crossfades between a 30 fps and a 24 fps clip stutter. Always run
normalize.pybefore assembling. - Trusting a tutorial that names an old model. The field turns over every few months. Check the version on the generation screen, and check dates on any guide, including this one.
A seventh habit is worth adopting too: write down what failed. A single line like take b drifted to a younger face after 3 seconds teaches you which model, shot length and prompt style to avoid next time, and it takes five seconds to type.
Troubleshooting: 10 Problems and Fixes
- Symptom: the face changes between shots. Cause: text-only prompts. Fix: re-render the shot with image-to-video from a reference still, and paste the identical character line into every prompt.
- Symptom:
ffmpeg: command not found. Cause: FFmpeg is not on your PATH. Fix: reinstall with the command for your system in step 2, then open a new terminal window so the PATH refreshes. - Symptom:
ModuleNotFoundError: No module named yaml. Cause: PyYAML is missing or the virtual environment is not active. Fix: activate.venvand runpip install pyyaml. - Symptom: the build script exits with
s02: missing audio. Cause: a shot lacks a required field. Fix: add the field inshots.yaml. The check is deliberate, so empty prompts never reach a paid generator. - Symptom: the crossfade fails with an
xfadeerror about mismatched sizes or frame rates. Cause: you skipped normalization. Fix: runpython3 normalize.pyand make sureassemble.pyreads fromnorm/, notraw/. - Symptom: no sound in the final file. Cause: the clip had no audio and no music was passed. Fix: normalization adds silence, so the track exists but is empty. Pass a music file to
assemble.py, or add effects in an editor. - Symptom: the video freezes or goes black at a transition. Cause: a clip shorter than the 0.5-second fade, or an offset computed from a wrong duration. Fix: confirm every clip runs at least 2 seconds with
ffprobe, and rerun normalization. - Symptom: the last frame looks blurry or half-faded. Cause: some generators end with a fade. Fix: change
-sseof -0.1to-sseof -0.5inlast_frame.shto grab a cleaner frame. - Symptom: the generator ignores your camera move. Cause: the camera instruction sits too late in the prompt, or it contradicts the action. Fix: keep it first, use one move per shot, and add locked-off frame for static shots.
- Symptom:
qc.pyreports FAIL on fps. Cause: the export frame rate differs from the YAML. Fix: setfpsto the value you want in theformatblock, rerun normalization, then reassemble.
If a problem persists, isolate the stage. Play the file in raw/, then in norm/, then in out/. The first stage where it breaks tells you which script to inspect, and because each stage writes to its own folder, you never lose earlier work.
Advanced Tips for Longer, Better AI Video
Script the whole run. Once your shots are approved, a single shell script rebuilds the film in seconds. This matters when you swap one shot at midnight and want a new export without retyping five commands.
#!/usr/bin/env bash
# run.sh: rebuild everything after any change
set -euo pipefail
python3 build_prompts.py 0.0068 > /dev/null
python3 normalize.py
python3 assemble.py "${1:-}"
python3 qc.py
Pass a music path as the first argument, or leave it empty. If you only remember one habit from this guide on how to make AI video, make it this one: rebuild from the plan, not from memory. The script stops at the first failure, which is what you want: a failed quality check should block the export.
Use reference assets for identity. Kling 4.0 accepts up to 15 combined reference assets in one generation, according to Kling’s own comparison material. Feed it the character portrait, the location still and a prop photo together instead of relying on one image. Seedance 2.5 and Veo 3.1 also advertise multimodal or ingredient-style references, so test the same idea there.
Go audio first for dialogue. If a shot depends on speech, record or generate the voice line before the video, and prompt for matching lip and body motion. Models that generate audio and video together can do this in one pass, but a prerecorded line gives you control over timing and tone that a prompt cannot.
Match color between tools. Different generators bias toward different looks. A light pass with FFmpeg’s eq or colorbalance filters in normalize.py, applied per shot, pulls them closer. A subtle film grain over the entire export, added in the final encode, hides small texture differences between sources.
Upscale at the end, not the start. Several current models render natively at high resolution, and Kling states that both 3.0 and 4.0 support 4K output. When your source is already high resolution, skip external upscalers and spend that budget on retakes. Use upscaling only for older or lower-resolution clips you want to rescue.
Route by shot, not by film. Add a model field to each YAML entry and let the prompt sheet print it. A staircase shot might go to a physics-strong model, a face close-up to the one that holds identity best, and the ocean to whichever handles water. Runway also hosts several outside models in the same interface and states that its commercial-use commitments extend to those hosted models, which makes it a convenient place to compare them side by side.
For ongoing coverage of the models behind these tools, follow our AI and machine learning section, where launches like Kling 4.0 get tracked as they land.
Frequently Asked Questions
What is the fastest way to learn how to make AI video as a beginner? Write a one-sentence story, pick one generator, and make a single 5-second text-to-video clip at low resolution. Once that works, add a reference still and rerun it as image-to-video. The 13-step pipeline above scales that habit into a finished film without changing the core idea.
Is text to video AI or image to video AI better? Neither wins everywhere. Text to video AI is faster for exploring ideas and camera moves. Image to video AI gives far more control over identity, wardrobe and composition. Most finished work uses text-to-video for drafts and image-to-video for final takes.
Which AI video generator is newest right now? As of October 22, 2026, Kling 4.0 is the newest launch among the major lines. Seedance 2.5 is the newest Seedance model, and Veo 3.1 is Google’s current Veo model. Runway’s own flagship remains Gen-4.5. Always confirm on the generation screen, because hosting sites can lag.
How long can an AI video clip be? It depends on the model and plan. Kling 4.0 and Seedance 2.5 both describe single-pass clips of up to 30 seconds, while Kling 3.0 was limited to 15. Many workflows still use 4 to 8 second shots because they are cheaper to retake and easier to cut.
Do I need to code to make AI videos? No. You can generate every clip in a web interface and cut them in any video editor. The Python and FFmpeg scripts here exist to make the process repeatable, catch format mistakes, and keep costs predictable. They are optional but useful once you make more than one video.
How much does it cost to make a 60-second AI video? With three takes per shot, the listed rates in this guide work out to roughly $1 to $15 of generation for 60 finished seconds on per-second routes, or more than three months of Standard-plan credits on Runway. Resolution and retakes are the main drivers, so draft low and finish high.
Can I use AI-generated video commercially? Terms vary by service and plan. Runway states that its commercial-use commitments cover the third-party models it hosts, and that those providers have committed not to train on customer content. Read the license on the exact plan you pay for, and keep records of prompts and references.
Why do AI video characters change faces between shots? Each generation starts from scratch unless you anchor it. Use identical character wording, supply a reference image, and chain from last frames for consecutive shots. If drift persists, reduce shot length and reset with a fresh reference still.





