RenderIO
Guides

Generate Thumbnails

Extract frames from video as images.

Generate Thumbnails

Generating thumbnails from video is essential for preview images, video galleries, and content management systems. FFmpeg can extract single frames, periodic snapshots, or create contact sheet grids from any video.

Single frame at a timestamp

Extract one frame at the 5-second mark and save it as an image.

ffmpeg -i {{in_video}} -ss 00:00:05 -frames:v 1 {{out_thumbnail}}
  • -ss 00:00:05 seeks to 5 seconds into the video
  • -frames:v 1 tells FFmpeg to output exactly one video frame

Multiple frames at intervals

Extract one frame every 10 seconds throughout the entire video. This generates multiple output files with sequential numbering.

ffmpeg -i {{in_video}} -vf "fps=1/10" {{out_frame}}_%03d.png
  • fps=1/10 outputs 1 frame every 10 seconds
  • %03d adds zero-padded numbering (001, 002, 003, etc.)

Note that this produces multiple files. The output alias {{out_frame}} serves as the filename prefix, and FFmpeg appends the sequential number.

Contact sheet / grid

Create a single image containing a grid of frames from throughout the video. This gives a visual overview of the entire video at a glance.

ffmpeg -i {{in_video}} -vf "select=not(mod(n\,100)),scale=320:180,tile=4x4" -frames:v 1 {{out_grid}}
  • select=not(mod(n\,100)) picks every 100th frame
  • scale=320:180 resizes each frame to 320x180
  • tile=4x4 arranges 16 frames in a 4-by-4 grid
  • -frames:v 1 outputs only the first complete grid

Adjust the frame interval and grid size based on your video length. For a 10-minute video at 30fps (18,000 frames), selecting every 1000th frame gives you 18 frames, enough to fill a 4x5 grid.

Full API example

Extract a thumbnail from the 5-second mark of a video.

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/source.mp4"
    },
    "output_files": {
      "out_thumbnail": "thumbnail.jpg"
    },
    "ffmpeg_command": "ffmpeg -ss 00:00:05 -i {{in_video}} -frames:v 1 {{out_thumbnail}}"
  }'
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/source.mp4",
        },
        "output_files": {
            "out_thumbnail": "thumbnail.jpg",
        },
        "ffmpeg_command": "ffmpeg -ss 00:00:05 -i {{in_video}} -frames:v 1 {{out_thumbnail}}",
    },
)

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/source.mp4",
    },
    output_files: {
      out_thumbnail: "thumbnail.jpg",
    },
    ffmpeg_command: "ffmpeg -ss 00:00:05 -i {{in_video}} -frames:v 1 {{out_thumbnail}}",
  }),
});

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/source.mp4",
    },
    output_files: {
      out_thumbnail: "thumbnail.jpg",
    },
    ffmpeg_command: "ffmpeg -ss 00:00:05 -i {{in_video}} -frames:v 1 {{out_thumbnail}}",
  }),
});

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/source.mp4",
    ],
    "output_files" => [
        "out_thumbnail" => "thumbnail.jpg",
    ],
    "ffmpeg_command" => "ffmpeg -ss 00:00:05 -i {{in_video}} -frames:v 1 {{out_thumbnail}}",
]));

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

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

Note that -ss is placed before -i in this example. When -ss comes before the input, FFmpeg seeks directly to that position without decoding every frame from the beginning. This is significantly faster, especially for long videos.

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

Tips and variations

  • -ss placement matters: Place -ss before -i for fast (but slightly less accurate) seeking. Place -ss after -i for frame-accurate seeking at the cost of decoding from the start. For thumbnails, the speed gain of pre-input seeking is almost always worth it.
  • Output format: The output format is determined by the filename extension. Use .jpg for smaller files, .png for lossless quality and transparency support, or .webp for modern web use.
  • JPEG quality: Control JPEG compression with -q:v 2 (range 2-31, lower is better quality).
  • Best frame selection: To pick the most visually interesting frame near a timestamp, use the thumbnail filter: -vf "thumbnail=100" which analyzes 100 frames and picks the most representative one.
  • Specific resolution: Add a scale filter to generate thumbnails at a specific size: -vf "scale=640:360" or -vf "scale=640:-2" to maintain aspect ratio.

On this page