DEV Community

王康
王康

Posted on

A Reproducible Video Compress Workflow for Web Delivery

A video can look perfectly reasonable on a laptop and still be a poor delivery asset. The file may exceed an upload limit, take too long to start on mobile, or consume far more bandwidth than the page around it.

A useful video compress workflow does not begin with the strongest preset. It begins with a delivery constraint: where the file will play, how large it may be, and which details must survive encoding.

This guide builds a repeatable workflow around FFmpeg. It covers source inspection, resolution decisions, constant-quality encoding, target-size encoding, and output validation.

TL;DR

  • Inspect the source before changing it.
  • Remove unused duration before reducing quality.
  • Use CRF when visual quality matters more than an exact file size.
  • Use a bitrate budget and two-pass encoding when a hard upload limit matters.
  • Test the result in the real playback environment.

Start with the delivery constraint

The same source needs different treatment depending on where it is going. A product demo embedded in documentation has different requirements from a video attached to a support ticket.

Delivery case Primary constraint What to verify
Web page preview Startup time and transfer size Playback on a slower network profile
Support or email attachment Hard file-size limit Final size with a safety margin
UI or code walkthrough Text readability Small labels, cursors, and terminal output
Social upload Platform compatibility Aspect ratio, audio sync, and platform re-encoding
Long-term archive Source fidelity Keep the original instead of optimizing only for delivery

Write down the constraint before choosing an encoder setting. “Make it smaller” is not testable. “Keep the file below 95 MB while preserving readable 1080p UI text” is.

Inspect the source with ffprobe

Do not guess what is inside the file. Read its duration, dimensions, frame rate, codecs, and bitrate first:

ffprobe -v error \
  -show_entries format=duration,size,bit_rate \
  -show_entries stream=index,codec_type,codec_name,width,height,r_frame_rate,bit_rate \
  -of json \
  input.mp4
Enter fullscreen mode Exit fullscreen mode

The output answers several useful questions:

  • Is the source 4K even though it will render in a 900-pixel content column?
  • Is a screen recording stored at 60 fps when 30 fps is sufficient for the motion it contains?
  • Is an unexpectedly large audio track consuming part of the budget?
  • Has the file already been heavily compressed?

Re-encoding an already damaged file cannot restore missing detail. Keep the original and generate each delivery version from that source.

Estimate where the bytes come from

For a quick estimate, file size is mostly a function of total bitrate and duration:

file size (MB) ≈ total bitrate (Mbps) × duration (seconds) ÷ 8
Enter fullscreen mode Exit fullscreen mode

A ten-minute file with a total bitrate of 12 Mbps will be roughly 900 MB before container overhead is considered. That explains why resolution alone is not a reliable predictor of size. Two 1080p files with different bitrates can have very different weights.

Duration is the least destructive variable to reduce. Trim dead air, duplicate takes, loading screens, and long static endings before touching image quality.

Use CRF when quality is the main constraint

For most web previews and documentation videos, an exact byte count is less important than stable visual quality. The Constant Rate Factor mode in libx264 is a practical starting point:

ffmpeg -i input.mp4 \
  -c:v libx264 \
  -preset slow \
  -crf 23 \
  -c:a aac \
  -b:a 128k \
  -movflags +faststart \
  output.mp4
Enter fullscreen mode Exit fullscreen mode

The important options are:

  • -crf 23 controls the quality target. Lower values retain more detail and usually produce larger files. A practical test range is 18–28.
  • -preset slow gives the encoder more time to find efficient representations. Presets change encoding effort, not the requested CRF quality level.
  • -b:a 128k sets the audio bitrate. Speech-heavy material may not need the same audio budget as music.
  • -movflags +faststart moves MP4 metadata toward the beginning of the file, which helps progressive web playback start sooner.

Run a short representative segment first. A screen recording with small text may need a lower CRF value than a talking-head clip with a simple background.

ffmpeg -ss 00:02:00 -t 30 -i input.mp4 \
  -c:v libx264 -preset slow -crf 23 \
  -c:a aac -b:a 128k \
  sample.mp4
Enter fullscreen mode Exit fullscreen mode

The command starts two minutes into the source and encodes a 30-second sample. Choose a section containing motion, text, gradients, and the details that matter to the viewer.

Scale only when the playback size justifies it

Keeping 4K pixels for a small documentation embed wastes bandwidth. On the other hand, reducing a UI walkthrough too aggressively can make labels unreadable.

To create a 1080p version while preserving the aspect ratio:

ffmpeg -i input.mp4 \
  -vf "scale=-2:1080" \
  -c:v libx264 -preset slow -crf 23 \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  output-1080p.mp4
Enter fullscreen mode Exit fullscreen mode

The -2 lets FFmpeg calculate a compatible width from the original aspect ratio. Judge the result at the size users will actually see. Zooming to 400% can reveal artifacts, but it does not tell you whether the delivered experience is acceptable.

