oxframe vs Shotstack
Pick oxframe when you want the video as code in your repository, rendered on your own machine or CI with no per-minute bill. Pick Shotstack when you want a hosted render API, a template editor for non-developers and no install at all.
At a glance
| oxframe | Shotstack | |
|---|---|---|
| How a video is described | A Rust scene | A JSON edit: a timeline of tracks and clips |
| Where it renders | Your machine or your CI | Shotstack’s cloud, through a render queue |
| Getting the file | It is written to disk | Poll the render by id, then download; output URLs last 24 hours |
| Price | $0; your own compute | From $10 for 50 minutes; plans from $39 a month for 250 minutes |
| Development | The same renderer everywhere | A stage sandbox; v1 renders without watermarks |
| Data from outside | Any Rust value: a CSV row, JSON, an API | Templates with merge fields |
| Layout | Rows, columns, wrapping text, themes | Positions and alignment per clip |
| Charts, code, math | Built in: axes, plots, code with diffs, matrices | Images and video you render elsewhere |
| An agent sees the frames | oxframe check with a JSON report | Render, download, look |
| Account and key | None | An API key in the x-api-key header |
| License | MIT or Apache-2.0 | Proprietary service |
One scene in both
The edit from Shotstack’s Hello World guide: “HELLO WORLD” fading in and out over five seconds, with a music track, at 1024 × 576. The Shotstack side is the JSON you POST to /edit/stage/render; the oxframe side is the scene you render with oxframe render. We did not time the Shotstack render: it needs an account, and its time includes the queue.
Shotstack
{
"timeline": {
"tracks": [
{
"clips": [
{
"asset": {
"type": "rich-text",
"text": "HELLO WORLD",
"font": {
"family": "Montserrat",
"color": "#ffffff",
"size": 32
},
"align": {
"horizontal": "left"
}
},
"start": 0,
"length": 5,
"transition": {
"in": "fade",
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "audio",
"src": "https://s3-ap-southeast-2.amazonaws.com/shotstack-assets/music/moment.mp3",
"effect": "fadeOut"
},
"start": 0,
"length": "end"
}
]
}
]
},
"output": {
"format": "mp4",
"size": {
"width": 1024,
"height": 576
}
}
}oxframe
use oxframe::prelude::*;
#[oxframe::scene]
pub async fn hello(view: View) {
// A rich-text clip, left-aligned, white, 32 px; it fades in and out.
let title = view.add(
Txt::new()
.text("HELLO WORLD")
.font_size(32.0)
.fill(Color::WHITE)
.to_edge(Edge::Left)
.opacity(0.0),
);
title.fade_in().await;
// Shotstack holds the frame still under its music. Without a soundtrack,
// oxframe check calls four still seconds dead time, so the title drifts.
title.scale().to(Vec2::new(1.08, 1.08), 4.0).ease(presets::LINEAR).await;
title.fade_out().await;
}
pub fn project() -> Project {
// The audio track becomes Audio::track("moment.mp3").master_fade(0.0, 1.0) with
// the file next to the project; the playground has no files, so it is left out.
Project::new("hello").size(1024, 576).scene(hello())
}What a thousand videos cost
Take a thousand 30-second videos a month: 500 minutes of video.
- Shotstack, from its pricing page on 2026-10-22: the Pro plan, $99 a month for 750 minutes, or $100 pay as you go at 20¢ a minute. The Starter plan’s 250 minutes are not enough.
- oxframe: no fee. On a laptop, a 27.5-second explainer renders in 8.4 seconds on 8 jobs (the measurement on the home page), so 500 minutes of similar video take about two and a half hours of one machine. On GitHub Actions that is runner time, free for public repositories.
The trade is time and hardware against a bill and a dependency. A hosted API wins when you render rarely or cannot run anything yourself; code wins when you render often, need logic in the video or want the data to stay with you.
Moving an edit over
A Shotstack edit maps onto a scene almost clip by clip:
| Shotstack | oxframe |
|---|---|
a clip with start and length | a node, with .await and wait_for for time |
transition: { in: "fade", out: "fade" } | fade_in(), fade_out(); scenes change with transition(…) |
rich-text and title assets | Txt, or components such as TitleCard and LowerThird |
image and video assets | Img, Video (feature video) |
an audio track | Audio::track(…) with fades and ducking under a voice |
merge fields {{NAME}} | a Rust value, read from the environment, a file or main |
output.size | Project::size(w, h), or .aspects(…) for several shapes at once |
A video per row of a CSV
Where Shotstack fills a template’s merge fields for each request, oxframe reads the row in the scene, and a shell loop renders one video per row.
use oxframe::prelude::*;
/// One row of the CSV, passed in by the loop that renders it.
fn field(name: &str) -> String {
std::env::var(name).unwrap_or_default()
}
#[oxframe::scene]
pub async fn card(view: View) {
let title = view.add(TitleCard::new(format!("Thank you, {}", field("NAME"))).subtitle(format!(
"{} pull requests merged this year",
field("PRS")
)));
title.enter().await;
title.next().await;
wait_for(1.5).await;
}
pub fn project() -> Project {
Project::new("thanks").scene(card())
}# people.csv: name,prs
# Ada,42
# Grace,17
cargo build --release
tail -n +2 people.csv | while IFS=, read -r name prs; do
NAME="$name" PRS="$prs" target/release/thanks render --out "out/$name.mp4"
doneOn the benchmark machine the loop renders 3 cards of 2.42 s each, 1080p at 60 fps, in 14.65 s on the CPU, one after another (2026-10-22).
Rows are independent, so a CI matrix or xargs -P renders them in parallel. NAME=… oxframe check on the longest name catches a title that does not fit its card before the batch runs.
When to pick Shotstack
- Nobody on the team should install or run a renderer.
- Non-developers edit templates in a web editor.
- You render a few videos a month and a bill is simpler than a machine.
When to pick oxframe
- You render many videos and do not want a per-minute bill.
- The video needs logic: charts from data, layout that adapts, code walkthroughs.
- Data and output should stay on your machine or in your CI.
- An agent writes the scene and should check its frames.
FAQ
Is oxframe an open-source Shotstack alternative?
For the rendering part, yes: oxframe is MIT or Apache-2.0 and renders on your machine. It is a library and a CLI, not a hosted API, so there is no endpoint to call; you run the render where you want it.
Can I keep my Shotstack JSON?
Not as is. oxframe does not read Shotstack edits; each clip becomes a line or two of a scene, as in the table above. An agent with the oxframe skill can port an edit for you.
How do I render one video per row of data?
Read the row in the scene (from the environment, a file or main) and render once per row from a loop or a CI matrix. The example above renders a thank-you card per contributor.
How fast is oxframe compared to Shotstack?
We have not timed Shotstack: it needs an account, and a cloud render includes the queue. oxframe renders a 27.5-second explainer in 8.4 seconds on a laptop with 8 jobs.
Do I need an account or an API key?
No. oxframe sends nothing anywhere; the only network use is the optional playground on oxframe.dev.
Sources
Checked on October 22, 2026. Found something out of date? Open an issue on oxframe-web.
- Shotstack pricing
- Shotstack Hello World guide
- Shotstack API reference
- oxframe license and what leaves your machine
- oxframe-sdk on GitHub