RenderIO
Guides

Compress Video

Reduce video file size with CRF encoding and bitrate targets.

Compress Video

Video compression reduces file size while balancing visual quality. FFmpeg provides several approaches: constant quality (CRF), target bitrate, and two-pass encoding. Each method suits different use cases depending on whether you prioritize consistent quality or predictable file size.

CRF encoding (constant quality)

CRF (Constant Rate Factor) is the most common compression method. It adjusts the bitrate dynamically to maintain consistent visual quality throughout the video.

ffmpeg -i {{in_video}} -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 128k {{out_video}}
  • -crf 28 sets the quality level. The CRF scale for H.264 is 0-51, where 0 is lossless and 51 is the worst quality. A value of 23 is the default, 28 produces noticeably smaller files with acceptable quality for most web use.
  • -preset medium controls the encoding speed vs. compression efficiency tradeoff.

Target bitrate

When you need predictable file sizes (e.g., for streaming), use a target bitrate instead of CRF. This approach produces more consistent file sizes but may have variable quality.

ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -c:a aac -b:a 128k {{out_video}}
  • -b:v 1M targets 1 megabit per second for the video stream.

Two-pass encoding (chained commands)

Two-pass encoding analyzes the entire video in the first pass to make better bitrate allocation decisions in the second pass. This produces the best quality at a given file size. Use the chained commands endpoint to run both passes sequentially.

curl -X POST https://renderio.dev/api/v1/run-chained-ffmpeg-commands \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_api_key_here" \
  -d '{
    "input_files": {
      "in_video": "https://example.com/source.mp4"
    },
    "output_files": {
      "out_video": "compressed.mp4"
    },
    "ffmpeg_commands": [
      "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 1 -an -f null /dev/null",
      "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k {{out_video}}"
    ]
  }'
import requests

response = requests.post(
    "https://renderio.dev/api/v1/run-chained-ffmpeg-commands",
    headers={
        "Content-Type": "application/json",
        "X-API-KEY": "ffsk_your_api_key_here",
    },
    json={
        "input_files": {
            "in_video": "https://example.com/source.mp4",
        },
        "output_files": {
            "out_video": "compressed.mp4",
        },
        "ffmpeg_commands": [
            "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 1 -an -f null /dev/null",
            "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k {{out_video}}",
        ],
    },
)

data = response.json()
print("Command ID:", data["command_id"])
interface ChainedCommandResponse {
  command_id: string;
}

const response = await fetch(
  "https://renderio.dev/api/v1/run-chained-ffmpeg-commands",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": "ffsk_your_api_key_here",
    },
    body: JSON.stringify({
      input_files: {
        in_video: "https://example.com/source.mp4",
      },
      output_files: {
        out_video: "compressed.mp4",
      },
      ffmpeg_commands: [
        "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 1 -an -f null /dev/null",
        "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k {{out_video}}",
      ],
    }),
  },
);

const { command_id } = (await response.json()) as ChainedCommandResponse;
console.log("Command ID:", command_id);
const response = await fetch(
  "https://renderio.dev/api/v1/run-chained-ffmpeg-commands",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": "ffsk_your_api_key_here",
    },
    body: JSON.stringify({
      input_files: {
        in_video: "https://example.com/source.mp4",
      },
      output_files: {
        out_video: "compressed.mp4",
      },
      ffmpeg_commands: [
        "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 1 -an -f null /dev/null",
        "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k {{out_video}}",
      ],
    }),
  },
);

const { command_id } = await response.json();
console.log("Command ID:", command_id);
<?php
$ch = curl_init("https://renderio.dev/api/v1/run-chained-ffmpeg-commands");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "X-API-KEY: ffsk_your_api_key_here",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "input_files" => [
        "in_video" => "https://example.com/source.mp4",
    ],
    "output_files" => [
        "out_video" => "compressed.mp4",
    ],
    "ffmpeg_commands" => [
        "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 1 -an -f null /dev/null",
        "ffmpeg -i {{in_video}} -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k {{out_video}}",
    ],
]));

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo "Command ID: " . $data["command_id"] . "\n";
  • Pass 1 analyzes the video and writes statistics to a log file. The output is discarded (-f null /dev/null) and audio is skipped (-an).
  • Pass 2 uses the statistics from pass 1 to encode the final video with optimal bitrate distribution.

