MEDIA GUIDES / Front-End Development

How to Use PHP to Generate Thumbnail from a Video

Videos are one of the most commonly used digital assets online, whether for tutorials, product demos, or social media platforms. As a developer, if you’re developing a video upload platform, for example, it’s common practice to show users a preview to confirm that they have selected the right video before uploading. A video preview can also grab viewers attention, increasing clickthrough rates and boosting engagements.

These video previews are also known as thumbnails – a small image that represents the video and gives users a quick idea of what to expect.

Key takeaways:

  • How to use PHP to generate and serve thumbnails from videos with and without FFmpeg
  • How to serve thumbnails directly in the browser
  • Scaling things up using services like Cloudinary

In this article:

Using HTML Canvas to Generate Thumbnail in PHP

The HTML <canvas> element is a container for graphics elements including raster images, 2D and 3D animations, and more.

This section explains how the canvas element captures preview images (thumbnails) from videos uploaded by users through an HTML input field.

  1. The user uploads a video through the input element (input type="file")
  2. Once the video has been selected, we use JavaScript to seek a random point in the video and display it on a hidden canvas element.
  3. Finally, we convert the canvas drawing into an image file, effectively generating a thumbnail.

Now let’s see how this works using code.

Create a file named index.php and add the following code to it:

<?php
?>
<!DOCTYPE html>
<html>

<head>
    <title>Thumbnail Generator</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>

<body class="bg-gray-100 font-sans p-6 md:p-10">

    <h2 class="text-2xl md:text-3xl font-bold mb-4">Upload a Video to Generate Thumbnail</h2>

    <form>
        <input type="file" id="videoUpload" accept="video/*" class="
            block w-1/2 text-sm text-gray-700
            file:mr-4 file:py-2 file:px-4
            file:rounded-full file:border-0
            file:text-sm file:font-semibold
            file:bg-blue-50 file:text-blue-700
            hover:file:bg-blue-100
            cursor-pointer
        ">
    </form>

    <video id="video" controls style="display:none;"></video>
    <canvas id="canvas" style="display:none;"></canvas>

    <h3 class="text-xl font-semibold mt-6 mb-2">Generated Thumbnail:</h3>
    <img id="thumbnail" src="https://placehold.co/400x225/E5E7EB/4B5563?text=Thumbnail%20Will%20Appear%20Here"
        alt="Thumbnail will appear here"
        class="mt-4 border-2 border-gray-300 max-w-full md:max-w-md rounded-lg shadow-lg">
    <script>
        const fileInput = document.getElementById("videoUpload");
        const video = document.getElementById("video");
        const canvas = document.getElementById("canvas");
        const thumbnail = document.getElementById("thumbnail");

        fileInput.addEventListener("change", function (event) {
            const file = event.target.files[0];
            if (!file) return;

            const url = URL.createObjectURL(file);
            video.src = url;
            video.load();

            video.addEventListener("loadedmetadata", function () {
                // Seek to a random point in the video
                const randomTime = Math.random() * video.duration;
                video.currentTime = randomTime;
            }, { once: true });


            video.addEventListener("seeked", function () {
                // Draw the video frame on canvas
                const w = video.videoWidth;
                const h = video.videoHeight;
                canvas.width = w;
                canvas.height = h;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(video, 0, 0, w, h);

                // Convert canvas to image and set as thumbnail
                const dataURL = canvas.toDataURL("image/jpeg");
                thumbnail.src = dataURL;

                // Free memory
                URL.revokeObjectURL(url);
            }, { once: true });
        });
    </script>

</body>
</html>

This is what the page looks like:

When you select a video, you should see the thumbnail generated as shown below:

In the above example, our code generates the thumbnail by randomly selecting a point in the video. We can rewrite our code so users can manually choose which point of the video they want to capture for the thumbnail.

Create Slider to Select a Video Frame for The Thumbnail

To create a slider, we’ll use the input element with a type of ‘range’ <input type="range">, which allows us to select a numeric within a range of values.

Replace the code in index.php with the following:

