ffmpegvideo-encoding

Stop Re-Exporting. Encoding a Video Once Covers Every Platform.

·Javid Jamae·10 min read
Stop Re-Exporting. Encoding a Video Once Covers Every Platform.

You finished the edit, and now the same video has to land on your site, in an email, on an old Android tablet in a lobby, and on three social platforms with three different aspect ratios. So you go back to the timeline and export again. Six times, every week, from a source file that keeps getting worse each round trip.

Quick answer: Encoding a video for several destinations works best as one master plus derived versions. Encode a single high-quality mezzanine with ffmpeg -i source.mov -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -c:a aac -b:a 192k -movflags +faststart master.mp4, then transcode that one file into each delivery rung: H.264 High at CRF 22 for 1080p web, H.264 Baseline 720p for maximum device compatibility, and 9:16 or 1:1 crops for social. Running those encodes yourself means installing FFmpeg builds and babysitting long jobs, so if you'd rather skip that, send the master to FFmpeg Micro and get every rendition back from one API call on the free tier.

One master file replaces per-platform exports

Conventional wisdom says every destination needs its own settings, so you re-export from the edit for each one. That's partly right: the settings really do differ per destination. But the expensive mistake isn't the settings, it's the source. Most people encode each delivery file from whatever they exported last, which stacks generation loss, or from the camera original, which means moving 40 GB around six times.

The fix is a mezzanine: one visually lossless intermediate that every delivery version is derived from. You encode the hard, slow, quality-critical pass exactly once. After that, each destination is a cheap transcode from an already-clean 8-bit 4:2:0 H.264 file, which decodes fast and reads consistently no matter what tool touches it next.

Here's the master. Encode it once, keep it, never post it anywhere.

ffmpeg -i source.MOV \
  -map 0:v:0 -map 0:a:0 \
  -c:v libx264 -crf 18 -preset slow \
  -profile:v high -level 4.2 -pix_fmt yuv420p \
  -g 48 -keyint_min 48 \
  -c:a aac -b:a 192k -ar 48000 \
  -movflags +faststart master.mp4

CRF 18 with -preset slow is the point where most viewers stop seeing a difference from the original on normal content. Going to CRF 16 roughly doubles the file for gains you'll only find in a frame-by-frame comparison of gradients and grain. The -g 48 keyframe interval (2 seconds at 24 fps) matters later, because clean cuts and platform re-encodes both behave better with regular keyframes.

The delivery rungs, and the CRF and preset for each

Each destination gets one rung derived from the master, and the differences between rungs are resolution, H.264 profile, and CRF. Three rungs cover almost every case: a 1080p web version, a small mobile version, and a maximum-compatibility version for players you don't control.

RungResolutionProfile / levelCRFPresetAudioWhere it goes
MezzanineSourceHigh 4.218slowAAC 192kArchive, source for everything below
Web 1080p1920x1080High 4.022mediumAAC 128kYour site, embeds, landing pages
Web light1280x720Main 3.124mediumAAC 96kMobile web, slow connections
Compatibility1280x720Baseline 3.023mediumAAC 128k, 44.1 kHzOld Android, kiosks, embedded players
Social1080x1920High 4.020slowAAC 128kReels, TikTok, Shorts

The web rung is two flags away from the master:

ffmpeg -i master.mp4 -vf "scale=-2:1080" \
  -c:v libx264 -crf 22 -preset medium \
  -profile:v high -level 4.0 -pix_fmt yuv420p \
  -c:a aac -b:a 128k -movflags +faststart web-1080.mp4

The compatibility rung is the one people get wrong. Baseline profile drops CABAC entropy coding and B-frames, which costs roughly 10 to 15 percent more bitrate for the same visual quality, so it's the wrong default. It's the right choice only when something in the chain is genuinely old: an Android 4.x device, a hardware digital-signage player, a set-top browser.

ffmpeg -i master.mp4 -vf "scale=-2:720" \
  -c:v libx264 -crf 23 -preset medium \
  -profile:v baseline -level 3.0 -pix_fmt yuv420p \
  -c:a aac -b:a 128k -ar 44100 -ac 2 \
  -movflags +faststart compat-720.mp4

Preset choice is a wall-clock decision, not a quality decision. On a four-core laptop, -preset slow at 1080p runs near realtime while -preset veryfast runs several times faster and costs you 10 to 20 percent more bitrate at matched quality. Use slow on the mezzanine and on anything a platform will re-encode after you (social), and medium on rungs you serve directly, where you can just spend the extra megabytes. There's more on the bitrate side of that trade in compress video for the web.

Social crops come from the master too, not from a re-export

Platform versions are a framing change plus a re-encode, and both should start from the mezzanine so the crop is applied to clean pixels. A center crop to 9:16 from a 16:9 master takes the full height and one ninth-sixteenths of the width:

ffmpeg -i master.mp4 \
  -vf "crop=ih*9/16:ih,scale=1080:1920:flags=lanczos" \
  -c:v libx264 -crf 20 -preset slow -pix_fmt yuv420p \
  -c:a aac -b:a 128k -movflags +faststart vertical.mp4

When cropping would cut off a face or a product, pad instead of crop. This is the 1:1 feed version with letterbox bars:

