cammackflight/motif-render-service
Motif render service
Server-side ProRes 4444 encoder for Motif. Works around the FFmpeg 6 / macOS Sonoma bug that causes client-side ProRes 4444 alpha to read as opaque in Premiere / FCP / Resolve.
The YAML frontmatter above is read by Hugging Face Spaces when this directory is pushed as a Space repo. Keep it intact even if you deploy elsewhere — it's ignored by everything that isn't HF.
Why this exists
@ffmpeg/core@0.12.10 (the latest ffmpeg.wasm) wraps FFmpeg 6.x. On macOS Sonoma 14.4+/Apple Silicon, FFmpeg 6 outputs ProRes 4444 with alpha metadata that Premiere, Final Cut, and Resolve interpret as opaque — even though the alpha is in the file (After Effects, using a different decode path, shows it correctly).
FFmpeg 7 fixes it. There's no FFmpeg 7 ffmpeg.wasm build as of April 2026. Running a real FFmpeg 7 binary on a small server does.
The service accepts PNG frames uploaded from the Motif plugin, pipes them through native FFmpeg 7, and returns the encoded .mov. Auth is piggy-backed on the main API — the service forwards Authorization: Bearer mt_* tokens to /api/library for validation.
Local dev
cd render-service
npm install
# Need FFmpeg 7+ on PATH for local dev. Check with `ffmpeg -version`.
# Homebrew: `brew install ffmpeg` (currently ships 7.x on macOS).
npm run devThe service listens on :8080 by default. Hit /healthz to smoke-test.
To point the Motif panel at a local render service, set VITE_RENDER_SERVICE_URL=http://localhost:8080 in a .env.local file in the repo root and npm run dev the main app.
Deploy (Hugging Face Spaces — recommended, free, no credit card)
Hugging Face Spaces hosts the Dockerfile for free on their CPU Basic tier (2 vCPU / 16 GB RAM, plenty for ffmpeg). No credit card required at any point. The service stays running for hours of inactivity before sleeping; cold-start wake is a few seconds.
One-time setup
- Sign up at https://huggingface.co/join — just email + password, no payment info ever requested.
- Create a new Space: https://huggingface.co/new-space
- Space name:
motif-render-service(or anything; it becomes part of the URL) - License: pick whatever (e.g. MIT)
- SDK: select Docker → "Blank" template
- Hardware: leave on the default free CPU Basic
- Visibility: Public is fine (the service has its own bearer-token auth — random callers will be 401'd)
- Click Create Space
- Push this directory's contents to the Space's git repo:
cd render-service
# Initialize a git repo here (separate from the main motif repo's git).
# The HF Space is its own repo at huggingface.co/spaces/<your-username>/<space-name>.
git init
git remote add hf https://huggingface.co/spaces/<your-username>/motif-render-service
git add .
git commit -m "Initial render service"
git push hf mainWhen git asks for credentials, use your HF username + a write-scope access token from https://huggingface.co/settings/tokens (NOT your account password — HF deprecated password auth).
- HF auto-builds the Dockerfile (5-10 min the first time — it's downloading FFmpeg 7). Watch progress on the Space page.
- When the build succeeds, the Space URL becomes live:
https://<your-username>-motif-render-service.hf.space - Smoke-test:
curl https://<your-username>-motif-render-service.hf.space/healthz
# expect: {"ok":true,"ffmpeg":"ffmpeg"}Wire it into Vercel
In Vercel → motif project → Settings → Environment Variables:
VITE_RENDER_SERVICE_URL = https://<your-username>-motif-render-service.hf.spaceThen Deployments tab → … on latest deploy → Redeploy so the env var bakes into the build.
Future updates
Edit code in render-service/, then:
cd render-service
git add .
git commit -m "Whatever"
git push hf mainHF auto-rebuilds. ~3-5 min later the new version is live.
Alternate deploys
Fly.io (requires credit card)
Has the same Dockerfile, Fly's wake-from-cold is faster than HF, and pricing is genuinely free under the trial allowance. But signup requires a credit card.
brew install flyctl
fly auth signup
cd render-service
fly launch --copy-config --no-deploy
fly deployThe committed fly.toml is set up for this.
Anywhere else with Docker support
Any Docker host works — the Dockerfile pulls FFmpeg 7 from BtbN's builds so no apt-installed FFmpeg 6 creeps in. Just point it at the directory and run.
Deploying elsewhere
Any Docker host works — the Dockerfile pulls FFmpeg 7 from BtbN's builds so no apt-installed FFmpeg 6 creeps in. Environment variables the service reads:
API
POST /render
Multipart form data:
Authorization: Bearer mt_*(required) — same tokens the rest of the plugin uses.settings(text field) — JSON{ fps: number, quality?: number }.frames(file field, repeated) — PNG files namedf000000.png,f000001.png, … Zero-padded 6-digit sequential indices.
Response:
200 OKwithContent-Type: video/quicktime— the encoded.movbytes. HeaderX-Render-Msreports encode wall clock.401 Unauthorized— missing / invalid / revoked bearer token.400 Bad Request— missing/malformed settings or frames.500 Internal Server Error— ffmpeg exited non-zero. Error message includes the tail of ffmpeg stderr.
GET /healthz
Returns { ok: true, ffmpeg: "<path>" }. Used by Fly.io's health check.
Cost ballpark
Fly.io shared-cpu-2x with 2 GB RAM, auto_stop_machines = "stop", min_machines_running = 0: pays only for actual render time plus ~10s wake-up. A 30s-render/day tester usage pattern costs under $1/month.
