Video bitrate guide: what to render for 1080p, 4K and the web

Contents
Contents

Video bitrate is the one number that sets both how good a video looks and how big the file is. Pick it too low and motion turns into blocks. Pick it too high and every render costs more storage, more bandwidth and a longer upload for no visible gain.

This guide covers the formula that links bitrate to file size, the 1080p and 4K targets YouTube and Vimeo publish, and what the five Shotstack quality presets produce when you measure them.

The last section has a short Python script that renders one clip at every preset and reads the bitrate back from the API, so you can run the same test on your own footage.

TL;DR

  • Bitrate is data per second of video. File size in bytes = bitrate in bits per second × seconds ÷ 8.
  • YouTube’s targets for standard dynamic range: 8 Mbps for 1080p and 35 to 45 Mbps for 4K at 24 to 30 fps, 50 per cent more at 48 to 60 fps.
  • Vimeo asks for variable bitrate in a range: 10 to 20 Mbps for 1080p and 30 to 60 Mbps for 4K, or a constant rate factor of 18 or lower.
  • The Shotstack medium preset rendered a 1080p clip at 8.2 Mbps, which lands on YouTube’s 1080p target. high and veryhigh both came out at about 18.4 Mbps. verylow produced a larger file than low.
  • The Edit API has no bitrate field. You choose a quality preset, and the encoder sets the bitrate.

What video bitrate is and how it sets file size

Bitrate is the amount of data the encoder spends on each second of video. It is written in kilobits per second (kbps, 1,000 bits per second) or megabits per second (Mbps, 1,000,000 bits per second). Every second of playback is a stream of bits, so the bitrate sets both how much detail each frame keeps and how large the file ends up.

The file size follows from the bitrate and the duration:

file_size_bytes = bitrate_bps × duration_seconds ÷ 8

A ten minute 1080p video at 8 Mbps works out to 8,000,000 × 600 ÷ 8 = 600,000,000 bytes, or 600 MB.

One thing to watch when you calculate this in code: encoders and network tools use decimal units (1 MB = 1,000,000 bytes), while most file systems report binary units (1 MiB = 1,048,576 bytes). The same file reads about 7 per cent smaller in binary units. This guide uses decimal units throughout.

Resolution and frame rate decide how many bits a video needs to look clean.

  • A 1080p frame has 2,073,600 pixels (1920 × 1080).
  • A 4K frame has 8,294,400 (3840 × 2160), four times as many.
  • Raising the frame rate from 30 to 60 fps doubles the number of frames per second.

Video codecs such as H.264 compress the stream by predicting change between frames and within frames, so the bitrate does not scale in a straight line with pixel count. It still has to grow with resolution and frame rate. When it does not, the encoder drops detail, and you see blocking in motion, banding in gradients and smeared edges.

Bitrate versus constant rate factor

There are two ways to tell an encoder how much data to spend.

  • A target bitrate (-b:v in FFmpeg, often with -maxrate and -bufsize) holds the data rate near a fixed number. The file size is predictable. Static scenes get more bits than they need, and fast motion can run short and show blocks.
  • A constant rate factor (-crf) holds the visual quality steady and lets the bitrate move. Complex frames get more bits, still frames get fewer. The file size is not known until the encode finishes.

Platforms that re-encode every upload tend to prefer the second approach for source files. Vimeo’s compression guidelines recommend setting the constant rate factor to 18 or below, with the H.264 High profile rather than Main. For the FFmpeg side of this, see the guide to constant rate factor encoding and the tutorial on compressing video with FFmpeg.

YouTube and Vimeo both transcode every upload into a ladder of streaming renditions. Your file is the source for that transcode, so it needs enough data that the second encode has something to work with. Both platforms publish numbers.

YouTube’s upload guidelines give target bitrates by resolution and frame rate, in an MP4 container with H.264 video:

ResolutionSDR, 24 to 30 fpsSDR, 48 to 60 fpsHDR, 24 to 30 fpsHDR, 48 to 60 fps
1080p8 Mbps12 Mbps10 Mbps15 Mbps
2160p (4K)35 to 45 Mbps53 to 68 Mbps44 to 56 Mbps66 to 85 Mbps

The high frame rate column is 50 per cent higher than the standard one, not double, because the encoder reuses most of each frame in the next. The 4K row is four to five times the 1080p row, roughly in line with the four times pixel count.

Vimeo’s guidelines ask for a variable bitrate inside a range instead of a single target:

ResolutionVimeo variable bitrate
720p5 to 10 Mbps
1080p10 to 20 Mbps
2K20 to 30 Mbps
4K30 to 60 Mbps

The two sets of numbers agree on the shape: around 8 to 20 Mbps for 1080p, 30 to 60 Mbps for 4K. For the dimensions and aspect ratios each platform expects, see the video sizes and aspect ratios reference.

