Inspiration

The hackathon brief asked for a generative media app built on Genblaze and Backblaze B2. Looking at how people actually use AI media today, one problem stood out: every model lives in its own app with its own subscription, and finished work ends up scattered across all of them. I wanted to see if a single infinite canvas could hold the entire workflow, from prompt to final render to durable storage.

What it does

MediaMint is a generative media studio on one infinite canvas. You describe what you want and an AI agent routes the prompt to the right model, or you pick one yourself from 35+ image, video, and audio models under a single credit balance. Results land on the board next to what they came from, so iteration stays visible.

Boards are multiplayer with live cursors and presence, teams get workspaces with their own credit pools, and every render is stored on Backblaze B2 in an asset library with tags, search, and thumbnails. Each generation ships with provenance: open any render and you see the Genblaze run it came from, its SHA-256 manifest hash, and a link to the manifest itself on B2. You can also wire models together with a node workflow (text to image to upscale or video to synced foley) and publish read-only board links for clients.

How we built it

The frontend is Next.js 16 with a custom-built infinite canvas: selection, frames, drawing tools, a laser pointer, minimap, node graph with connectable edges, and realtime collaboration over WebSockets authorized through Supabase. The backend is FastAPI with a durable SQLite job queue that survives restarts and resumes in-flight generations.

Genblaze is the orchestration layer. fal.ai is not one of its built-in connectors, so I wrote a custom provider implementing the SDK's submit, poll, and fetch_output lifecycle exactly as the Genblaze new-provider guide describes. Every fresh job runs as a Genblaze Pipeline through that provider, and the pipeline's sink writes the finished asset straight into B2:

result = (
    Pipeline("mediamint-image")
    .step(FalProvider(on_submit=persist_request_id),
          model="fal-ai/flux/dev", prompt=job.prompt, modality=Modality.IMAGE)
    .run(sink=ObjectStorageSink(S3StorageBackend.for_backblaze("MediaMint")))
)
# result.manifest -> SHA-256 verified, pinned to B2 at provenance/{job_id}.json

B2 also stores user uploads, reference images, auto-generated WebP thumbnails, and the whole asset library, served through cache-friendly presigned URLs.

Providers and models, all orchestrated via Genblaze with the custom fal provider: Black Forest Labs FLUX family (Schnell, Dev, Krea, Pro 1.1, Ultra, FLUX 2 Turbo, Pro, Max), Google Nano Banana 1, 2, and Pro plus Veo 3.1, OpenAI GPT Image Mini, 1.5, and 2, Kling 2.5 Turbo, O3, and video-to-audio, MiniMax Hailuo 02 Pro, ByteDance Seedance 2.0, ElevenLabs Multilingual v2, Turbo v2.5, Music, and SFX, Stability Audio 2.5 and 3 SFX, Topaz, Clarity, and Recraft upscalers, Bria background removal, BiRefNet, Ideogram Character, ControlFoley, and ThinkSound. The canvas assistant runs on Google Gemini. Billing is Stripe, deployed on Vercel and Railway.

Challenges we ran into

The hardest part was marrying Genblaze's atomic pipeline runs with my durable job queue. A pipeline can't adopt a half-finished job after a server restart, so the provider fires a callback the moment fal accepts a submission, the request id is persisted immediately, and restarts resume through a direct poll path instead of paying for the generation twice. Another one: genblaze-s3 requires boto3 1.36+, whose new default checksum behavior can break S3-compatible stores, so all B2 clients are pinned to when_required. And building an infinite canvas from scratch (marquee selection, resize handles, edge routing, realtime presence) is its own month of small hard problems compressed into days.

Accomplishments that we're proud of

Extending the Genblaze ecosystem with a working fal.ai provider rather than just calling a built-in connector. A verified end-to-end run: pipeline submits to fal, the sink lands the asset in B2, and manifest.verify() checks out. Provenance surfaced as a real product feature users can see, not a JSON file nobody opens. And the whole thing, canvas included, built inside the hackathon window.

What we learned

Genblaze's provider lifecycle and sink architecture in real depth, since writing a connector forces you to understand retry policies, adaptive polling, and manifest canonicalization. That provenance becomes valuable the moment it is visible in the UI instead of buried in storage. And more than I expected about S3-compatible storage edge cases, presigned URL caching, and designing job queues that assume the process will die.

What's next for MediaMint

Running the canvas node graph server-side as one chained Genblaze pipeline, so text to image to upscale to video to foley executes as a single run with full lineage. Mirroring Genblaze's parent_run_id on the board, so the visual iteration history and the cryptographic one match. Fallback chains behind Auto mode. And finishing the MCP connector so you can generate onto your boards straight from Claude, with the OAuth flow already underway.

Built With

Share this project:

Updates

Submission history