<?php
?>
<!DOCTYPE html>
<html>
<head>
    <title>Thumbnail Generator</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 font-sans p-6 md:p-10">

    <h2 class="text-2xl md:text-3xl font-bold mb-4">Upload a Video to Generate Thumbnail</h2>

    <form>
        <input type="file" id="videoUpload" accept="video/*" class="
            block w-1/2 text-sm text-gray-700
            file:mr-4 file:py-2 file:px-4
            file:rounded-full file:border-0
            file:text-sm file:font-semibold
            file:bg-blue-50 file:text-blue-700
            hover:file:bg-blue-100
            cursor-pointer
        ">
    </form>

    <video id="video" controls style="display:none;"></video>
    <canvas id="canvas" style="display:none;"></canvas>

    <div id="controls" style="display:none;" class="mt-6 flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-4">
        <label for="timeSlider" class="text-sm font-medium text-gray-700">
            Select Frame (seconds): <span id="timeLabel" class="font-bold w-4 text-gray-900">0</span>s
        </label>
        <div class="flex items-center space-x-2 w-full md:w-auto">
            <input type="range" id="timeSlider" min="0" value="0" step="0.1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
            <button type="button" id="captureBtn" class="px-8 py-2 bg-blue-600 text-white text-sm font-medium rounded-full hover:bg-blue-700 transition-colors">
                Generate Thumbnail
            </button>
        </div>
    </div>

    <h3 class="text-xl font-semibold mt-6 mb-2">Generated Thumbnail:</h3>
    <img id="thumbnail" 
         src="https://placehold.co/400x225/E5E7EB/4B5563?text=Thumbnail%20Will%20Appear%20Here" 
         alt="Thumbnail will appear here" 
         class="mt-4 border-2 border-gray-300 max-w-full md:max-w-md rounded-lg shadow-lg">

    <script>
        const fileInput = document.getElementById("videoUpload");
        const video = document.getElementById("video");
        const canvas = document.getElementById("canvas");
        const thumbnail = document.getElementById("thumbnail");
        const controls = document.getElementById("controls");
        const timeSlider = document.getElementById("timeSlider");
        const timeLabel = document.getElementById("timeLabel");
        const captureBtn = document.getElementById("captureBtn");

        let videoURL = null;

        fileInput.addEventListener("change", function(event) {
            const file = event.target.files[0];
            if (!file) return;

            if (videoURL) {
                URL.revokeObjectURL(videoURL); // Clean up previous URL
            }

            videoURL = URL.createObjectURL(file);
            video.src = videoURL;
            video.load();

            video.addEventListener("loadedmetadata", function() {
                // Set slider max to video duration
                timeSlider.max = video.duration.toFixed(1);
                timeSlider.value = 0;
                timeLabel.textContent = "0";
                controls.style.display = "flex";
            }, { once: true });
        });

        timeSlider.addEventListener("input", function() {
            timeLabel.textContent = this.value;
        });

        captureBtn.addEventListener("click", function() {
            const time = parseFloat(timeSlider.value);
            if (isNaN(time)) return;

            video.currentTime = time;

            video.addEventListener("seeked", function captureFrame() {
                // Draw the video frame on canvas
                const w = video.videoWidth;
                const h = video.videoHeight;
                canvas.width = w;
                canvas.height = h;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(video, 0, 0, w, h);

                // Convert canvas to image and show
                const dataURL = canvas.toDataURL("image/jpeg");
                thumbnail.src = dataURL;

                // Remove listener to avoid multiple triggers
                video.removeEventListener("seeked", captureFrame);
            });
        });
    </script>

</body>
</html>

This is what the page looks like now:

With this, users can select any frame in the video to use as a thumbnail.

Generating Thumbnails With FFmpeg in PHP

In the previous example, we showed how to generate thumbnails on the client side in a PHP application. But there are instances where you might want to do this on the server side. One of the most common ways to create thumbnails on the server side in PHP is by using FFmpeg.

FFmpeg is a free and open-source multimedia framework that consists of several libraries and tools for processing audio and video files, including decoding, encoding, transcoding, streaming, and playback. FFmpeg is originally written in C, however, it has wrappers and bindings in other programming languages, including PHP, that make it widely used by developers.

To use FFmpeg in PHP, we’ll install the PHP-FFMpeg library using composer.

composer require php-ffmpeg/php-ffmpeg

Note: FFmpeg itself must also be installed and accessible from the command line on your server or computer. You can download and install it for your OS by following the instructions on the official page.

Next, we’ll

Create a file called generate.php and add the following code to it:

<?php

require 'vendor/autoload.php';

use FFMpeg\FFMpeg;
use FFMpeg\Coordinate\TimeCode;

$ffmpeg = FFMpeg::create();

try {
    // Open the video file
    $video = $ffmpeg->open('sample-video.mp4');

    // Specify the frame to capture in seconds
    $frame = $video->frame(TimeCode::fromSeconds(5));

    // Save the frame as a thumbnail
    $frame->save('thumbnail.jpg');

    echo "Thumbnail generated!";
} catch (\FFMpeg\Exception\RuntimeException $e) {
    echo "An error occurred: " . $e->getMessage();
}

To serve the generated thumbnail to the user, you can output the thumbnail as an image file directly from the server or embed the thumbnail dynamically in your HTML.

For example, if the thumbnails are stored in a folder like uploads/thumbs/, you can embed them dynamically in HTML as follows:

<?php
$thumbDir = 'uploads/thumbs/';
$thumbnails = glob($thumbDir . "*.jpg");
?>
<!DOCTYPE html>
<html>
<head>
    <title>Video Thumbnails</title>
</head>
<body>
    <h2>Generated Thumbnails</h2>
    <div style="display: flex; flex-wrap: wrap; gap: 10px;">
        <?php foreach ($thumbnails as $thumb): ?>
            <img src="<?php echo $thumb; ?>" alt="Video Thumbnail" width="200">
        <?php endforeach; ?>
    </div>
</body>
</html>

Scaling with Cloudinary