What the Shotstack quality presets produce, measured

The Shotstack Edit API does not expose encoder flags. The output.quality field takes one of five presets, verylow, low, medium, high and veryhigh, and the API reference describes medium, the default, as the balance of quality, render speed and file size. The reference does not state a bitrate for any preset, so we measured them.

The test rendered the same three second clip at resolution: "1080" (1920 × 1080 at 25 fps) once per preset. It then read each output back through the API’s probe endpoint, which runs ffprobe on the hosted file and returns the container and stream metadata.

The renders ran in production, not the sandbox, so there is no watermark in the picture. The source clip has no audio to speak of, so the figures below are the video stream.

The clip below is one frame split down the middle. The left half is the low render, the right half is high, both from the production runs in the table.

Quality presetH.264 profileBitrateFile size (3.01 s)Share of YouTube’s 8 Mbps 1080p target
verylowConstrained Baseline7.08 Mbps2.66 MB89%
lowHigh5.49 Mbps2.07 MB69%
medium (default)High8.17 Mbps3.07 MB102%
highHigh18.45 Mbps6.94 MB231%
veryhighHigh18.38 Mbps6.91 MB230%

Three things stand out.

  1. medium lands on the YouTube 1080p target. 8.17 Mbps against a recommended 8 Mbps. If you render at the default and upload to YouTube, you are already at the number Google publishes.
  2. high and veryhigh produced the same file. Both came out at about 18.4 Mbps, 2.3 times the size of medium. For this clip the encoder had enough bits at high to keep every detail, and veryhigh had nothing left to add. On footage with more motion or grain the two may separate. For a master file that will be re-encoded, high is the sensible ceiling.
  3. verylow is not the smallest file. It came out 28 per cent larger than low. The probe shows why: verylow switches the H.264 profile to Constrained Baseline, which drops the compression tools the High profile uses, so it needs more bits for the same picture. If the goal is the smallest file, low is the preset to test first.

For a 4K output, set resolution: "4k" (3840 × 2160 at 25 fps) in the same script and run it again. 4K rendering is available on the plans that include it, so check the pricing page before you test.

Measure it yourself in Python

The script below renders the clip at all five presets, waits for each render to finish, and reads the bitrate, file size, codec profile and frame rate from the probe endpoint. It needs only the Python standard library.

To run it:

  1. Sign up for a free account and copy an API key.
  2. Set the key as SHOTSTACK_API_KEY.
  3. Run the script. It uses the sandbox by default, which adds a watermark. Set SHOTSTACK_ENV=v1 to run it in production, which is what produced the numbers above.
"""Render one clip at every Shotstack quality preset and measure the result with the probe endpoint.

Usage: SHOTSTACK_API_KEY=... python bitrate_benchmark.py            (sandbox)
       SHOTSTACK_API_KEY=... SHOTSTACK_ENV=v1 python bitrate_benchmark.py   (production)
"""
import json
import os
import sys
import time
import urllib.parse
import urllib.request

API_KEY = os.environ.get("SHOTSTACK_API_KEY") or sys.exit("Set SHOTSTACK_API_KEY first.")
ENV = os.environ.get("SHOTSTACK_ENV", "stage")  # stage = sandbox, v1 = production
BASE = f"https://api.shotstack.io/edit/{ENV}"
SOURCE = "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/footage/beach-overhead.mp4"
LENGTH = 3  # seconds
QUALITIES = ["verylow", "low", "medium", "high", "veryhigh"]


