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 ObjectStorageSink with KeyStrategy.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:*, prefix veristudio/) 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-SHA256 v1= 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 entire users/{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/Step chains for every media generation; chain=True drives the gpt-image-2 → sora-2 animate chain (gpt-image-2 is the one image model emitting Sora-exact 1280x720).
  • external_inputs seeds runs with pre-existing B2 assets (uploaded frames, keyframes, cast portraits) as Asset(url, sha256) — provenance starts in the bucket.
  • FFmpegCompositor fan-in: a 3-step pipeline mixes sora-2 video and TTS audio with input_from=[0, 1], StepType.MIX.
  • Two-layer cross-provider fallbacks: per-step fallback_models plus a candidate-chain walker that advances on both raised errors and RunStatus.FAILED; clips fall back HappyHorse → Seedance 2.0.
  • Custom BaseProvider written from scratch (alibaba-happyhorse): HappyHorse-1.1 i2v/t2v on the DashScope-intl async video API, keyframe passed as first_frame, watermark off.
  • FluxRefsProvider: a ReplicateProvider subclass with a custom submit() — up to 8 reference portraits into flux-2-pro at custom 1280x720. Portraits remain step.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/StepBuilder construct byte-exact compose manifests whose step inputs list every source asset, with sources[] (run id + manifest URL) and the sealed timeline in step metadata.
  • Storyboards use Genblaze chat() with strict-schema response_format; storage is genblaze_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_verified required, 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 … RETURNING closes 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() passes image=, but openai SDK 2.x expects an input_reference file upload; chained assets arrive as local temp paths.
  • #127 — Sora fetch_output() calls videos.content(), renamed download_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

Share this project:

Updates