💡 Inspiration
There are far more good scripts in the world than there are people who know how to write a prompt.
The video models are astonishing now, and most of them will take anyone's money. But there is an entry fee nobody mentions: you have to already know how to talk to them. Which words the model actually listens to. Which quality tier to pick. Whether this particular shot is the kind that tends to come out right, and roughly what three minutes of footage will cost before you press the button.
Nobody teaches any of that. There is no manual.
And the people we had in mind — a screenwriter with a finished script in a drawer, a novelist who spent three years on one story — are not going to go learn prompt engineering so they can see their own work move. We didn't think they should have to.
So we didn't build another text-to-video tool. We built the layer that sits between them and the model. If we had to say it in one line:
Most people can't really talk to AI. We're the thing standing in between.
We made that testable. If a text box shows up anywhere in the product besides file upload, we've broken our own rule. When the system needs something it doesn't have, it makes two to four candidates and lets you pick. It never hands you an empty field and asks you to describe what you want.
The user does two things, start to finish. Drops a file. Presses buttons.
In this business a film gets made when someone green-lights it, and that decision has always belonged to whoever holds the money. We wanted it to belong to the person who wrote the thing.
🎬 What it does
You give it a screenplay. It reads the whole thing, splits it into chapters, breaks each chapter into shots, casts the characters, writes the prompts, generates the footage, and then watches what it just made. If shot 3 has a person in it who shouldn't be there, it notices, and it puts that in the retry instruction.
You press keep or again. That's the whole job.
Nine agents do the work. But a handful of decisions are what make it feel like a producer rather than a pipeline, and those are worth describing.
It tells you the odds before you spend. Not "generating…" — but something closer to: this chapter is 12 shots, it'll cost $4.50, and about half of them will come out right the first time. Knowing that before you commit is the promise. Everything else follows from it.
The cost ladder is deliberate. A candidate image for a character costs two cents. A shot costs a dollar. A finished segment costs twenty. So the system pushes every decision as far down that ladder as it possibly can. You settle what a character looks like at two cents a try, not at a dollar a try.
We checked whether that actually matters. Same shots; the only difference was whether a character reference existed first:
| First-pass success | Expected retry cost | |
|---|---|---|
| Before casting | 29% | $1.86 |
| After casting | 49% | $1.17 |
Twenty points, for two cents. That table is why casting is a hard gate and not a suggestion.
The face is an input, the look is an output. Upload a photo — yourself, or a character sheet you've drawn for years — and the designer keeps that person and re-renders only the look in the film's style. A character locked from generated candidates works the same way, so "new wardrobe" never quietly means "new person". And wardrobe belongs to a chapter, not the film: pick chapter seven, ask for a new outfit, the face stays locked and every other chapter keeps its own. Uploads require one checkbox — the image is yours to use and the liability is yours — stored with a timestamp.
The least intuitive thing we know is printed right on the buttons. Photorealistic is the hardest style to get right; animation is far more forgiving. In our tests, 35% first-pass against 50%, and animation is cheaper too. It's the uncanny valley, but with a number attached: people catch a flaw in a real-looking human instantly, and the same flaw in an animated frame just reads as style. So the style buttons carry the adjustment (−10pt / +12pt / +15pt) instead of hiding it in the backend. You don't need to know why. You just get to see it before you choose.
It remembers what you've shot. Every generation and every keep-or-again verdict writes a row to ClickHouse. Once enough similar shots pile up, the success estimate stops using our built-in priors and starts using your own numbers, and it says so, along with the sample size. The more you shoot, the less it costs you to get what you were after.
And you can leave with your work. After casting, every prompt is exportable. Take them somewhere else if you want. We figured a tool that's willing to hand your work back is one you can trust with it.
🔧 How we built it
One Cloud Run service, frontend and backend shipped together. The Gemini family on Vertex AI. Firestore for state, Cloud Storage for media, ClickHouse for the learning loop, reached through the official mcp-clickhouse server.
The stack is in the README. Two decisions actually shaped the product, so those get the space here.
Which model does which job is a cost decision. Each model in the family is good at something different, so we assign by the shape of the work. Pro takes anything that needs to hold the whole thing in its head at once: reading the full screenplay, working out that "Luna" and "月音" are the same person, cutting 37 scenes into chapters that can be filmed. Flash takes work with clear rules and clear formats: writing prompts, translating subtitles, counting how many people appear in a finished clip. Flash-Lite takes the high-volume, low-judgement batch work.
Screenplay parsing gets the expensive model because it's the foundation. Get it wrong and everything downstream is wrong in ways you won't notice until much later. The premium is about $0.11 on a 90-page script. Saving eleven cents there is a bad trade. Saving it across a few hundred batch calls is a very good one.
Gates are real state transitions, not polite requests in a prompt. Every action that costs money is a state change in the schema. We never ask the model to please confirm first. Probabilistic output can't guarantee a hard requirement, so anything the architecture can enforce, the architecture enforces.
💸 Challenges
We had $150 in credits. Ten seconds of video costs a dollar.
That meant we never got to do the thing a funded team does when a shot comes out wrong, which is run it again. Every button press had to be worth it. Every failure genuinely hurt. The full film prices out at roughly $238, and we still haven't shot all of it.
Turn that around, though, and it's the whole reason the product exists.
Nobody with a budget builds success-rate prediction. Why would they? If it's wrong, generate it again. Nobody with a budget builds a cost ladder, or writes every failed take into a database so the next estimate is a little better. There's no reason to.
And being broke is exactly the position our users are in. An independent screenwriter doesn't get to "just generate it again" either.
We were broke, so we built the thing broke people need.
That became a real dimension of the product rather than a complaint. Money in Greenlight is calculable, and it's a choice. Every quality tier has a price in the config, and the system prices your selection before you commit. More budget, higher tier, higher resolution. Less budget, a cheaper tier, and you still get the whole film. Same script, you can shoot it cheap first to check the story holds, then re-shoot a handful of key moments at the top tier.
The tool being expensive or cheap was never the point. The point is whether you know the number before you press the button. Most tools generate first and show you the bill later. Ours quotes first, you decide, then it spends.
📚 What we learned
A 200 doesn't mean it worked. We ran into this again and again. Subtitle burn-in returned 200, produced a 30MB file, and had no visible text, because the container had no fonts and ffmpeg exits 0 regardless. Our video timeout was set to 300 seconds, but requests measures the gap between two reads, not total elapsed time, so a connection that stays open and never answers never trips it. The request ran until the platform killed it at 900 seconds. Getting killed is worse than timing out: the exception handler never runs, so the clip state and the failure record are both gone. Our rule now is that degrading is fine and degrading silently is not.
A check that covers half the problem is worse than no check. We wrote a health endpoint that reported "12 fonts, 4 CJK." It looked perfect. We cited it as proof. But fontconfig's config file was missing, so libass still couldn't draw a single character. It gave us a green light that looked like someone had verified it.
Two implementations of the same thing will drift. The homepage and the project page each had their own learning-loop component. One read a field under the wrong key and quietly showed 0 for three days. The other was correct, only because it was different code. They're one component now. The demo build and the production build are also one codebase, one image, one commit; the only difference is an environment variable.
Users describe the symptom, not the cause, and the symptom is always real. Every "it's stuck" and "nothing happens when I click" came with a diagnosis that turned out to be wrong. Not once did the report itself point at the wrong place.
🚀 What's next
Shooting the rest of the film. That takes budget more than it takes time.
Every iteration has the same target: push the displayed success rate higher, so it costs the user less to get what they came for. Taking that barrier down is really the only job we have.
This is version one. It'll keep changing, because we use it ourselves.
Built With
- clickhouse
- cloud-run
- cloud-storage
- docker
- fastapi
- ffmpeg
- firestore
- gemini
- gemini-3.6-flash
- gemini-omni-flash
- gemini-tts
- google-adk
- google-cloud
- google-genai
- model-context-protocol
- pydantic
- python
- react
- typescript
- vertex-ai
- vite


Log in or sign up for Devpost to join the conversation.