<meta property="og:video">
URL params in, a streaming MP4 out. A Rust compositor and x264, both in wasm, render each frame inside a Cloudflare Worker and stream the file out faster than it plays. iMessage, Slack and Discord play it inline.
<!-- that's the whole integration -->
<meta property="og:video" content="https://og.video/v1/title-card.mp4?title=We+shipped+it&kicker=Launch&brand=ACME&url=example.com">
<meta property="og:video:type" content="video/mp4">
<meta property="og:image" content="https://og.video/v1/title-card.png?title=We+shipped+it&kicker=Launch&brand=ACME&url=example.com">
A scenario is a pure function of (params, frame). The pipeline runs it once per video.
GET /v1/kinetic.mp4?text=… ──► edge cache hit ───────────────────────────────► MP4, ~2 ms
──► R2 hit ───────────────────────────────────────► MP4, streamed
──► miss:
1. write the prefix: opening frames, pre-encoded once per prefix key
2. render the rest live, one fMP4 fragment per frame
GSAP timeline ──► Rust/wasm compositor (tiny-skia + SIMD) ──► x264 (wasm) ──► moof/mdat ──► you
3. store the finished file in R2 + the edge cache
Each one is live. Edit the fields and the URL updates; fields left at their default stay out of it. Arrays and objects are JSON.
Everything is a GET on a query string. Keys a scenario doesn't declare (utm_source) are ignored and don't touch the cache key. Cache misses are rate limited per IP.
| Request | Returns |
|---|---|
GET /v1/<scenario>.mp4?<params> | The video. Streamed on a miss, byte-ranged on a hit. |
GET /v1/<scenario>.png?<params> | The poster frame, for og:image. |
GET /v1/<scenario>?<params> | A share page with the og:video and og:image tags set. |
GET /v1/<scenario>.json?<params> | Resolved params and defaults, content id, links, and a packed ?s= URL about 3× shorter. |
POST /v1/<scenario> | JSON params in, a short /v1/<scenario>/<id>.mp4 out. The id is a content hash. |
Agents: add https://og.video/mcp as a remote MCP server, or npx skills add https://og.video. The server builds these URLs from params, or designs a card from a description. The skill covers the same API.
| Endpoint | Returns |
|---|---|
POST /mcp | A Streamable HTTP MCP server with two tools: og_video_url (params to URLs and og tags) and og_video_from_prompt (a description to a design). |
GET /.well-known/agent-skills/ | The og-video agent skill, listed in index.json. |
GET /llms.txt | A Markdown overview of the site for LLMs. |
og.video runs streaming-og-video unchanged. Deploy the same Worker on your Cloudflare account, with your own scenarios.
# one dependency, no build step
npm install github:runtypelabs/og-video
// src/index.js
import { createHandler, kvStore } from 'streaming-og-video/worker'
import { titleCard, kinetic } from 'streaming-og-video/scenarios'
export default createHandler({
scenarios: [titleCard, kinetic],
bucket: 'RENDERS', // R2: videos, posters, prefixes
store: kvStore({ kv: 'STATE', r2: 'RENDERS' }),
secret: 'OGV_SECRET', // signed URLs
rateLimit: 'RENDER_LIMIT',
})
A scenario is a small program: GSAP on plain objects for the timeline, a draw function for cached layers and live overlays. npx sogv stills, render and bench iterate locally; the repo ships an agent skill that knows the API. Write a scenario.