admin管理员组

文章数量:1287970

I'm trying to build a function that extracts frames from a video in JavaScript. Here the code I came up with. The function receives a source and a callback. From there, I create a video with the source and I want to draw frames of the video in the canvas with a set interval.

Unfortunately, the frames returned are all transparent images.

I tried a few different things, but I can't make it work. Can someone help? Thanks.

const extractFramesFromVideo = function(src, callback) {
  var video = document.createElement('video');
  video.src = src;

  video.addEventListener('loadeddata', function() {
    var canvas = document.createElement('canvas');
    var context = canvas.getContext('2d');
    canvas.setAttribute('width', video.videoWidth);
    canvas.setAttribute('height', video.videoHeight);

    var frames = [];
    var fps = 1; // Frames per seconds to
    var interval = 1 / fps; // Frame interval
    var maxDuration = 10; // 10 seconds max duration
    var currentTime = 0; // Start at 0

    while (currentTime < maxDuration) {
      video.currentTime = currentTime; 
      context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
      var base64ImageData = canvas.toDataURL();
      frames.push(base64ImageData);

      currentTime += interval;

      if (currentTime >= maxDuration) {
        console.log(frames);
        callback(frames);
      }
    }
  });
}

export default extractFramesFromVideo;

I'm trying to build a function that extracts frames from a video in JavaScript. Here the code I came up with. The function receives a source and a callback. From there, I create a video with the source and I want to draw frames of the video in the canvas with a set interval.

Unfortunately, the frames returned are all transparent images.

I tried a few different things, but I can't make it work. Can someone help? Thanks.

const extractFramesFromVideo = function(src, callback) {
  var video = document.createElement('video');
  video.src = src;

  video.addEventListener('loadeddata', function() {
    var canvas = document.createElement('canvas');
    var context = canvas.getContext('2d');
    canvas.setAttribute('width', video.videoWidth);
    canvas.setAttribute('height', video.videoHeight);

    var frames = [];
    var fps = 1; // Frames per seconds to
    var interval = 1 / fps; // Frame interval
    var maxDuration = 10; // 10 seconds max duration
    var currentTime = 0; // Start at 0

    while (currentTime < maxDuration) {
      video.currentTime = currentTime; 
      context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
      var base64ImageData = canvas.toDataURL();
      frames.push(base64ImageData);

      currentTime += interval;

      if (currentTime >= maxDuration) {
        console.log(frames);
        callback(frames);
      }
    }
  });
}

export default extractFramesFromVideo;
Share Improve this question asked Jun 1, 2017 at 1:38 Steven FabreSteven Fabre 911 silver badge2 bronze badges 3
  • If I move the context.drawImage outside of the while, it draws the first frame. – Steven Fabre Commented Jun 1, 2017 at 2:51
  • Possible duplicate of JavaScript: Extract video frames reliably – Kaiido Commented Jun 1, 2017 at 23:32
  • 2 I think the problem with this is that after changing video.currentTime, you need to actually wait for the video to seek to that position and update the frame data, then you can extract it. It may need to download some data, so it's not synchronous. The loadeddata event just indicates that the first frame as be downloaded (at least). I think, after changing currentTime, you'd need to either wait for the seeked event. – user993683 Commented Sep 16, 2018 at 18:43
Add a ment  | 

2 Answers 2

Reset to default 9

After tweaking your code to wait for the seeked event, and fixing a few bits and pieces, it seems to work fine:

async function extractFramesFromVideo(videoUrl, fps=25) {
  return new Promise(async (resolve) => {

    // fully download it first (no buffering):
    let videoBlob = await fetch(videoUrl).then(r => r.blob());
    let videoObjectUrl = URL.createObjectURL(videoBlob);
    let video = document.createElement("video");

    let seekResolve;
    video.addEventListener('seeked', async function() {
      if(seekResolve) seekResolve();
    });

    video.addEventListener('loadeddata', async function() {
      let canvas = document.createElement('canvas');
      let context = canvas.getContext('2d');
      let [w, h] = [video.videoWidth, video.videoHeight]
      canvas.width =  w;
      canvas.height = h;

      let frames = [];
      let interval = 1 / fps;
      let currentTime = 0;
      let duration = video.duration;

      while(currentTime < duration) {
        video.currentTime = currentTime;
        await new Promise(r => seekResolve=r);

        context.drawImage(video, 0, 0, w, h);
        let base64ImageData = canvas.toDataURL();
        frames.push(base64ImageData);

        currentTime += interval;
      }
      resolve(frames);
    });

    // set video src *after* listening to events in case it loads so fast
    // that the events occur before we were listening.
    video.src = videoObjectUrl; 

  });
}

Usage:

let frames = await extractFramesFromVideo("https://example./video.webm");

currentComponent.refs.videopreview is <video ref="videopreview" autoPlay></video> in HTML page Below is the code to extract the frame from the video.

   const getFrame = () => {

        const video = currentComponent.refs.videopreview;
        if(!video.srcObject) {
          return null;
        }
        const canvas = document.createElement('canvas');
        canvas.width = canvasWidth;
        canvas.height = canvasHeight;
        canvas.getContext('2d').drawImage(video, 0,0);
        const data = canvas.toDataURL('image/jpeg');     
        return data; // frame data
      }

getFrame function can be called at required interval.

本文标签: javascriptExtract frames from video in JSStack Overflow