Async-first FFmpeg API for upload and URL-based media processing, audio activity analysis, and produ
1.3K
ffmpeg-api is an async-first HTTP API for FFmpeg maintained by Rods Company. It lets backend services upload a file or point to a URL, run a recipe of audio or video operations, and fetch the generated artifact either synchronously for small inputs or asynchronously through job polling.
It is built for practical media workflows such as:
FFmpeg is powerful, but wiring uploads, URL ingestion, polling, output storage, and safe runtime defaults into a service usually gets repeated in every project.
This API packages that into a single service with:
sync, async, and auto execution modessync, async, and auto execution modesghcr.io/rods-company/ffmpeg-api<your-dockerhub-namespace>/ffmpeg-apiGET /docs for the interactive Scalar API referenceGET /openapi.yaml for the raw OpenAPI contractGET / redirects to /docsGET /docs interactive API documentationGET /openapi.yaml raw OpenAPI documentGET /endpoints endpoint listing as JSONGET /v1/health service health and effective runtime settingsGET /v1/capabilities runtime FFmpeg, FFprobe, formats, codecs, and filtersPOST /v1/analyze/audio-activity/url heuristic analysis from a remote URLPOST /v1/analyze/audio-activity/upload heuristic analysis from an uploaded filePOST /v1/jobs/url create a processing job from JSON URL inputPOST /v1/jobs/upload create a processing job from multipart uploadPOST /v1/chunks/url split remote audio or video into chunks and return a ZIP file or async jobPOST /v1/chunks/upload split uploaded audio or video into chunks and return a ZIP file or async jobGET /v1/jobs/:jobId poll async job statePOST /v1/jobs/:jobId/cancel cancel a queued or running jobGET /v1/jobs/:jobId/artifact download the completed output fileapplication/jsonmultipart/form-datamode=auto|sync|async controls whether the request should try the synchronous pathENABLE_SYNC_SMALL_JOBS=trueanalysis.audioActivity can be enabled inside POST /v1/jobs/url and POST /v1/jobs/upload to avoid sending the same file twiceurl and upload routes for clearer contracts and better API documentationGET /v1/capabilities when you need the real formats, codecs, and filters available in the current deploymentBuild the image from the repository root:
docker build -t ffmpeg-api .
Run the container with default settings:
docker run --rm -p 3000:3000 --name ffmpeg-api ffmpeg-api
Run the container in background using .env.example as a base:
cp .env.example .env
docker run -d \
--name ffmpeg-api \
--env-file .env \
-p 3000:3000 \
ffmpeg-api
After the container starts:
http://127.0.0.1:3000/docshttp://127.0.0.1:3000/openapi.yamlhttp://127.0.0.1:3000/v1/healthInstall dependencies inside src/:
cd src
npm install
Start the API:
npm start
If you want custom settings locally, create src/.env or export variables before starting the server.
For Dockploy or any container platform that supports environment variables:
rodscompany/ffmpeg-api:latest or pin a version such as rodscompany/ffmpeg-api:1.1.03000ALLOW_PRIVATE_URLS=false unless you have a controlled private-network use caseExample Dockploy variables:
FFMPEG_API_IMAGE=rodscompany/ffmpeg-api:latestPUBLIC_PORT=3000EXTERNAL_PORT=3000PUBLIC_BASE_URL=https://ffmpeg-api.seudominio.comJOB_CONCURRENCY=2MAX_QUEUE_SIZE=100The compose file also mounts a named volume at /tmp/ffmpeg-api so temporary artifacts are not lost immediately when the container restarts.
The new v1 job API is configurable through environment variables and all of them have defaults. For Docker, Dockploy, or Compose-style deployments, see .env.exampleโ .
For the full explanation of every variable, including defaults, tradeoffs, and when to change them, see docs/environment-reference.mdโ .
SERVER_PORT=3000 internal HTTP port used by the serviceEXTERNAL_PORT=3000 external/public port exposed by the deploymentPUBLIC_BASE_URL= optional full public base URL used in startup links, for example https://ffmpeg-api.rods.companyENABLE_SYNC_SMALL_JOBS=true allows the API to process small requests synchronously instead of creating an async jobSYNC_MAX_INPUT_BYTES=10485760 maximum size in bytes for automatic synchronous processingJOB_CONCURRENCY=2 number of FFmpeg jobs processed in parallelMAX_QUEUE_SIZE=100 maximum queued async jobs before the API returns 429JOB_RETENTION_MS=900000 how long completed artifacts stay available for downloadJOB_TIMEOUT_MS=3600000 socket timeout for long-running requestsDOWNLOAD_TIMEOUT_MS=60000 timeout for remote URL downloadsURL_MAX_REDIRECTS=5 maximum redirects allowed when ingesting a URLALLOW_PRIVATE_URLS=false when true, the API accepts loopback and private network URLsJSON_BODY_LIMIT=1mb maximum JSON payload size for URL-based requestsAUDIO_ACTIVITY_NOISE_THRESHOLD_DB=-35 silence threshold used by the audio activity analyzerAUDIO_ACTIVITY_MIN_SILENCE_DURATION=0.5 minimum silence duration for the analyzerAUDIO_ACTIVITY_MIN_SEGMENT_DURATION=1.2 minimum active segment duration required to avoid being classified as background-onlyAUDIO_ACTIVITY_MIN_ACTIVE_RATIO=0.08 minimum active ratio required to avoid being classified as background-onlyAUDIO_ACTIVITY_USE_SPEECH_BAND=true applies a speech-band filter before silence detectionSHOW_STARTUP_BANNER=true enables the Rods Company startup banner and quick links in the server consoleInput media can be anything that FFmpeg supports. .ogg is a first-class supported output format in the API examples and tests.
When you need the actual formats, codecs, and filters supported by the current deployment, use GET /v1/capabilities. That endpoint is the runtime source of truth.
You can send media in two ways:
application/jsonmultipart/form-dataFor uploads, use a file field named file.
curl -X POST http://127.0.0.1:3000/v1/jobs/url \
-H "Content-Type: application/json" \
-d '{
"mode": "sync",
"input": {
"type": "url",
"url": "https://example.com/input.ogg"
},
"analysis": {
"audioActivity": true
},
"recipe": {
"output": {
"container": "ogg",
"filename": "output.ogg"
},
"operations": [
{
"type": "speed",
"factor": 1.2
}
]
}
}' > output.ogg
When analysis.audioActivity is enabled in synchronous mode, the API returns the generated file and includes the diagnostic summary in response headers such as:
X-Audio-Activity-Background-OnlyX-Audio-Activity-Contains-Speech-Like-ActivityX-Audio-Activity-Active-Ratiocurl -X POST http://127.0.0.1:3000/v1/jobs/upload \
-F "file=@./input.ogg" \
-F "mode=sync" \
-F "analysis={\"audioActivity\":true}" \
-F "recipe={\"output\":{\"container\":\"ogg\",\"filename\":\"output.ogg\"},\"operations\":[{\"type\":\"speed\",\"factor\":1.2}]}" \
-o output.ogg
curl -X POST http://127.0.0.1:3000/v1/jobs/url \
-H "Content-Type: application/json" \
-d '{
"mode": "async",
"input": {
"type": "url",
"url": "https://example.com/input.ogg"
},
"analysis": {
"audioActivity": true
},
"recipe": {
"output": {
"container": "ogg",
"filename": "output.ogg"
},
"operations": [
{
"type": "silence_trim"
},
{
"type": "speed",
"factor": 1.2
}
]
}
}'
curl -X POST http://127.0.0.1:3000/v1/chunks/url \
-H "Content-Type: application/json" \
-d '{
"mode": "sync",
"input": {
"type": "url",
"url": "https://example.com/input.ogg"
},
"output": {
"container": "ogg",
"filenamePrefix": "part",
"archiveName": "parts.zip"
},
"chunking": {
"strategy": "parts",
"parts": 4,
"paddingBeforeSeconds": 0.5,
"paddingAfterSeconds": 0.5
}
}' > parts.zip
curl -X POST http://127.0.0.1:3000/v1/chunks/url \
-H "Content-Type: application/json" \
-d '{
"mode": "async",
"input": {
"type": "url",
"url": "https://example.com/interview.ogg"
},
"output": {
"container": "ogg",
"filenamePrefix": "segment",
"archiveName": "segments.zip"
},
"chunking": {
"strategy": "silence",
"minSilenceDuration": 0.5,
"paddingBeforeSeconds": 0.2,
"paddingAfterSeconds": 0.2,
"minChunkDurationSeconds": 1,
"maxChunkDurationSeconds": 20
}
}'
The generated artifact is a .zip file containing each chunk plus a manifest.json with the chunk boundaries.
chunking.strategy is the field that selects the chunk mode:
parts divides the full media into equal parts and uses partsduration divides every fixed interval and uses segmentDurationsilence cuts around active segments and uses silence-related options such as minSilenceDurationmaxChunkDurationSeconds can be used with silence to keep chunks small enough for transcription without needing perfectly precise cutsparts and duration, paddingBeforeSeconds and paddingAfterSeconds tell the API to look for a nearby silence before doing a hard cutcurl -X POST http://127.0.0.1:3000/v1/chunks/upload \
-F "file=@./input.ogg" \
-F "mode=sync" \
-F "container=ogg" \
-F "filenamePrefix=part" \
-F "archiveName=parts.zip" \
-F "strategy=parts" \
-F "parts=4" \
-o parts.zip
curl -X POST http://127.0.0.1:3000/v1/chunks/upload \
-F "file=@./input.ogg" \
-F "mode=async" \
-F "container=ogg" \
-F "filenamePrefix=segment" \
-F "archiveName=segments.zip" \
-F "strategy=silence" \
-F "minSilenceDuration=0.5" \
-F "paddingBeforeSeconds=0.2" \
-F "paddingAfterSeconds=0.2" \
-F "minChunkDurationSeconds=1" \
-F "maxChunkDurationSeconds=20"
curl http://127.0.0.1:3000/v1/jobs/<jobId>curl http://127.0.0.1:3000/v1/jobs/<jobId>/artifact > output.oggWhen analysis.audioActivity is enabled for an async job, the diagnostic is attached to the job payload in the analysis.audioActivity field.
curl -X POST http://127.0.0.1:3000/v1/jobs/upload \
-F "file=@./input.ogg" \
-F "mode=async" \
-F "analysis={\"audioActivity\":true}" \
-F "recipe={\"output\":{\"container\":\"ogg\",\"filename\":\"output.ogg\"},\"operations\":[{\"type\":\"silence_trim\"},{\"type\":\"speed\",\"factor\":1.2}]}"
curl -X POST http://127.0.0.1:3000/v1/analyze/audio-activity/url \
-H "Content-Type: application/json" \
-d '{
"input": {
"type": "url",
"url": "https://example.com/input.ogg"
}
}'
This endpoint does not recognize speech. It uses a speech-band activity heuristic and returns fields such as:
hasAudioActivitylikelyBackgroundOnlylikelyContainsSpeechLikeActivitysilenceSegmentsactiveSegmentscurl -X POST http://127.0.0.1:3000/v1/analyze/audio-activity/upload \
-F "file=@./input.ogg" \
-F "options={\"minActiveRatio\":0.08,\"minSegmentDuration\":1.2}"
The repository is set up to automate releases from Conventional Commits.
main or master update a Release Please PR automatically1.2.3, 1.2, 1, and latestIf you use squash merge, the pull request title becomes especially important because it usually becomes the final commit message on the default branch.
silence_trim removes silent sections. It is not noise reduction.audio-activity is a heuristic analysis. It is not speech recognition.Originally developed by Paul Visco @surebertโ .
This project builds on ideas or implementations from:
Content type
Image
Digest
sha256:82cd829d2โฆ
Size
104.8 MB
Last updated
7 months ago
docker pull rodscompany/ffmpeg-api