Generate Video Thumbnails from a URL (Even from a 2 GB File)
Extract JPEG and PNG frames from video URLs at exact timestamps with one API call, using byte-range reads so a large source never has to be downloaded first.
Whether you are building a video management library, an e-learning platform course index, or a social media feed, generating preview thumbnails for user uploads is essential.
Doing this in a traditional server environment is awkward. Downloading a multi-gigabyte source video to a local disk just to extract a single 100 KB frame wastes bandwidth, disk and time.
In this guide, we show you how to generate video thumbnails directly from source URLs, without copying the whole file first.
1. The Traditional Way (And Why It Hurts)
In standard video processing pipelines, developers extract thumbnails by:
- Downloading the entire video file from cloud storage (like Amazon S3) to a local server volume.
- Running a local FFmpeg command like:
Terminal window ffmpeg -ss 10 -i local-file.mp4 -frames:v 1 -q:v 2 thumbnail.jpg - Uploading the extracted
thumbnail.jpgback to their storage or CDN. - Deleting the local file from disk.
The Bottleneck
If a user uploads a 2 GB video file, your server downloads all 2 GB and writes it to disk, all to grab a single frame from the 10-second mark.
As uploads grow, those downloads queue up behind each other, and your disks and network carry bytes you never use.
2. Byte-Range Reads Instead of Full Downloads
FFmpeg does not need the whole file to decode one frame. With HTTP range requests (Range: bytes=...), it reads the container index (the moov atom in MP4/MOV), finds where the frame at your timestamp lives, and fetches only those bytes.
You can see this with FFmpeg on your own machine. Put -ss before -i, so FFmpeg seeks in the input before it decodes, and pass the URL directly:
ffmpeg -ss 12.5 -i "https://example.com/video.mp4" -frames:v 1 -q:v 2 thumbnail.jpgFor a thumbnail from a large file, that is a small fraction of the source. Most CDNs, S3/R2 presigned URLs and many file servers support range requests (Accept-Ranges: bytes).
3. Using Xora’s thumbnail Recipe
Xora’s thumbnail recipe takes a source URL and a timestamp and returns one image. Add "stageInput": false so Xora reads your URL with range requests instead of copying the file to engine storage first. If the origin does not support ranges, Xora falls back to staging and the job still completes (input staging guide).
cURL API Request
Here is a cURL call that extracts a frame at the 12.5-second mark as a JPEG:
curl -X POST https://api.xora.sh/v1/jobs \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "recipe", "input": { "url": "https://storage.googleapis.com/raw-videos/sample-2gb.mp4" }, "output": { "format": "jpg" }, "recipe": { "name": "thumbnail", "seekSeconds": 12.5 }, "stageInput": false }'The API returns 202 with a job ID. When the job reaches completed, the job record holds a signed download URL for the image — or, if you connected your own Cloudflare R2 bucket, the image is already there.
4. Integration Snippet: Node.js Handler
Here is a Node.js function that triggers a thumbnail job and asks Xora to call your webhook when it finishes:
const generateThumbnail = async (videoUrl, timestamp) => { const response = await fetch('https://api.xora.sh/v1/jobs', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'recipe', input: { url: videoUrl }, output: { format: 'png' }, recipe: { name: 'thumbnail', seekSeconds: timestamp }, stageInput: false, webhookUrl: 'https://your-app.com/webhooks/xora' }) });
return await response.json();};
// Example usagegenerateThumbnail('https://example.com/movie.mov', 45.0) .then(job => console.log('Thumbnail job triggered:', job.id));When the job is done, Xora sends a POST to webhookUrl with data.jobId, data.state and, for a completed job, data.output.signedUrl.
5. What it costs
A thumbnail from a 2 GB video uses about 0.1 credits on Xora, because only a small part of the file is read and the image is small. The Free plan includes 25 credits every month with no card. Failed and rejected jobs cost nothing. See pricing.
Related
- Thumbnail recipe reference: all options.
- FFmpeg cheat sheet: the common commands, each as an API call.
- How to compress video with FFmpeg.
Frequently asked questions
How do I generate a thumbnail from a video URL with FFmpeg?
Put -ss before -i so FFmpeg seeks before it decodes: ffmpeg -ss 12.5 -i https://example.com/video.mp4 -frames:v 1 -q:v 2 thumbnail.jpg. When the server supports HTTP range requests, FFmpeg reads only the parts of the file it needs.
Do I have to download the whole video to get one frame?
No. MP4 and MOV files have an index that tells FFmpeg where each frame is. With range requests, FFmpeg reads the index and the bytes around the timestamp, not the full file. With Xora, set stageInput to false to use range reads.
How much does a thumbnail cost on Xora?
A thumbnail from a 2 GB video uses a fraction of one credit, because only a small part of the file is read and the output is small. The Free plan includes credits every month with no card, and failed jobs cost nothing.
Can I get a PNG in place of a JPEG?
Yes. Set the output format to png. JPEG files are smaller; PNG keeps every pixel exactly, which is better for screenshots and text.