A REST API that wraps FFmpeg for media processing operations. Built with Node.js, Hono, and BullMQ for reliable async job processing.
Convert and process media files through simple HTTP endpoints:
- Video: Convert any video to MP4, convert to animated GIF, extract audio tracks (mono/stereo), extract frames at custom FPS (compressed as ZIP/GZIP)
- Audio: Convert any audio to MP3 or WAV
- Image: Convert any image format to JPG, resize images while preserving format
- Media Info: Probe any media file for metadata and stream information
The API supports two storage modes configured via the STORAGE_MODE environment variable:
stateless(default) - Files returned directly in HTTP responsess3- Files uploaded to S3-compatible storage, URLs returned
Files are processed and returned directly in the HTTP response. Simple and straightforward for immediate consumption.
Cost Consideration: On Railway, stateless mode is cheaper than running S3 Mode unless you have free egress at your S3-storage provider (like Cloudflare R2). Railway charges $0.05 per GB egress vs S3's typical $0.09 per GB, but you trade off file persistence - processed files aren't stored for later retrieval.
Stateless mode can optionally cache binary conversion outputs using cacache to avoid rerunning FFmpeg on identical inputs + params.
- Cache scope: binary conversion endpoints only (not
/.../urlS3 responses, not/media/info) - Cache key: SHA-256 of input bytes + job type + normalized processing params
- Retention: TTL + size cap (enforced on reads/writes and startup)
- Storage: local filesystem (ephemeral by default)
Configuration:
CACHE_ENABLED=false # Enable/disable stateless binary cache
CACHE_DIR=/tmp/ffmpeg-rest/cache # Cache directory (defaults to <TEMP_DIR>/cache)
CACHE_TTL_HOURS=2160 # Entry TTL in hours (90 days)
CACHE_MAX_SIZE_MB=1024 # Max cache size on disk in MiB (1 GiB)Processed files are uploaded to S3-compatible storage and a URL is returned. This mode significantly reduces egress bandwidth costs since users download the processed files directly from S3 rather than through your API server. Ideal for production deployments where bandwidth costs matter.
Why Cloudflare R2? R2 is S3-compatible and offers no egress fees, which dramatically lowers costs when serving processed media from your bucket via Cloudflare's global network. While any S3-compatible storage works, R2 is the only major provider with zero egress charges—making it the optimal choice for media delivery.
Configure S3 mode by setting STORAGE_MODE=s3 and providing S3 credentials in your environment variables.
S3 Mode includes intelligent content-based deduplication to optimize storage costs and upload performance:
- SHA-256 File Hashing: Each processed file is hashed using SHA-256 before upload
- Redis Cache: File hashes are mapped to S3 URLs with a 90-day TTL (configurable)
- Automatic Deduplication: Identical files are only uploaded once - subsequent requests return the cached S3 URL
- Zero-Cost Cache Hits: When a duplicate file is processed, the upload to S3 is skipped entirely
- Graceful Degradation: Cache failures don't block uploads - the system falls back to normal upload behavior
Configuration:
S3_DEDUP_ENABLED=1 # Enable/disable deduplication (default: true)
S3_DEDUP_TTL_DAYS=90 # Cache TTL in days (default: 90)This feature dramatically reduces S3 storage costs and upload bandwidth for workloads with duplicate media content, while improving response times through cache hits.
This API is built with documentation-first approach using Hono Zod OpenAPI and Scalar:
- Type-Safe Schemas: All endpoints use Zod schemas for validation, ensuring type safety and automatic OpenAPI spec generation
- Interactive API Reference: Beautiful, interactive documentation powered by Scalar at
/reference - OpenAPI Spec (
/doc): Complete machine-readable API specification (served as OpenAPI 3.0) - LLM-Friendly Docs (
/llms.txt): Markdown documentation optimized for AI assistants (generated from OpenAPI 3.1, following llmstxt.org standard)
Every endpoint is fully documented with request/response schemas, validation rules, and example payloads. No manual documentation maintenance required.
- Node.js 22+ and npm
- FFmpeg and FFprobe installed and available in PATH
- Redis server running
-
Clone the repository
git clone https://github.com/crisog/ffmpeg-rest cd ffmpeg-rest -
Install dependencies
npm ci
-
Start Redis (using Docker)
docker-compose up -d
-
Configure environment
cp .env.example .env cp apps/web/.env.example apps/web/.env # Edit both files with your settings -
Run the API
Development mode (with auto-reload):
# Starts web + server + worker npm run devOr run processes individually:
npm run dev:server npm run dev:worker npm run dev:web
Production mode:
npm run build npm start
The apps/web workspace is a Vite + TanStack Router frontend with a Nitro catch-all proxy for /api/**.
- Dev server:
npm run dev:web - Proxy route:
apps/web/routes/api/[...path].ts - Frontend API client uses Hono RPC (
hc) with sharedAppTypefrom the server
Proxy behavior:
- Requests to
/api/**are forwarded toBACKEND_URL(required) Authorization: Bearer ${AUTH_TOKEN}is injected server-side whenAUTH_TOKENis set- Browser clients do not need to store or submit
AUTH_TOKEN - Web dev server runs on
http://localhost:5173(fixed), API server defaults tohttp://localhost:3000
Environment variables:
BACKEND_URL=http://localhost:3000
AUTH_TOKEN=your-api-token
WEB_PASSWORD=FFmpeg REST is open source but only accepting contributions for bug fixes.
