-
-
VeriStudio — AI films with receipts: every frame hash-sealed to Backblaze B2, every film publicly verifiable, no login required.
-
Public /verify-film re-fetches all 9 source manifests from B2, recomputes every hash, and draws the film's lineage tree — all green.
-
Pro Mode: cast bank, inspector and multi-track timeline. Elias & Pip render through FLUX.2 with their portraits as reference inputs.
-
Same production, scene 1: same keeper, same dog. Portraits are recorded as step inputs — consistency is provable, not a prompt trick.
-
Studio board: script → storyboard → clips → audio → final cut. A model picker per stage, one Run all — every output sealed as it lands.
-
The tamper test: change one character in a manifest and the seal breaks. Verification is an open recompute, not a promise.
-
Underneath: one Backblaze B2 bucket. Content-addressed assets — the address IS the sha256 — public manifests, per-project final cuts.
-
From "The Last Light" — a 25s film written, cast, animated, scored and sealed in VeriStudio on the judges' own test account.
-
The live app. Every hero card is a sealed output carrying the model that made it: sora-2, imagen, happyhorse, seedance, flux.
-
Built on Backblaze B2 + the Genblaze SDK: GPT-5.6, gpt-image-2, FLUX.2 Pro, HappyHorse 1.1, Seedance 2.0, Sora 2, 4o-mini-TTS, Lyria 2.
What it does
VeriStudio is a text-to-film studio built for the deepfake era, where verification is the product. Two creator surfaces share one pipeline:
- Studio (
/studio) — a beginner board with five columns: Script | Storyboard | Clips | Audio | Final cut. Name a project, pick models per stage, hit Run all. - Pro (
/pro) — a dark NLE: a cast bank of up to six characters, reference-image keyframes for cast consistency, a multi-track timeline (cut, trim, clip toggles, music gain), drag-resizable panels, per-stage provider pickers.
Every media generation — cast portraits, keyframes, clips, narration, music — runs through a Genblaze Pipeline and lands on Backblaze B2 beside a canonical SHA-256-sealed manifest with per-asset hashes. (Storyboards come from Genblaze chat() with a strict schema; scene pipelines pick up provenance from the first visual stage on. The final cut is stitched by local ffmpeg and sealed with a byte-exact Genblaze RunBuilder manifest through the same sink.) Provenance chains end to end: cast portrait → keyframe → clip → final film, whose compose manifest lists every source asset, run id, manifest URL, and the sealed timeline.
Public pages /verify and /verify-film — no login — recompute the hashes and render the whole lineage tree. Change one pixel anywhere in the chain and the tree goes red.
Our demo film "A Truce in the Rain" (29 s) keeps two cast members, Miso and Rex, consistent across every scene via flux-2-pro reference images — and the one-click verify link above confirms it all-green against 9 source manifests: portrait → keyframe → clip → final cut.
Why it matters
AI video has a trust problem, and a provenance claim is worthless if it depends on the operator's word. VeriStudio makes it structural: an asset's address is its SHA-256 (identical bytes dedupe automatically), manifests are sealed at generation time at durable public URLs — never expiring signed links — and verification is an open recompute.
Concretely: a newsroom that receives a syndicated VeriStudio clip pastes its manifest URL into /verify-film and gets, in seconds, a green lineage tree naming every model, input, and byte that produced it — no account, no API key, no trust in us. Even user-uploaded bytes are covered: their sha256es appear in the film manifest's input hashes, and verify-film checks membership. Verification is deliberately anchored to the sealed bucket — verify-film fetches manifests only from our B2 and CDN hosts, which is both the trust model and SSRF hardening on a public endpoint. If VeriStudio vanishes tomorrow, the bucket still proves the film.
How we use Backblaze B2
- Content-addressed layout via Genblaze's
ObjectStorageSinkwithKeyStrategy.CONTENT_ADDRESSABLE:veristudio/assets/{sha[:2]}/{sha[2:4]}/{sha}.ext— the address is the hash, and identical bytes dedupe automatically. - Public durable manifests at
veristudio/manifests/{run_id}.json— verification links live forever. - Event Notifications, live in production. Rule
veristudio-pipeline(b2:ObjectCreated:*, prefixveristudio/) was configured entirely through the native API (b2_set_bucket_notification_rules) after Backblaze support enabled the feature on our account — the console was never needed. Our webhook verifies the HMAC-SHA256v1=signature with a constant-time compare, fails closed (503) without a secret, dedups on eventId, and answers in under 3 seconds. Real signed ObjectCreated events for assets and manifests arrive at the production webhook and merge with app-level events into one account-scoped activity feed. - Cloudflare CDN in front of B2 (Bandwidth Alliance free egress) at
media.veristudio.dpdns.org. New assets embed CDN URLs; older direct-B2 URLs are rewritten on read so stored manifests stay byte-identical and hashes stay intact. This closed the free-tier 1 GB/day download-cap 403s. - Full data lifecycle. Per-user uploads prefix
veristudio/users/{uid}/uploads/; project and job deletes cascade to their B2 objects; admin account deletion clears the entireusers/{uid}/prefix. Quotas: 2 GB per account, per-file caps 15 MB image / 120 MB video / 30 MB audio, enforced by a Content-Length precheck plus a streaming cap.
How we use the Genblaze SDK
Pipeline/Stepchains for every media generation;chain=Truedrives the gpt-image-2 → sora-2 animate chain (gpt-image-2 is the one image model emitting Sora-exact 1280x720).external_inputsseeds runs with pre-existing B2 assets (uploaded frames, keyframes, cast portraits) asAsset(url, sha256)— provenance starts in the bucket.FFmpegCompositorfan-in: a 3-step pipeline mixes sora-2 video and TTS audio withinput_from=[0, 1],StepType.MIX.- Two-layer cross-provider fallbacks: per-step
fallback_modelsplus a candidate-chain walker that advances on both raised errors andRunStatus.FAILED; clips fall back HappyHorse → Seedance 2.0. - Custom
BaseProviderwritten from scratch (alibaba-happyhorse): HappyHorse-1.1 i2v/t2v on the DashScope-intl async video API, keyframe passed asfirst_frame, watermark off. FluxRefsProvider: aReplicateProvidersubclass with a customsubmit()— up to 8 reference portraits into flux-2-pro at custom 1280x720. Portraits remainstep.inputs, so manifests chain portrait → keyframe → clip → film.PatchedSoraProvider: works around two shipped adapter bugs against openai SDK 2.x — both filed upstream (see Feedback Prize below).Manifest.verify()powers both public verify pages;RunBuilder/StepBuilderconstruct byte-exact compose manifests whose step inputs list every source asset, withsources[](run id + manifest URL) and the sealed timeline in step metadata.- Storyboards use Genblaze
chat()with strict-schemaresponse_format; storage isgenblaze_s3.S3StorageBackend.for_backblaze()+ObjectStorageSink.
AI providers & models
| Pipeline stage | Provider / default model | Allowlisted alternatives & fallbacks |
|---|---|---|
| Script & storyboard | OpenAI gpt-5.6-luna | gpt-5.6-terra, gpt-5.6-sol, gpt-5.4-nano (auto-fallback; the model actually used is recorded) |
| Keyframes | Google imagen-4.0-fast-generate-001 | imagen-4.0-generate-001; OpenAI gpt-image-2 → gpt-image-1.5; Black Forest Labs flux-2-pro → flux-schnell (Replicate) |
| Keyframes w/ cast refs | Black Forest Labs flux-2-pro via our FluxRefsProvider | — |
| Scene clips (i2v) | Alibaba happyhorse-1.1-i2v (custom DashScope provider) | ByteDance seedance-2.0 (auto-fallback), seedance-1-lite; Kuaishou kling-v2.1 |
| Narration | OpenAI gpt-4o-mini-tts | 6 voices (alloy, echo, fable, onyx, nova, shimmer) |
| Music bed | Google lyria-2 | MiniMax music-1.5 |
| Final cut | Local ffmpeg (concat, timed narration, ducked looped music, burned SRT captions) | — |
| Legacy single-shot / animate | OpenAI sora-2 (PatchedSoraProvider) | Google veo-3.1-fast-generate-preview → veo-3.1-lite |
Production readiness
- Real accounts: email+password (stdlib scrypt) and Google Sign-In (server-side token verification,
email_verifiedrequired, no silent account linking); 30-day sessions; change-password revokes all other sessions. - Real money — Stripe is LIVE: credits (1 cr = $0.01, 150 free at signup), Checkout packs $5/500 · $12/1,300 · $25/3,000, signature-verified webhooks, idempotent fulfilment keyed by checkout session, and chargeback/refund clawback — disputes reverse credits, balances can go negative, and the debt blocks spending until repaid. One real $5 purchase has completed end-to-end.
- Atomic credit ledger: all balance writes are relative with UNIQUE-ref idempotency; a lost-update race was found, fixed, and re-tested under 20 concurrent grants; failed runs auto-refund only undispatched jobs (
DELETE … RETURNINGcloses the finish-mid-delete TOCTOU). - Two internal security-audit rounds (a 7-dimension multi-agent review, then a full payments + injection pass), with every fix in the repo: open-redirect fix on login, SSRF-hardened public verify-film (https-only host allowlist, redirects refused, 4 MB manifest cap, 60-source cap, rate-limited), model-slug allowlists that close flat-price arbitrage onto expensive models, an upload MIME allowlist that rejects SVG (a script container on a CDN), CSP/HSTS/nosniff/frame-ancestors headers, and both webhooks failing closed without secrets.
- Rate limits on register, login, Google auth, change-password, uploads, and verify-film.
- Admin console: stat tiles, cascade account deletes (DB + B2), credit grants, all productions, unfiltered event stream.
- Legal pages live:
/terms,/privacy,/refunds— auto-refund of failed runs, 14-day unused-pack refunds, and plain disclosure that media and manifests live at public durable URLs.
Product feedback submitted — Feedback Prize
Filed against backblaze-labs/genblaze from bugs we hit in production:
- #125 — quickstart_local.py fails
Manifest.verify()on core 0.3.4: assets lack sha256. - #126 — Sora image→video chaining broken:
submit()passesimage=, but openai SDK 2.x expects aninput_referencefile upload; chained assets arrive as local temp paths. - #127 — Sora
fetch_output()callsvideos.content(), renameddownload_content()in openai SDK 2.x.
Our PatchedSoraProvider ships working fixes for #126 and #127.
Architecture
Next.js web + FastAPI API on Railway. SQLite on a Railway volume holds accounts, projects, jobs, and the credit ledger; every byte of media and every manifest lives on Backblaze B2 behind the Cloudflare CDN. Flow: prompt → FastAPI job → Genblaze Pipeline (multi-provider, fallback-walked) → ObjectStorageSink → B2 asset + sealed manifest → B2 ObjectCreated webhook → activity feed. The verify pages fetch manifests straight from the bucket and recompute the hashes. Single-container by design — an honest fit at this scale, documented in the code.
What's next
B2 Object Lock on the manifests prefix for tamper-proof retention, C2PA export so VeriStudio seals travel inside industry-standard content credentials, email verification at signup, and moving the ledger and rate limits to shared stores for horizontal scale.
Built With
- backblaze-b2
- cloudflare
- dashscope
- fastapi
- ffmpeg
- gemini
- genblaze
- next.js
- openai
- python
- railway
- replicate
- sqlite
- stripe
- typescript
Log in or sign up for Devpost to join the conversation.