REST API for converting audio, video and image files with FFmpeg. One container, one curl command, no FFmpeg installation on your machine.
docker run -d --name ffmpeg-api -p 3000:3000 kazhar/ffmpeg-api
curl -F "file=@input.wav" 127.0.0.1:3000/convert/audio/to/mp3 > output.mp3- Convert audio to MP3 and WAV, video to MP4, images to JPG.
- Extract the audio track or frames (PNG, optionally zipped) from a video.
- Probe any media file and get its metadata as JSON.
- Full OpenAPI 3.1 description with an interactive Swagger UI at
/docsand the machine readable document at/openapi.json. Generate clients in any language from it. - Built on Red Hat UBI, Python and FastAPI. FFmpeg is the latest release, compiled in the image build.
- Self-contained: the running container makes no external calls, and the API docs work offline.
- Runs as a non-root user, works on Kubernetes and OpenShift.
Note: There is no authentication. Run it in a trusted network or behind a reverse proxy.
docker run -d --name ffmpeg-api -p 3000:3000 kazhar/ffmpeg-apiOpen http://127.0.0.1:3000/docs to try the API in the browser.
The OpenAPI document at /openapi.json (and Swagger UI at /docs) is the reference for all request and response details. Summary:
| Method | Path | Description |
|---|---|---|
| GET | / |
Landing page with links to the docs. |
| GET | /docs |
Interactive API documentation (Swagger UI). |
| GET | /openapi.json |
OpenAPI document. |
| GET | /endpoints |
Service endpoints as JSON. |
| GET | /health |
Health check, returns {"status": "ok"}. |
| POST | /convert/audio/to/mp3 |
Convert audio to MP3. Returns the MP3 file. |
| POST | /convert/audio/to/wav |
Convert audio to WAV (16-bit PCM). Returns the WAV file. |
| POST | /convert/video/to/mp4 |
Convert video to a small web friendly MP4 (H.264, AAC). Returns the MP4 file. |
| POST | /convert/image/to/jpg |
Convert an image to JPG. Returns the JPG file. |
| POST | /video/extract/audio |
Extract the audio track of a video as WAV. Mono by default. mono=no keeps all channels. |
| POST | /video/extract/images |
Extract frames of a video as PNG, 1 per second by default. Returns JSON with download links. fps=<number> sets the frame rate. compress=zip|gzip returns a ZIP or tar.gz archive instead. |
| GET | /video/extract/download/{filename} |
Download an extracted image or archive. The file is deleted from the server after download. delete=no keeps it. |
| POST | /probe |
Probe a media file, returns JSON metadata (same as ffprobe -of json -show_streams -show_format). |
All POST endpoints take a multipart/form-data body with exactly one file in the form field file. The input can be anything FFmpeg supports, see the FFmpeg documentation.
Errors are JSON: {"error": "message"} with status 400 (bad request), 404 (not found), 413 (file too large), 422 (invalid query parameter) or 500 (FFmpeg failed).
curl -F "file=@input.wav" 127.0.0.1:3000/convert/audio/to/mp3 > output.mp3
curl -F "file=@input.m4a" 127.0.0.1:3000/convert/audio/to/wav > output.wav
curl -F "file=@input.mov" 127.0.0.1:3000/convert/video/to/mp4 > output.mp4
curl -F "file=@input.mp4" 127.0.0.1:3000/convert/video/to/mp4 > output.mp4
curl -F "file=@input.tiff" 127.0.0.1:3000/convert/image/to/jpg > output.jpg
curl -F "file=@input.png" 127.0.0.1:3000/convert/image/to/jpg > output.jpgThe MP4 conversion produces H.264 (high profile), 15 fps, 640 pixels high, about 500 kbit/s video and AAC 128 kbit/s audio.
# JSON with a download URL for each image (1 image per second)
curl -F "file=@input.mov" 127.0.0.1:3000/video/extract/images
# download an image (deleted from the server afterwards)
curl 127.0.0.1:3000/video/extract/download/ba0f565c-0001.png > image.png
# download an image and keep it on the server
curl "127.0.0.1:3000/video/extract/download/ba0f565c-0001.png?delete=no" > image.png
# all images as ZIP or tar.gz
curl -F "file=@input.mov" "127.0.0.1:3000/video/extract/images?compress=zip" > images.zip
curl -F "file=@input.mov" "127.0.0.1:3000/video/extract/images?compress=gzip" > images.tar.gz
# one image every 2 seconds (fps=0.5), or four images per second (fps=4)
curl -F "file=@input.mov" "127.0.0.1:3000/video/extract/images?fps=0.5"Example response, see also extracted_images.json:
{
"totalfiles": 2,
"description": "Extracted image files and URLs to download them. ...",
"files": [
{ "name": "ba0f565c-0001.png", "url": "http://127.0.0.1:3000/video/extract/download/ba0f565c-0001.png" },
{ "name": "ba0f565c-0002.png", "url": "http://127.0.0.1:3000/video/extract/download/ba0f565c-0002.png" }
]
}# mono WAV
curl -F "file=@input.mov" 127.0.0.1:3000/video/extract/audio > audio.wav
# WAV with all channels of the video
curl -F "file=@input.mov" "127.0.0.1:3000/video/extract/audio?mono=no" > audio.wavcurl -F "file=@input.mov" 127.0.0.1:3000/probeReturns the same JSON as ffprobe -of json -show_streams -show_format input.mov. See probe_metadata.json.
Set environment variables with docker run -e NAME=value.
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
info |
Log level: debug, info, warning, error. |
FILE_SIZE_LIMIT_BYTES |
536870912 (512 MB) |
Maximum size of an uploaded file. Larger uploads get HTTP 413. |
KEEP_ALL_FILES |
false |
Set to true to keep uploaded and generated files in the container's /tmp (for debugging). |
EXTERNAL_PORT |
3000 |
Port used in the download URLs returned by the extract images endpoint. Set it when the published port differs from 3000. |
docker run -d -p 3001:3000 -e EXTERNAL_PORT=3001 -e LOG_LEVEL=debug kazhar/ffmpeg-apiUploads and conversions run for as long as FFmpeg needs; connections stay open for up to an hour.
git clone https://github.com/samisalkosuo/ffmpeg-api.git
cd ffmpeg-api
docker build -t ffmpeg-api .The build compiles FFmpeg (with libx264 and libmp3lame) on UBI, so the first build takes several minutes. Use --build-arg BUILD_JOBS=8 to compile faster. Everything is downloaded at build time; the running container needs no network access.
- New UBI release: change
UBI_IMAGE,UBI_MINIMAL_IMAGE(andPYTHON_VERSIONif needed) at the top of the Dockerfile. - New FFmpeg release: rebuild.
FFMPEG_VERSION=latest(default) looks up the newest release on ffmpeg.org when building. Pin one with--build-arg FFMPEG_VERSION=9.0.2.
python -m venv .venv && . .venv/bin/activate
pip install -r requirements-dev.txt
ruff check . && pytest # media tests need ffmpeg and ffprobe on PATH
# or run the whole test suite in a container
docker build --target test -t ffmpeg-api-test . && docker run --rm ffmpeg-api-testTests generate their input media with FFmpeg, no media files are stored in the repository.
The container runs as a non-root user and works with arbitrary user IDs. A minimal deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ffmpeg-api
spec:
replicas: 1
selector:
matchLabels: { app: ffmpeg-api }
template:
metadata:
labels: { app: ffmpeg-api }
spec:
containers:
- name: ffmpeg-api
image: kazhar/ffmpeg-api
ports:
- containerPort: 3000
readinessProbe:
httpGet: { path: /health, port: 3000 }
resources:
limits: { cpu: "2", memory: 1Gi }The API paths, parameters and response formats are unchanged. Differences:
- MP4 audio is encoded with FFmpeg's native AAC encoder (
libfdk_aacis not freely redistributable). - Errors are JSON
{"error": "..."}. - Files above the size limit return 413 (was 500).
- New: OpenAPI document, Swagger UI,
/health.
The Node.js version is kept on a separate legacy branch in this repository. See CHANGES.md.
Originally developed by Paul Visco (docker-ffmpeg-service). Version 2 is a rewrite using Python, FastAPI, Red Hat UBI and a source-built FFmpeg.
Licensed under the MIT License. The image contains FFmpeg built with GPL components (libx264), which are licensed under their own terms.