Getting Video Snapshot for Thumbnail

Getting video snapshot for thumbnail

To fix the thumbnail orientation set appliesPreferredTrackTransform to YES in the AVAssetImageGenerator instance. If you add your own video composition, you'll need to include the right transform to rotate the video as wanted.

generate.appliesPreferredTrackTransform = YES;

Remember to release the obtained image reference with CGImageRelease.

To request multiple thumbnails it's better to do asynchronously with generateCGImagesAsynchronouslyForTimes:completionHandler:.

Generate a thumbnail/snapshot of a video file selected by a file input at a specific time

There are four major steps:

  1. Create <canvas> and <video> elements.
  2. Load the src of the video file generated by URL.createObjectURL into the <video> element and wait for it to load by listening for specific events being fired.
  3. Set the time of the video to the point where you want to take a snapshot and listen for additional events.
  4. Use the canvas to grab the image.

Step 1 - Create the elements

This is very easy: just create one <canvas> and one <video> element and append them to <body> (or anywhere really, it doesn't really matter):

var canvasElem = $( '<canvas class="snapshot-generator"></canvas>' ).appendTo(document.body)[0];
var $video = $( '<video muted class="snapshot-generator"></video>' ).appendTo(document.body);

Notice that the video element has the attribute muted. Don't put any other attributes like autoplay or controls. Also notice that they both have the class snapshot-generator. This is so we can set the style for both of them so that they are out of the way:

.snapshot-generator {
display: block;
height: 1px;
left: 0;
object-fit: contain;
position: fixed;
top: 0;
width: 1px;
z-index: -1;
}

Some browsers work with them set to display: none, but other browsers will have serious problems unless they are rendered on the page, so we just make them minuscule so that they are essentially invisible. (Don't move them outside the viewport though, as otherwise you may see some ugly scrollbars on your page.)

Step 2 - Load the video

Here's where things start to get tricky. You need to listen to events to know when to continue. Different browsers will fire different events, different times and in different orders, so I'll save you the effort. There are three events that must always fire at least once before the video is ready; they are:

  • loadedmetadata
  • loadeddata
  • suspend

Set up the event handler for these events and keep track how many have fired. Once all three have fired, you are ready to proceed. Keep in mind that, since some of these events may fire more than once, you only want to handle the first event of each type that is fired, and discard subsequent firings. I used jQuery's .one, which takes care of this.

var step_2_events_fired = 0;
$video.one('loadedmetadata loadeddata suspend', function() {
if (++step_2_events_fired == 3) {
// Ready for next step
}
}).prop('src', insert_source_here);

The source should just be the object URL created via URL.createObjectURL(file), where file is the file object.

Step 3 - Set the time

This stage is similar to the previous: set the time and then listen for an event. Inside our if block from the previous code:

$video.one('seeked', function() {
// Ready for next step
}).prop('currentTime', insert_time_here_in_seconds);

Luckily its only one event this time, so it's pretty clear and concise. Finally...

Step 4 - Grab the snapshot

This part is just using the <canvas> element to grab a screenshot. Inside our seeked event handler:

canvas_elem.height = this.videoHeight;
canvas_elem.width = this.videoWidth;
canvas_elem.getContext('2d').drawImage(this, 0, 0);
var snapshot = canvas_elem.toDataURL();

// Remove elements as they are no longer needed
$video.remove();
$(canvas_elem).remove();

The canvas needs to match the dimensions of the video (not the <video> element) to get a proper image. Also, we are setting the canvas's internal .height and .width properties, not the canvas height/width CSS style values.

The value of snapshot is a data URI, which is basically just a string that starts with data:image/jpeg;base64 and then the base64 data.

Our final JS code should look like:

var step_2_events_fired = 0;
$video.one('loadedmetadata loadeddata suspend', function() {
if (++step_2_events_fired == 3) {
$video.one('seeked', function() {
canvas_elem.height = this.videoHeight;
canvas_elem.width = this.videoWidth;
canvas_elem.getContext('2d').drawImage(this, 0, 0);
var snapshot = canvas_elem.toDataURL();

// Delete the elements as they are no longer needed
$video.remove();
$(canvas_elem).remove();
}).prop('currentTime', insert_time_here_in_seconds);
}
}).prop('src', insert_source_here);

Celebrate!

You have your image in base64! Send this to your server, put it as the src of an <img> element, or whatever.

For example, you can decode it into binary and directly write it to a file (trim the prefix first), which will become a JPEG image file.

You could also use this to offer previews of videos while they are uploaded. If you are putting it as the src of an <img>, use the full data URI (don't remove the prefix).

Creating Thumbnail of Video from screenshot using Fluent FFMPEG in Node JS

I figured out why it wasn't working. I was missing with 2 things.

  1. FFmpeg is not installed. I installed from this Link. (Note: Fluent-ffmpeg requires FFmpeg to be installed in your OS. Without FFmpeg, Fluent-ffmpeg will not work.)
  2. Set Environment Variable. Follow this tutorial to set the environment variable.

After doing the above two task, I was successfully able to create screenshots of videos.

Automatic Thumbnails/Screenshots for Chapter in HTML Video

This could be done in theory by jumping to specific times in the video (say every 10 seconds) using video.currentTime, waiting for the frame to be available (using progress events), drawing the frame to a canvas (canvas.drawImage) and storing it in some way (say an array of images having image.src = canvas.toDataURL).

However, this process will take time because at least the relevant parts of the video would need to be loaded in the browser so the frame could be grabbed. The video would not be playable during the process as it is being skipped to different frames.

This behavior is usually not acceptable, but it really depends on your specific use case.



Related Topics



Leave a reply



Submit