Build a bitrate budget for a hard size limit

CRF does not promise a specific output size. When an upload form rejects anything above a fixed limit, calculate a bitrate budget instead:

target total bitrate (kbps)
  ≈ target size (MB) × 8192 × 0.97 ÷ duration (seconds)
Enter fullscreen mode Exit fullscreen mode

The 0.97 factor leaves a small allowance for container overhead. Subtract the planned audio bitrate to estimate the video bitrate.

This small Node.js script makes the calculation repeatable:

// bitrate-budget.mjs
const [targetMB, durationSeconds, audioKbps = "128"] = process.argv.slice(2);

if (!targetMB || !durationSeconds) {
  console.error("Usage: node bitrate-budget.mjs <targetMB> <durationSeconds> [audioKbps]");
  process.exit(1);
}

const totalKbps = (Number(targetMB) * 8192 * 0.97) / Number(durationSeconds);
const videoKbps = Math.floor(totalKbps - Number(audioKbps));

if (videoKbps <= 0) {
  throw new Error("The target size is too small for the selected audio bitrate.");
}

console.log({
  targetMB: Number(targetMB),
  durationSeconds: Number(durationSeconds),
  audioKbps: Number(audioKbps),
  videoKbps,
});
Enter fullscreen mode Exit fullscreen mode

For a 600-second video targeting 95 MB with 128 kbps audio:

node bitrate-budget.mjs 95 600 128
Enter fullscreen mode Exit fullscreen mode

The calculation produces a video budget of about 1130 kbps. Use that value with two-pass encoding when final size is more important than encoding speed:

# Pass 1: analyze the video stream
ffmpeg -y -i input.mp4 \
  -c:v libx264 -b:v 1130k -pass 1 \
  -an -f mp4 /dev/null

# Pass 2: write the final video and audio
ffmpeg -i input.mp4 \
  -c:v libx264 -b:v 1130k -pass 2 \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  output-95mb.mp4
Enter fullscreen mode Exit fullscreen mode

On Windows, replace /dev/null with NUL. The result can still vary slightly, so leave headroom instead of targeting the platform limit exactly.

Validate more than the final size

An effective video compress pass preserves the information the viewer needs. Check the output in motion, not only through a single thumbnail.

Inspect these areas:

  • text, cursor edges, and thin interface lines;
  • faces, hair, foliage, and other fine detail;
  • gradients, shadows, and dark scenes;
  • fast movement and scene changes;
  • audio sync at the beginning, middle, and end;
  • seeking and startup behavior in the target browser or app.

Run ffprobe on the output as well:

ffprobe -v error \
  -show_entries format=duration,size,bit_rate \
  -show_entries stream=codec_type,codec_name,width,height,r_frame_rate \
  -of json \
  output.mp4
Enter fullscreen mode Exit fullscreen mode

For automated pipelines, record the command, source checksum, output size, duration, and encoder version in a manifest. That turns a one-off optimization into a reproducible build step.

Where a browser tool fits

FFmpeg is a better foundation when the process must be scripted, reviewed, or repeated across many files. A browser workflow can still be useful for an occasional comparison when no local media environment is available.

For example, a non-sensitive sample can be processed in VideoCompress and compared with the local FFmpeg output. Judge both versions using the same source segment and the same playback conditions. Confidential recordings and unreleased material should remain in an approved environment.

The tool is only a comparison point here; the workflow and validation criteria remain the same regardless of implementation.

Common mistakes

  • Starting with the strongest preset: optimize for the delivery constraint, not the largest reduction percentage.
  • Re-encoding the previous output: return to the original source for every experiment to avoid cumulative damage.
  • Changing several variables at once: test CRF, scale, frame rate, and audio settings separately when diagnosing quality loss.
  • Ignoring short motion samples: a static frame can hide blocking, smearing, and temporal artifacts.
  • Targeting the exact upload ceiling: reserve a few megabytes for bitrate variation and container overhead.
  • Keeping only the optimized copy: delivery files are not substitutes for an archive-quality original.

Pre-publish checklist

  • [ ] The delivery size, playback environment, and quality requirement are written down.
  • [ ] The source duration, resolution, frame rate, codecs, and bitrate were inspected.
  • [ ] Unused duration was removed before lowering quality.
  • [ ] A representative short segment was tested first.
  • [ ] The output was reviewed at its actual display size.
  • [ ] Text, motion, gradients, and audio sync were checked.
  • [ ] The final file has headroom below the upload limit.
  • [ ] Browser startup and seeking were tested where relevant.
  • [ ] The untouched source remains stored separately.

Final thoughts

A reliable video compress workflow is a constraint-solving process, not a race toward the smallest file. Inspect the source, choose either a quality target or a size budget, test a representative segment, and validate the result where it will actually play.

Once those decisions are explicit, they can move into a script or build pipeline. That is the point where video delivery stops being a collection of guesses and becomes a repeatable engineering task.

Top comments (0)