Inspiration
Calling a text-to-video model is the easy part. The hard, valuable problems are trust and orchestration.
As AI media floods the internet and regulators (the EU AI Act) start demanding disclosure, brands and creators need to prove how an asset was generated, not just assert it in a caption. And real output is never one API call: it's a chain across providers (script → image → video → voiceover), where any link can be down, rate-limited, or out of credits.
We noticed that Genblaze already emits a cryptographic provenance manifest for every run, and that almost nobody surfaces it. That manifest was sitting there as plumbing. We decided to make it the product: a Content Passport that a non-technical person can read, and that anyone can verify without trusting us.
What it does
Veritas Studio turns a one-line creative brief into a short, campaign-ready clip that ships with its own proof of origin.
- Brief → plan. A one-line brief is expanded into image, motion, and voiceover prompts.
- Multi-step, multi-model Genblaze pipeline.
text → keyframe image → image-to-video(conditioned on the keyframe)→ voiceover, with automatic per-step model fallback if a provider fails. - Everything lands on Backblaze B2. Each asset and a tamper-evident provenance manifest are written to B2 with a content-addressable layout, so identical bytes are stored once.
- A public Content Passport for every run. Verified SHA-256 manifest, per-step model lineage, generation cost, and a "download verifiable copy" with the manifest embedded inside the media file.
- A Verify page. Drop in any file, we extract and cryptographically verify its Genblaze
provenance. No account, no trust in us required (it also works with the
genblaze verifyCLI). - An Admin · Operations console. Live provider/credential health, a per-step run monitor that
names the model that failed and why (e.g. an out-of-credits
402), verification state, cost, and retry/delete.
How we built it
The run is a small composition with one deliberate branch, the video is conditioned on the keyframe, the voiceover is not conditioned on anything but the script:
$$ \text{brief} \;\xrightarrow{\;\text{plan}\;}\; p \;=\; (p_{\text{img}},\, p_{\text{vid}},\, p_{\text{vo}}) \qquad \begin{cases} a_0 = f_{\text{img}}(p_{\text{img}}) \[2pt] a_1 = f_{\text{vid}}(p_{\text{vid}},\, a_0) & \texttt{input_from=0} \[2pt] a_2 = f_{\text{tts}}(p_{\text{vo}}) & \texttt{input_from=[]} \end{cases} $$
- Backend: Python + FastAPI. Routers stay thin; the pipeline
(
backend/app/pipelines.py) is pure Genblaze. - Orchestration & provenance: Genblaze
Pipeline/Step,input_fromchaining,fallback_models, and theManifest(verify/embed/extract). - Storage: Genblaze
S3StorageBackend.for_backblaze(...)+ObjectStorageSinkwithKeyStrategy.CONTENT_ADDRESSABLE, preflighted at startup so bad credentials fail loudly instead of mid-generation. - Models: GMI Cloud — Seedream (image), Kling (image→video), MiniMax TTS (voiceover), all swappable via env vars, because Genblaze abstracts the provider.
- Frontend: a pnpm + Turborepo monorepo with two React + Vite SPAs, a customer app
(Create / Library / Passport / Verify) and a login-gated admin console, sharing types, an API
client, and a Tailwind design system through
@veritas/shared. - Index: a local SQLite mirror for fast library and dashboard queries. B2 is the source of truth; the index is disposable and rebuildable.
Challenges we ran into
- A silently-dropped voiceover. GMI's MiniMax TTS endpoint wants the script under a
textkey, but the bundled model spec allowlistspromptwith no mapping, so the script vanished and we got audio of nothing. We fork the model registry at runtime, aliasprompt → text, and fall back to the stock provider if the SDK internals change. - Chaining the right steps, and only the right ones. The video step must be conditioned on the
generated keyframe (
input_from=0), but the voiceover must stay pure text-to-speech, in chain mode it would otherwise inherit the video as input. It needed an explicitinput_from=[]. - "Success" that produced nothing. A run could come back
okwith zero assets. We now treat asset count as the source of truth for run status, and distinguish failed from partial, surfacing which step, which model, and which error. - Verifying without spending money. Every real generation costs credits, so the routine test suite is fully offline and the credit-spending smoke test is a deliberate, separate step. That discipline shaped how much we mock at the service boundary.
- Making cryptography feel tangible. A SHA-256 canonical hash means nothing to a marketer. The Content Passport and the drag-and-drop verifier were the answer: proof you can see, and proof you can re-run yourself.
- Auth timing. The admin token has to be injected into the shared API client synchronously on
login/init, doing it in a
useEffectraces the first request straight into a 401.
Accomplishments that we're proud of
- The manifest survives the download. You can pull a clip out of the app, hand it to someone with no account, and they can still verify where it came from, the provenance travels inside the file bytes.
- Provenance is a first-class UI surface, not a debug log.
manifest.verify(),canonical_hash, and per-stepsha256are all rendered for the user. - Content-addressable storage that doubles as an integrity check. An asset's key is its digest, $k(b) = \mathrm{SHA256}(b)$, so one decision buys three properties: identical bytes collapse to one object, a fetch is self-checking (recompute and compare), and forging a matching object means finding a collision, around $2^{-256}$ per guess.
- It's operable. Per-step failure reasons, retries, cost tracking ($C = \sum_s c_s$ over the steps that actually billed), live credential health, and a preflight smoke test, the boring production surface that usually gets skipped in a hackathon.
- Real resilience. Per-step fallback models mean one provider outage degrades the run instead of killing it. Treating the $n$ candidates for a step as roughly independent with success probabilities $p_i$, the step survives with $1 - \prod_{i=1}^{n}(1 - p_i)$, the reason a single fallback moves a flaky step from "usually fine" to "fine".
What we learned
- Provenance is a UX problem more than a crypto problem. The hashing is solved; making a person believe the hash is the work.
- Never trust an SDK's default spec. The TTS bug was invisible, valid response, valid audio file, wrong content. Verify what actually reaches the provider.
status == "ok"is not success. Assert on outputs, not on return codes.- Pick the storage key strategy first. Content-addressable keys gave us dedup, integrity, and cache-safety for free; retrofitting that later would have been painful.
- Treat the database as a cache. Once B2 is unambiguously the source of truth, the local index becomes a performance detail you can drop and rebuild without fear.
What's next for Veritas Studio
- A/V compositing mux the voiceover onto the video via Genblaze's FFmpeg compositor so a run ships one finished clip instead of parallel tracks.
- LLM brief-planning via GMI chat, replacing the deterministic planner with genuine creative expansion of the brief.
- Parquet provenance index (
genblaze index) for fleet-level analytics cost per campaign, model reliability over time, verification rates. - User accounts and role tiers, so teams and agencies can share a workspace and an audit trail.
- Interoperability with open provenance standards, so a Veritas passport is readable by tools outside our app.
Built With
- claude
- genblaze-b2
- github
- gmi-cloud
- python
- react
- render
- typescript
- uvicorn
- vercel