Both passes share the same sandbox filesystem, so the log file created in pass 1 is automatically available in pass 2.

Full API example

Compress a video using CRF encoding for good quality at a reduced file size.

curl -X POST https://renderio.dev/api/v1/run-ffmpeg-command \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_api_key_here" \
  -d '{
    "input_files": {
      "in_video": "https://example.com/raw-footage.mp4"
    },
    "output_files": {
      "out_video": "compressed.mp4"
    },
    "ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 128k {{out_video}}"
  }'
import requests

response = requests.post(
    "https://renderio.dev/api/v1/run-ffmpeg-command",
    headers={
        "Content-Type": "application/json",
        "X-API-KEY": "ffsk_your_api_key_here",
    },
    json={
        "input_files": {
            "in_video": "https://example.com/raw-footage.mp4",
        },
        "output_files": {
            "out_video": "compressed.mp4",
        },
        "ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 128k {{out_video}}",
    },
)

data = response.json()
print("Command ID:", data["command_id"])
interface RunCommandResponse {
  command_id: string;
}

const response = await fetch("https://renderio.dev/api/v1/run-ffmpeg-command", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": "ffsk_your_api_key_here",
  },
  body: JSON.stringify({
    input_files: {
      in_video: "https://example.com/raw-footage.mp4",
    },
    output_files: {
      out_video: "compressed.mp4",
    },
    ffmpeg_command:
      "ffmpeg -i {{in_video}} -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 128k {{out_video}}",
  }),
});

const { command_id } = (await response.json()) as RunCommandResponse;
console.log("Command ID:", command_id);
const response = await fetch("https://renderio.dev/api/v1/run-ffmpeg-command", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": "ffsk_your_api_key_here",
  },
  body: JSON.stringify({
    input_files: {
      in_video: "https://example.com/raw-footage.mp4",
    },
    output_files: {
      out_video: "compressed.mp4",
    },
    ffmpeg_command:
      "ffmpeg -i {{in_video}} -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 128k {{out_video}}",
  }),
});

const { command_id } = await response.json();
console.log("Command ID:", command_id);
<?php
$ch = curl_init("https://renderio.dev/api/v1/run-ffmpeg-command");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "X-API-KEY: ffsk_your_api_key_here",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "input_files" => [
        "in_video" => "https://example.com/raw-footage.mp4",
    ],
    "output_files" => [
        "out_video" => "compressed.mp4",
    ],
    "ffmpeg_command" => "ffmpeg -i {{in_video}} -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 128k {{out_video}}",
]));

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo "Command ID: " . $data["command_id"] . "\n";

The API returns a command_id immediately. Poll GET /api/v1/commands/:commandId to check when the compression is complete, then download the output from the storage_url in the response.

Tips and variations

  • CRF range guide: 18 is visually lossless (large files), 23 is the default (good balance), 28 is acceptable for web delivery (small files), and 33+ introduces visible artifacts.
  • Preset tradeoff: Presets control encoding speed vs. file size. From fastest to most compressed: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow. A slower preset produces a smaller file at the same CRF but takes longer.
  • Scale before compressing: Reducing resolution before compressing dramatically cuts file size. Combine with the resize filter: -vf "scale=1280:-2" -c:v libx264 -crf 28.
  • Audio bitrate: 128k is a good default for AAC audio. Use 96k for speech-only content or 192k for music.
  • VP9 compression: For WebM output, VP9 offers better compression than H.264 at the same quality: -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus.
  • Resize Video -- reducing resolution before compressing cuts file size further
  • Chained Workflow -- two-pass encoding requires chained commands

Further reading

On this page