def call(method, path, body=None):
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(BASE + path, data=data, method=method, headers={
        "x-api-key": API_KEY, "Content-Type": "application/json", "Accept": "application/json"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)["response"]


def render(quality):
    edit = {
        "timeline": {"tracks": [{"clips": [{"asset": {"type": "video", "src": SOURCE}, "start": 0, "length": LENGTH}]}]},
        "output": {"format": "mp4", "resolution": "1080", "quality": quality},
    }
    return call("POST", "/render", edit)["id"]


def wait(render_id):
    while True:
        r = call("GET", f"/render/{render_id}")
        if r["status"] == "done":
            return r["url"]
        if r["status"] == "failed":
            sys.exit(f"render {render_id} failed: {r.get('error')}")
        time.sleep(3)


def probe(url):
    meta = call("GET", "/probe/" + urllib.parse.quote(url, safe=""))["metadata"]
    video = next(s for s in meta["streams"] if s["codec_type"] == "video")
    audio = next((s for s in meta["streams"] if s["codec_type"] == "audio"), {})
    return meta["format"], video, audio


ids = {q: render(q) for q in QUALITIES}
print(f"{'quality':<9}{'MB':>7}{'Mbps':>7}{'video kbps':>12}{'audio kbps':>12}  codec        fps    seconds  render id")
for quality, render_id in ids.items():
    fmt, video, audio = probe(wait(render_id))
    size_mb = int(fmt["size"]) / 1_000_000
    mbps = int(fmt["bit_rate"]) / 1_000_000
    v_kbps = int(video.get("bit_rate", 0)) / 1000
    a_kbps = int(audio.get("bit_rate", 0)) / 1000
    num, den = video["r_frame_rate"].split("/")
    fps = int(num) / int(den)
    print(f"{quality:<9}{size_mb:>7.2f}{mbps:>7.2f}{v_kbps:>12.0f}{a_kbps:>12.0f}  "
          f"{video['codec_name']} {video.get('profile', '')}  {fps:g}   {float(fmt['duration']):.2f}     {render_id}")

The script submits all five renders first, then polls each one. A render returns an id straight away and finishes in the background, so submitting them together saves waiting five times over. The probe call takes the hosted output URL and returns the same JSON ffprobe would give you locally, with no download. The media metadata guide covers the rest of that response.

This is the output from the run behind the table above:

quality       MB   Mbps  video kbps  audio kbps  codec        fps    seconds  render id
verylow     2.66   7.08        7092           2  h264 Constrained Baseline  25   3.01     8d3907c7-d0df-485d-ae6d-a8d515cba8df
low         2.07   5.49        5498           2  h264 High  25   3.01     cfd23894-5053-409a-ba95-52c6c62e301a
medium      3.07   8.17        8185           2  h264 High  25   3.01     5dc19b33-f962-46fd-971c-7129ac70f2f9
high        6.94  18.45       18490           2  h264 High  25   3.01     26417d43-b9f7-45c8-877d-af29fdd22c73
veryhigh    6.91  18.38       18419           2  h264 High  25   3.01     baf289d8-3219-4ca1-9d68-91754a8a3c2a

Swap SOURCE for your own footage to see how the presets behave on it. Talking heads, screen recordings and drone shots all compress differently, and the gap between medium and high will move with them.

Render at a known bitrate without tuning encoder flags

The platform targets give you the number to hit. The measurements give you the preset that hits it. For most 1080p work the default medium preset is already at YouTube’s target, and high is the ceiling worth paying for.

The script above tells you where your own footage lands in a couple of minutes. Sign up for a free API key and run it on your clips.

Frequently asked questions (FAQs)

Does the quality preset change the resolution or frame rate?

No. All five renders in the test came back at 1920 × 1080 and 25 fps. The preset changes how many bits the encoder spends, and at verylow it also changes the H.264 profile. Resolution comes from output.resolution or output.size, and frame rate from output.fps, which accepts 12, 15, 23.976, 24, 25, 29.97, 30, 48, 50, 59.94 and 60.

What audio bitrate should an upload use?

YouTube’s guidelines list 128 kbps for mono, 384 kbps for stereo and 512 kbps for 5.1 surround. Vimeo suggests 320 kb/s. Audio is a small share of the total: a 384 kbps stereo track adds under 5 per cent to an 8 Mbps 1080p file.

Can I set an exact bitrate in the Edit API?

No. The output settings are format, resolution or size, aspect ratio, frame rate and quality. There is no bitrate field. If a delivery spec demands an exact figure, render at high and transcode the output with FFmpeg using -b:v, -maxrate and -bufsize, as shown in the FFmpeg compression guide.

Why would I ever use verylow if it makes a larger file?

For compatibility and speed, not size. The Constrained Baseline profile plays on older hardware decoders and encodes faster than High. If every viewer is on a modern phone or browser, low gives a smaller file at better quality.

Get started with Shotstack's video editing API in two steps:

  1. Sign up for free to get your API key.
  2. Send an API request to create your video:
    curl --request POST 'https://api.shotstack.io/v1/render' \
    --header 'x-api-key: YOUR_API_KEY' \
    --data-raw '{
      "timeline": {
        "tracks": [
          {
            "clips": [
              {
                "asset": {
                  "type": "video",
                  "src": "https://shotstack-assets.s3.amazonaws.com/footage/beach-overhead.mp4"
                },
                "start": 0,
                "length": "auto"
              }
            ]
          }
        ]
      },
      "output": {
        "format": "mp4",
        "size": {
          "width": 1280,
          "height": 720
        }
      }
    }'
Peace Aisosa

BY PEACE AISOSA

Published October 28, 2026

Peace Aisosa is a Customer Success Specialist at Shotstack. She writes about video automation when she's not troubleshooting customers' video pipelines. More from Peace

Studio Real Estate
Experience Shotstack for yourself.
SIGN UP FOR FREE