ffmpeg -i master.mp4 \
  -vf "scale=1080:-2,pad=1080:1080:(ow-iw)/2:(oh-ih)/2:color=black" \
  -c:v libx264 -crf 20 -preset slow -pix_fmt yuv420p \
  -c:a aac -b:a 128k -movflags +faststart square.mp4

Encode social rungs a little richer than you think you need, around CRF 20 rather than 23. Every platform re-encodes your upload, and it re-encodes whatever you gave it, so compression artifacts you ship get baked in and then compressed again. Platform-specific targets for one common case are in best video settings for Instagram Reels.

Five things that break this pipeline

Most failures in a one-master, many-rungs setup come from a handful of repeat offenders, and every one of them has a one-flag fix.

  1. Odd dimensions. scale=-1:1080 can produce an odd width, and libx264 with yuv420p refuses it. Use -2 instead of -1 so FFmpeg rounds to an even number.
  2. Missing -pix_fmt yuv420p. A 10-bit or 4:2:2 source carries its format straight through, and the output plays fine in VLC and not at all in Safari or QuickTime.
  3. Missing -movflags +faststart. The moov atom lands at the end of the file, so browsers download the whole thing before the first frame appears.
  4. Variable frame rate sources. Screen recorders from OBS and phone screen capture write VFR, and audio drifts over long clips. Add -vsync cfr -r 30 when transcoding the mezzanine.
  5. Re-encoding a delivery file. Making the square version from vertical.mp4 instead of master.mp4 stacks two lossy passes. Every rung comes from the mezzanine, always.

Audio is worth one pass of its own. If your clips come from different shoots, normalize loudness on the master before deriving anything, because fixing it per rung means fixing it five times. The loudness normalization recipe covers the two-pass loudnorm approach.

Where encoding once is the wrong plan

A single set of progressive MP4 files is the right answer for marketing video, social clips, product pages, and anything under a few minutes. Long-form playback for a large audience is different: a viewer on a bad connection needs the player to switch versions mid-stream, which means an adaptive ladder packaged as HLS or DASH, and that's a job for a managed streaming pipeline rather than a folder of MP4s. Live video is out of scope entirely. So is per-viewer personalization, where thousands of near-identical renders differ by a name field, which is what a template rendering service exists for.

The same ladder as one API call

Everything above is four FFmpeg commands, and four commands is fine on your laptop. It stops being fine when it becomes a queue: a 20-minute source, -preset slow, six outputs, running on a box that has to stay awake, inside an automation tool that times out at 30 seconds. That's the point where the encoder should not live on your machine at all.

FFmpeg Micro takes a source URL and gives back the renditions, with no binaries to install and no server to keep alive. You submit a job, get a job ID, and take a webhook when the output is ready, which works the same whether the caller is your backend, an n8n or Make workflow, or an AI agent through the MCP server. The FFmpeg API overview has the job model, and if the exact task is one file into four platform-ready variants (9:16, 1:1, 4:5, and a 16:9 that passes Amazon's specs), the Listing Kit blueprint is that ladder as an upload-and-download step. For a whole folder at once, batch transcoding with one workflow shows the queue pattern.

FAQ

What settings should I use for encoding a video for the web?

For general web delivery, encode H.264 High profile at level 4.0, 1920x1080, CRF 22, -preset medium, -pix_fmt yuv420p, AAC audio at 128 kbps, and -movflags +faststart. That combination plays in every current browser and starts before the download finishes. Drop to 1280x720 at CRF 24 for a lighter mobile version.

Is CRF or two-pass bitrate better for encoding a video?

CRF is better for files you deliver yourself, because it spends bits where the content needs them and holds quality constant across a mixed library. Two-pass with a target bitrate is better when you have a hard size or bandwidth ceiling to hit, like a fixed-bitrate ad spec or an adaptive ladder rung that must sit at exactly 3 Mbps.

Do I have to re-encode a video separately for every social platform?

You don't need a separate edit export per platform, but you do need a separate encode per aspect ratio. Instagram Reels, TikTok, and YouTube Shorts all take the same 1080x1920 H.264 file, so one vertical rendition covers all three, while a 1:1 feed post and a 16:9 landscape version each need their own crop or pad pass from the master.

How long does encoding a video actually take?

Encoding a 1080p video with libx264 at -preset medium runs at roughly one to three times realtime on a modern four-core CPU, so a 10-minute video takes about 3 to 10 minutes per output. -preset slow is two to three times slower than that, and 4K sources multiply everything by four, which is why a six-rung ladder for a long video is an overnight job on a laptop and a parallel job on a service.

Can I encode video without installing FFmpeg?

You can send the file to a hosted encoder instead of running the binary locally. FFmpeg Micro exposes transcoding, cropping, captions, and watermarks as REST calls at api.ffmpeg-micro.com, so a workflow in n8n, Make, or Zapier can produce every rendition without an FFmpeg install anywhere in the chain. Installing it locally is still worth doing for one-off experiments, and the install guides cover each platform.

Build the mezzanine command once for your own footage, get it looking right, then hand the ladder off. Sign up free and run the first set of renditions against a real file to see what comes back.

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.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Skip the command line

The Listing Video Kit blueprint runs the same job for you: upload, done.

Run it (free)