Dev.to · 9 min read

Keeping AI characters consistent across a 30-shot short drama: treat them as dependencies, not descriptions

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 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. Outline What a vertical short drama is. 1–3 minute episodes, 9:16, dozens of episodes per series, a hook in the first seconds. Why it's worth engineering for. Sensor Tower data, with its caveats. The typical generation pipeline. LLM script → text-to-image reference images → image-to-video shots → TTS → edit. Five reasons consistency breaks every call is stateless text is a weak description of a face prompt drift across shots random seeds "consistent within a shot" isn't "consistent across shots" Common fixes and their trade-offs: reference images, LoRA, IP-Adapter-style adapters, first/last-frame control. Expensive, slow, fails. What this means for architecture: async jobs, per-shot retries, catch problems before paying. My approach: characters as dependencies; 7 stages, each producing a stored artifact. The pre-generation check (prompt_consistency.py) and job ordering. Architecture, with each component mapped to a pain point. Deploying it: three commands, plus what Compose does and doesn't include. What it doesn't do well yet. License and links. Short dramas, briefly 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. Why it's worth treating as an engineering problem 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. [1] In Q1 2025, global short-drama app IAP revenue was about $700M, up from $178M in Q1 2024. [1] In the same quarter, ReelShort and DramaBox took roughly $130M and $120M respectively, ranking first and second. [1] The US was the largest single market, at about $350M in Q1 2025, close to half the global total. [2] For full-year 2025, Sensor Tower's State of Mobile 2026 puts short-drama app IAP revenue at $2.98B, up 115% year over year. [3] The same 2025 report credits one fast-growing app's rise partly to localization, multi-language subtitles and frequent releases. [2] 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. The pipeline, and why consistency breaks A typical AI short-drama pipeline: 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 Consistency breaks for five reasons: Every call is amnesiac. The video model doesn't remember the previous shot. Anything not passed in again is up for grabs. Text is a weak description of a face. "Early 20s, long black hair, scar over left brow" fits millions of faces. Prompts drift. When an LLM writes 30 shot prompts, details get paraphrased, shortened or dropped. Seeds. Same prompt, different seed, different person. Within-shot consistency doesn't mean across-shot consistency. Models are getting good at the first; the second is your job. The usual fixes Reference images (image conditioning). Approve one character portrait, then pass it with every shot. This is simple and widely supported. It holds the overall look, but details still slip when angle, expression or lighting change a lot. LoRA fine-tuning. A small adapter is trained per character. The constraint is stronger, but it costs training time and data, and you need control over the model. IP-Adapter-style adapters. The reference image is encoded and injected through attention, with no per-character training. Face-identity variants exist. First/last-frame control. The start and/or end frame of a clip is pinned, which helps continuity between adjacent shots. All of them share one idea: give the model something stronger and more persistent than text. Expensive, slow, and it fails 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. My approach: characters are dependencies, not descriptions Instead of re-describing the heroine in every prompt, she becomes an asset: Characters, scenes and props are each generated first as a reference image. A human approves it once, and version history is kept. Shots reference assets. The reference images are sent with the shot prompt to the video model. Changing an asset bumps its version, so each shot knows which version it was built on, and a rollback or re-run has something to go on. The workflow mirrors how short dramas are already made, with every stage storing an artifact: # Stage Artifact 1 Script genre, aspect ratio, style, synopsis; episode scripts (continue, rewrite, lock) 2 Characters reference portraits (Seedream or your own ComfyUI), versioned 3 Scenes space and lighting, concept image 4 Props key props and materials 5 Storyboard / shots LLM shot breakdown; Seedance generates each shot from asset references 6 Voice narration and dialogue TTS (edge-tts by default, or an OpenAI-compatible TTS) 7 Edit timeline, FFmpeg export A cheap check before the expensive call Before any video request, studio/server/prompt_consistency.py runs deterministic rules. No extra model call is made: every character who speaks in the dialogue must appear in the shot prompt; if a line mentions a visible piece of evidence (a scar, a ring, a letter), the prompt must put it on screen; props the dialogue doesn't mention (phones, screens) shouldn't take over the shot; every @name reference to a character, scene or prop must exist in the asset library. 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. Architecture React UI (studio/src) │ ▼ FastAPI (studio/server) ──▶ PostgreSQL (projects · studio_kv · meta) │ └──────▶ Redis (media job queue) └──▶ MinIO / S3 (inspire · media blobs) 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 Then open http://localhost:8787. The health check is GET /api/health. Included in docker-compose.studio.yml? Studio (UI + API) and media worker ✅ built from the repo PostgreSQL 16, Redis 7, MinIO ✅ LLM API key ❌ bring your own Image + video generation ❌ Seedream / Seedance keys (paid third-party APIs), or your own ComfyUI for images TTS edge-tts by default, no key 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. What it doesn't do well yet Cost. Video and images rely on paid Seedance/Seedream APIs. Try three to five shots before running a whole episode. Reference images aren't a silver bullet. With big changes in angle, expression or lighting, details can still drift. Asset approval and the pre-check catch a lot, but not everything. Invalidation is coarse. After a script edit, which downstream shots need redoing is tracked by stage status and versions. Line-level dependency tracking ("this line affects these two shots") isn't built yet. Language. The UI is mostly Chinese today. An English UI has just been merged to main but isn't on the public demo yet. The built-in edge-tts character voices are Chinese, so other-language dubbing needs an external TTS, and some internal prompts and docs are still Chinese. The QC flags are noisy. They sometimes mark usable clips. Setup cost. You need your own server and model keys. It's v0.1.0, and I'd rather you arrive with accurate expectations. License and links 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) Sensor Tower, State of Short Drama Apps 2025: https://sensortower.com/blog/state-of-short-drama-apps-2025 Sensor Tower, State of Short Drama Apps Overseas 2025 (PDF): https://downloads.ctfassets.net/vfkpgemp7ek3/6cNlWw8314o8FX2cNa1OlE/14111704149f364f862ad9650f395bc5/State_of_Short_Drama_Apps_Overseas_2025_Report.pdf Marketing Dive, "Microdrama apps stand out on mobile: Here's what the numbers say," Feb 20, 2026, citing Sensor Tower State of Mobile 2026: https://www.marketingdive.com/news/microdrama-apps-stand-out-on-mobile-heres-what-the-numbers-say/812610/ Disclosure: I'm the author of the project. I drafted this post with AI assistance and reviewed and edited it myself.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News