繁体   English   中英

使用 HTML5 和 JavaScript 从视频中捕获帧

[英]Capture frames from video with HTML5 and JavaScript

我想每 5 秒从视频中捕获一帧。

这是我的 JavaScript 代码:

video.addEventListener('loadeddata', function() {
    var duration = video.duration;
    var i = 0;

    var interval = setInterval(function() {
        video.currentTime = i;
        generateThumbnail(i);
        i = i+5;
        if (i > duration) clearInterval(interval);
    }, 300);
});

function generateThumbnail(i) {     
    //generate thumbnail URL data
    var context = thecanvas.getContext('2d');
    context.drawImage(video, 0, 0, 220, 150);
    var dataURL = thecanvas.toDataURL();

    //create img
    var img = document.createElement('img');
    img.setAttribute('src', dataURL);

    //append img in container div
    document.getElementById('thumbnailContainer').appendChild(img);
}

我遇到的问题是第一个生成的两个图像是相同的,并且没有生成持续时间为 5 秒的图像。 我发现缩略图是在< video>标签中显示特定时间的视频帧之前生成的。

例如,当video.currentTime = 5时,生成第 0 帧的图像。 然后视频帧跳转到时间 5s。 所以当video.currentTime = 10时,生成第 5 帧的图像。

原因

问题是寻找视频(通过设置它的currentTime )是异步的。

您需要收听所seeked事件,否则它将冒着采用实际当前帧的风险,这可能是您的旧值。

由于它是异步的,因此您不能使用setInterval()因为它也是异步的,并且在寻找下一帧时您将无法正确同步。 不需要使用setInterval()因为我们将利用所seeked事件来保持一切同步。

解决方案

通过稍微重新编写代码,您可以使用所seeked事件来浏览视频以捕获正确的帧,因为该事件通过设置currentTime属性确保我们实际上处于我们请求的帧。

示例

// global or parent scope of handlers
var video = document.getElementById("video"); // added for clarity: this is needed
var i = 0;

video.addEventListener('loadeddata', function() {
    this.currentTime = i;
});

将此事件处理程序添加到聚会中:

video.addEventListener('seeked', function() {

  // now video has seeked and current frames will show
  // at the time as we expect
  generateThumbnail(i);

  // when frame is captured, increase here by 5 seconds
  i += 5;

  // if we are not past end, seek to next interval
  if (i <= this.duration) {
    // this will trigger another seeked event
    this.currentTime = i;
  }
  else {
    // Done!, next action
  }
});

如果您想从视频中提取所有帧,请参阅此答案 下面的示例假设您希望按照 OP 的要求每 5 秒提取一帧。


这个答案需要在撰写本文时 Chrome 和 Edge 支持的WebCodecs。

<canvas id="canvasEl"></canvas>
<script type="module">
  import getVideoFrames from "https://deno.land/x/get_video_frames@v0.0.8/mod.js"

  let ctx = canvasEl.getContext("2d");

  // `getVideoFrames` requires a video URL as input.
  // If you have a file/blob instead of a videoUrl, turn it into a URL like this:
  let videoUrl = URL.createObjectURL(fileOrBlob);

  const saveFrameEverySeconds = 5;
  let elapsedSinceLastSavedFrame = 0;
  await getVideoFrames({
    videoUrl,
    onFrame(frame) {  // `frame` is a VideoFrame object: 
      elapsedSinceLastSavedFrame += frame.duration / 1e6; // frame.duration is in microseconds, so we convert to seconds
      if(elapsedSinceLastSavedFrame > saveFrameEverySeconds) {
        ctx.drawImage(frame, 0, 0, canvasEl.width, canvasEl.height);
        elapsedSinceLastSavedFrame = 0;
      }
      frame.close();
    },
    onConfig(config) {
      canvasEl.width = config.codedWidth;
      canvasEl.height = config.codedHeight;
    },
  });
  
  URL.revokeObjectURL(fileOrBlob); // revoke URL to prevent memory leak
</script>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM