Keeping AI characters consistent across a 30-shot short drama: treat them as dependencies, not descriptions
Generate one good AI video shot and you have a demo. Generate the thirty shots of a single vertical-drama episode and you have a production problem. The heroine has a scar over her left eyebrow in shot 3, and by shot 14 it's gone. Her coat changes color between the café and the street. A letter the

Generate one good AI video shot and you have a demo. Generate the thirty shots of a single vertical-drama episode and you have a production problem. The heroine has a scar over her left eyebrow in shot 3, and by shot 14 it's gone. Her coat changes color between the café and the street. A letter the dialogue keeps mentioning never shows up on screen. Each clip looks fine alone, and the episode falls apart.
Over the last few weeks I've been building a self-hosted studio for making these short dramas, and most of the work went into this problem rather than into model calls. This post covers why consistency breaks, the common fixes, and the structure I ended up with: characters, scenes and props become versioned assets that every shot depends on, and a cheap deterministic check runs before any expensive video call.
A vertical short drama is a series of 1–3 minute episodes shot for a phone held upright. Plots are fast and genre-driven (revenge, romance, hidden identity), and every episode ends on a cliffhanger. A single series can run to dozens of episodes, and each episode is dozens of shots. At that volume, small inconsistencies stop being small.
The market numbers explain why people want to produce these cheaply and repeatably. The figures below are from Sensor Tower's public reports. Note their scope: they're estimates of in-app purchase revenue on the App Store and Google Play only, excluding ad revenue, third-party Android stores and web payments.
The same 2025 report credits one fast-growing app's rise partly to localization, multi-language subtitles and frequent releases. Speed of producing versions matters, not just the script.
To be clear, these numbers describe the industry. They have nothing to do with my project; no hit was made with it, and it can't make your series a hit.
idea → LLM: series outline, episode scripts, dialogue → LLM: break each episode into shots (framing, camera, lines) → text-to-image: character, scene and prop reference images → image-to-video: each shot, conditioned on references → TTS: narration and dialogue → edit: cut, sync, export the episode Enter fullscreen mode Exit fullscreen mode Consistency breaks for five reasons:
All of them share one idea: give the model something stronger and more persistent than text.
Pricing and speed vary by vendor and change often, so this is qualitative. High-quality video generation is usually billed per call, per second or per resolution. A single clip takes far longer than an image, with queues at peak times. Calls fail from moderation blocks, timeouts, rate limits, bad reference images, or output that's just wrong. Across dozens of shots, some failures are certain.
So a tool needs three things: async execution, per-shot retries, and catching mistakes before the expensive call.
Instead of re-describing the heroine in every prompt, she becomes an asset:
The workflow mirrors how short dramas are already made, with every stage storing an artifact:
Before any video request, studio/server/prompt_consistency.py runs deterministic rules. No extra model call is made:
If the dialogue mentions the scar and the prompt dropped it, the shot is blocked before you pay for the clip.
The same idea applies to job ordering. The queue runs still-image jobs, such as reference portraits, ahead of video jobs, so a bad reference is fixed first instead of burning video generations.
React UI (studio/src) │ ▼ FastAPI (studio/server) ──▶ PostgreSQL (projects · studio_kv · meta) │ └──────▶ Redis (media job queue) └──▶ MinIO / S3 (inspire · media blobs) Enter fullscreen mode Exit fullscreen mode PostgreSQL holds state. Scripts, assets, shots and versions are structured data, so any stage can be rolled back or re-run without redoing the episode. The Redis queue holds media jobs.
That's the answer to expensive/slow/fails. A separate media worker executes image and video requests; the UI just watches job status over WebSocket. If one shot fails, only that shot is retried.
MinIO / S3 holds files. References, clips and finished episodes go to object storage; the database stores pointers. A single YAML file defines the workflow.
Navigation, stages, tools and UI copy live in studio/config/platform.yaml, so adding or renaming a stage doesn't mean hunting strings across the frontend. Models are pluggable. Text models go through any OpenAI-compatible endpoint, configured per account with a connection test.
Images can also go to your own ComfyUI; if you use LoRA or IP-Adapter there, that stays in your workflow, and the studio just calls it and manages the outputs. Running it git clone https://github.com/charliewwdev/OpenMontage.git && cd OpenMontage cp studio/.env.example.env # add your model keys docker compose -f docker-compose.studio.yml up --build Enter fullscreen mode Exit fullscreen mode Then open http://localhost:8787. The health check is GET /api/health.
Before exposing it publicly, change the MinIO default credentials and turn off demo login ( STUDIO_DEMO_ENABLED ). Model costs are paid directly to the model vendors.
It's v0.1.0, and I'd rather you arrive with accurate expectations.
It's AGPL-3.0. If you offer a modified version as a network service, section 13 requires you to offer its source to users.
I'd really like to hear how others handle cross-shot consistency: reference images, LoRA, IP-Adapter, first/last frame, or something else?
Repo: https://github.com/charliewwdev/OpenMontage · Read-only demo: https://openmontage.dtok.io
References (third-party estimates, accessed November 2026)
Disclosure: I'm the author of the project. I drafted this post with AI assistance and reviewed and edited it myself.