Video Transcoding Isn't a Bitrate Ladder. It's One File at Spec

Search for a definition of transcoding and you get an adaptive bitrate ladder, an HLS manifest, and a CDN diagram. That's the right answer for someone building a streaming product. It's the wrong answer if what you need is one MP4 at platform spec, out the far end of a workflow that runs four hundred times a month.
Quick answer: Video transcoding is decoding compressed video back to raw frames and re-encoding it with different settings. Five stages: demux the container, decode the streams, filter (scale, pad, change frame rate, tone-map), re-encode with a codec such as H.264, then remux into a new container. Streaming services run that pipeline many times per input to build a bitrate ladder; most automation pipelines run it once, to get a single file at a platform's required resolution, codec, and bitrate. FFmpeg does both from the command line, and a hosted video API such as FFmpeg Micro does the same job as one HTTP request with no encoder to host.
Transcoding is five operations, and the codec is only one of them
"Converting the format" describes the file extension, not the work. The work is five distinct operations, and knowing which one you're asking for stops you paying for a re-encode you didn't need.
- Demux. The container (MP4, MOV, MKV, WebM) is opened and split into elementary streams: one video, one or more audio, maybe subtitles and chapters.
- Decode. Compressed packets become raw frames in memory, usually YUV 4:2:0. This is where the CPU goes.
- Filter. Scale to 1080x1920, pad or crop to a new aspect ratio, resample 60 fps down to 30, tone-map HDR to SDR, overlay a logo, burn in subtitles.
- Encode. Raw frames go back out through libx264, libx265, or an AV1 encoder at a target CRF or bitrate. This is a second lossy pass over material that was already lossy.
- Mux. The new streams are written into a container, with the MP4 index up front if you want playback to start before the download finishes.
Stream copy skips stages two through four entirely. ffmpeg -i in.mkv -c copy out.mp4 moves the existing compressed packets into a new container with no decode and no quality loss, and finishes in seconds where a re-encode takes minutes. That's a remux, not a transcode, and it's the right call more often than people think. It can't be combined with filters, which is how builders usually find out the difference the hard way: filtering and stream copy cannot be used together.
Most pipelines need one file at spec, not a bitrate ladder
Streaming platforms transcode one input into many outputs: roughly 360p at 400 kbps through 4K at 15 Mbps, segmented into .ts chunks with an .m3u8 manifest so a player can switch rungs mid-playback. Cloudinary's guide lays that ladder out rung by rung, like most other explainers.
That ladder exists to serve strangers on unknown networks from your own player. If you're uploading to TikTok, Instagram, YouTube, or Meta's ad platform, the platform builds its own ladder after ingest. Your job is to hand it one file that matches the ingest spec: right container, right codec profile, right resolution, right audio sample rate. Getting that wrong is what degrades the result, which is the argument behind Facebook video specs aren't the problem, your encode is.
The same two words cover opposite jobs: packaging and delivery for one reader, a deterministic file-in, file-out job for the other. The rest is for the second one.
The same transcode, three ways
Here is one concrete job: a 1920x1080 MOV from a screen recorder, out as a vertical 1080x1920 MP4 at 30 fps, H.264 High profile, AAC audio at 48 kHz, ready for Reels.
As an FFmpeg command
Every stage above shows up in this one invocation, which is the clearest proof that a transcode is a pipeline, not a setting:
ffmpeg -i input.mov \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,\
pad=1080:1920:(ow-iw)/2:(oh-ih)/2,fps=30" \
-c:v libx264 -profile:v high -crf 21 -preset medium -pix_fmt yuv420p \
-c:a aac -b:a 128k -ar 48000 \
-movflags +faststart output.mp4
-vf is the filter stage, -c:v libx264 is the encode, -movflags +faststart moves the MP4 index to the front of the file. Drop -pix_fmt yuv420p and the output may refuse to play in Safari or QuickTime even though it's a valid H.264 file.
As one REST job
The same job over HTTP is a submit, a poll, and a download. Point the API at a URL it can read, or get a presigned upload URL if the file is on your laptop:
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [{"url": "https://storage.example.com/input.mov"}],
"outputFormat": "mp4",
"options": [
{"option": "-vf", "argument": "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,fps=30"},
{"option": "-c:v", "argument": "libx264"},
{"option": "-crf", "argument": "21"},
{"option": "-pix_fmt", "argument": "yuv420p"}
]
}'
The response comes back with a job ID and "status": "pending". GET /v1/transcodes/:id returns completed or failed along with started_at, lane, and queue_position, and GET /v1/transcodes/:id/download hands back a signed URL. Status checks, job listing, and downloads are all free. That's the whole integration: the FFmpeg API is the same five-stage pipeline with nobody maintaining the binary.
If you prefer presets to flags, swap the options array for "preset": {"quality": "medium", "resolution": "1080p"} and skip the FFmpeg syntax entirely.
As an HTTP Request node in n8n
In n8n, Make, or Zapier, the transcode is an HTTP Request node with that JSON body, followed by either a Wait-and-poll loop or a webhook callback. The polling version is three nodes; the webhook version is two workflows and scales better once jobs run longer than a minute, common for long-running video jobs.
This path exists because the alternative stopped working. n8n disabled the Execute Command node by default in v2.0, the distroless image dropped the shell, and the 3.0 breaking-changes list removes the Read/Write Binary File operations and cuts the task runner timeout from five minutes to one, which is shorter than most real transcodes. If you're still shelling out to a local binary, your FFmpeg step needs HTTP.
More than 300 builders have run transcodes from Make or n8n, out of over 700 developers and automation builders on the platform. Roughly a third arrived through a no-code tool and wouldn't call themselves developers. That demand isn't an OTT shape.
The billing unit matters more than the rate
Most explainers define the word and hand you off to a pricing page without naming the unit you'll be charged in. The unit is where the surprises live.
AWS Elemental MediaConvert, the reference for file-based transcoding at scale, bills per minute of output video in normalized minutes, with multipliers for resolution, frame rate, and features, and a ten-second minimum per output. Ask for four social cuts from one input and you pay four times, each short cut rounded up. That makes sense for a ladder, not for one file in and one file out.
FFmpeg Micro bills on the duration of the video you process, not on server time:
- Per second, with no rounding up to the minute. A 10 second video costs 10 seconds.
- Trim a 30 second clip out of a one hour recording and you're billed for the 30 seconds, not the hour.
- Failed jobs are never billed.
One token buys 10 seconds of input, metered per second. A $5 top-up of 500 tokens is 5,000 seconds of input video, about 83 minutes. The free 200 tokens are 2,000 seconds, about 33 minutes.
| Plan | Price | Monthly tokens | Max input file |
|---|---|---|---|
| Free | $0 | 200 once, at sign-up | 100 MB |
| Starter | $19.99 / month | 2,500 | 1,024 MB |
| Pro | $49.99 / month | 7,000 | 2,048 MB |
The free tier needs no card and includes API access, unlimited API keys, and unlimited storage. It doesn't include customer support, token management, automatic top-up, or token banking; those start at Starter. Top-ups work on any plan including Free (500 for $5, 1,200 for $10, 3,000 for $25), and purchased tokens don't expire while the account is open.
Pitfalls that cost people an afternoon
Transcode jobs fail in the same handful of ways, none of them exotic.
- Re-encoding when a remux would do. Changing container only? Use
-c copy. Seconds instead of minutes, and no generation loss. - Odd output dimensions. H.264 with yuv420p needs even width and height. A crop that produces 1079 pixels fails with a divisible-by-two error.
- Filters plus stream copy. You can't scale and copy the same stream. FFmpeg's "Impossible to convert between the formats" error is usually a format negotiation problem, not a bad filter: fix the format, not the filter.
-filter_complexin API options. The advanced options array doesn't support it. Use-vffor single-stream filtering, or the dedicated overlay and text options.- Sending
fileSizeas a string. The presigned upload endpoint wants a JSON number of bytes."1024"returns a 400 with a validation error. - Shipping an encode you never measured. CRF 28 at 480p will look fine in a 200-pixel-wide preview and terrible full screen. Score it with VMAF before it goes out at volume: check your encode didn't wreck the video.
When a single output file genuinely isn't enough
A hosted job API is the wrong tool when you're the one serving playback. If you're delivering a 90 minute film to phones on variable cellular, you need a real ladder, segment packaging, a CDN, a player with analytics, and probably DRM. That's a managed streaming platform's job, not a per-job transcode endpoint. FFmpeg Micro can package HLS variants, but it doesn't deliver them, doesn't run a player, and isn't an editing UI.
The honest DIY picture: Cloudflare published a reference serverless media build in October 2026 whose moving parts are a Go media engine running FFmpeg in a container, plus a worker and a durable object owning session lifecycle, orchestration, and access control. That's what you maintain if you run your own. Reasonable to own at high enough volume. Unreasonable to own because one workflow step needs a 9:16 MP4.
FAQ
Is transcoding the same thing as converting a video file?
Transcoding is one kind of conversion, the expensive kind: it decodes the video to raw frames and re-encodes it, so quality drops and the CPU works hard. Remuxing, also called stream copy or transmuxing, rewraps the existing compressed frames in a new container with no decode, no quality loss, and a runtime measured in seconds.
Do I need an ABR ladder and HLS to post video to social platforms?
No. TikTok, Instagram, YouTube, and Meta's ad platform all build their own bitrate ladders after you upload, so a single file matching each platform's ingest spec is the whole requirement. You need a ladder only when you own the player and the delivery.
If I only need 30 seconds out of a one-hour recording, am I billed for the hour?
You're billed for the 30 seconds. FFmpeg Micro meters the duration actually processed, per second, with no rounding up to the nearest minute, so a trim is priced as the trim. Failed jobs are never billed.
How do I transcode video in n8n without rebuilding my Docker image?
Replace the Execute Command node with an HTTP Request node that posts a transcode job to a video API, then poll the job status or receive a webhook when it completes. No binary in the image, no shell, and nothing that breaks when n8n changes its runtime defaults.
The five-stage pipeline without an encoder to own: the FFmpeg API is one POST, one poll, and one download, and the first 200 tokens come with the account, not a credit card form.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

How to Upscale Video with FFmpeg (and When It Actually Helps)
FFmpeg upscale video the honest way: the lanczos scale command for 1080p and 4K, the bitrate math that decides how it looks, and when no scaler helps.

Frame counts lie. FFmpeg progress percentage is in out_time_us
Parse FFmpeg's -progress output for a real ffmpeg progress percentage: out_time_us against an ffprobe duration, speed= for ETA, and where parsing breaks.

scale_npp Removed in FFmpeg 9: Move GPU Scaling to scale_cuda
scale_npp removed in FFmpeg 9.0 along with all libnpp filters. The scale_cuda rewrite for every -vf chain, plus what a rented GPU actually costs you now.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free