So far, we’ve looked at how to generate thumbnails in two different ways: using the HTML <canvas> element, and using FFmpeg. However, the canvas method does not save the generated image automatically, making it difficult to use in other workflows. Likewise in the second method, running FFmpeg on your own server can quickly become slow, expensive, and hard to manage, especially if you’re building an app that handles a large amount of video files.

Cloudinary is a cloud-based media management solution that handles uploading, storing, transforming, and delivering images and videos. One of its most useful features is the ability to generate thumbnails on the fly using simple URL-based transformations.

To generate thumbnails from a video using Cloudnary, the video must have already been to Cloudinary. You can learn more about uploading videos to Cloudinary using the PHP SDK in this quickstart guide.

Once a video has been uploaded to Cloudinary, you’ll need its public URL to add transformations to it to generate the thumbnail.

Let’s take this URL as an example:

https://res.cloudinary.com/demo/video/upload/docs/walking_talking.mp4

Using Cloudinary’s transformation parameters, we can generate a thumbnail from the video at a specific point using the start offset qualifier (so in URLs). The following example generates a thumbnail from the video frame at 8.5 seconds from the start:

https://res.cloudinary.com/demo/video/upload/so_8.5/docs/walking_talking.jpg

Note: If you wish to save the thumbnail in a different format, you can simply change the file extension at the end of the URL.

Thumbnail generation is not the only thing you can do with Cloudinary. You can also use it to add text or image overlays, apply filters and effects, and perform various transformations. You can read more about image transformations in the documentation.

Wrapping Up

Creating a video thumbnail in PHP can take a few different approaches. In this article, we’ve shown you different methods you can use to create thumbnails from videos, depending on your use cases. For small apps, client-side generation using the canvas element or server-side generation using FFMpeg might be enough. For more robust workflows, Cloudinary will give you the simplicity and scalability you need.

If you haven’t already, sign up for a free Cloudinary account now and start exploring its endless possibilities

Frequently Asked Questions

Can I generate multiple thumbnails from the same video?

Yes, you can. For instance, using PHP-FFmpeg, you can call the frame() method multiple times at different timecodes. For example, you might capture frames at 5 seconds, 15 seconds, and 30 seconds, then save each as a separate image.

Can I use the HTML Canvas to generate thumbnails for large videos?

While technically possible, the client-side approach may struggle with very large video files or videos on devices with slow CPUs. The canvas method should only be used for generating a temporary preview or a user-selected frame before the final video upload, especially when dealing with small to medium-sized files, as it avoids burdening the server with initial processing.

QUICK TIPS
Colby Fayock
Cloudinary Logo Colby Fayock

In my experience, here are tips that can help you better generate thumbnails from videos using PHP and related tools:

  1. Pre-analyze video metadata before frame extraction
    Use ffprobe (bundled with FFmpeg) to analyze video length, frame rate, resolution, and keyframe positions before deciding on thumbnail timecodes. This ensures you don’t accidentally extract from a low-detail or black frame early in the video.
  2. Target keyframes for high-quality thumbnails
    When using FFmpeg, specify -skip_frame nokey or parse keyframe data to extract from I-frames only. This avoids blur and artifact issues common in P- or B-frames during high compression.
  3. Integrate perceptual hashing to detect duplicate thumbnails
    Use tools like pHash or ImageMagick to compare generated thumbnails and filter out nearly identical frames across different time points. This helps when auto-generating thumbnails at intervals.
  4. Implement frame bracketing around action peaks
    Analyze audio peaks or scene cuts using FFmpeg filters like select='gt(scene,0.4)' to isolate more engaging thumbnails during high motion or transition points.
  5. Normalize thumbnail brightness and contrast
    Thumbnails extracted from darker scenes can appear dull. Automatically apply histogram equalization or brightness/contrast correction using ImageMagick or FFmpeg’s eq filter.
  6. Capture adaptive resolution thumbnails for performance
    Dynamically resize thumbnails during extraction (e.g., 320×180, 640×360, etc.) based on the requesting device or page layout, instead of post-processing. Use FFmpeg’s -vf scale=iw/2:ih/2 to handle this inline.
  7. Use color histogram or entropy scoring to auto-select best frames
    Implement a scoring system that favors frames with more visual information (entropy or color variance) for automated selection. This yields more engaging thumbnails than arbitrary time-based picks.
  8. Cache thumbnails with content-based hashes
    Store thumbnails using hashes of video file contents (e.g., MD5 or SHA1) to detect and reuse thumbnails for identical videos uploaded under different filenames or formats.
  9. Enable server-side fallback for canvas thumbnails
    Provide a fallback API using FFmpeg when canvas-based client thumbnails fail (e.g., due to mobile limitations). Log failures and transparently switch to server-side capture to improve UX.
  10. Integrate async queue systems for batch processing
    For apps handling large video uploads, offload FFmpeg thumbnail generation to background jobs (e.g., using Redis queues or Laravel Horizon). This avoids blocking uploads or overloading the server.
Last updated: Oct 15, 2025