生成由文件输入在特定时间选择的视频文件的缩略图/快照

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

本文关键字:文件 的视频 略图 选择 快照 定时间 输入      更新时间:2023-09-26

如何在视频中静默地抓取通过<input type="file">在特定时间选择的视频文件的快照(即没有可见元素、闪烁、声音等)?

有四个主要步骤:

  1. 创建<canvas><video>元素。
  2. URL.createObjectURL生成的视频文件的src加载到 <video> 元素中,并通过侦听正在触发的特定事件来等待其加载。
  3. 将视频的时间设置为要拍摄快照并侦听其他事件的位置。
  4. 使用画布抓取图像。

步骤 1 - 创建元素

这很简单:只需创建一个<canvas>和一个<video>元素并将它们附加到<body>(或者真的在任何地方,这并不重要):

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

请注意,视频元素具有属性 muted 。不要放置任何其他属性,如 autoplaycontrols 。另请注意,它们都具有类snapshot-generator。这样我们就可以为它们设置样式,以便它们不碍事:

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

一些浏览器将它们设置为 display: none ,但其他浏览器除非在页面上呈现,否则会出现严重问题,因此我们只是将它们缩小,以便它们基本上不可见。(但是不要将它们移到视口之外,否则您可能会在页面上看到一些丑陋的滚动条。

第 2 步 - 加载视频

这就是事情开始变得棘手的地方。您需要收听事件以了解何时继续。不同的浏览器会触发不同的事件,不同的时间和不同的顺序,所以我会省去你的工作量。在视频准备就绪之前,有三个事件必须始终至少触发一次;它们是:

  • 加载的元数据
  • 加载的数据
  • 暂停

为这些事件设置事件处理程序,并跟踪已触发的事件数。一旦所有三个都开火了,您就可以继续了。请记住,由于其中一些事件可能会触发多次,因此您只想处理触发的每种类型的第一个事件,并放弃后续触发。我使用了jQuery的.one,它解决了这个问题。

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);

源应该只是通过URL.createObjectURL(file)创建的对象URL,其中file是文件对象。

步骤 3 - 设置时间

此阶段与上一个阶段类似:设置时间,然后侦听事件。在前面代码的if块中:

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

幸运的是,这次只有一个事件,所以它非常清晰简洁。最后。。。

步骤 4 - 获取快照

这部分只是使用 <canvas> 元素来抓取屏幕截图。在我们的 seeked 事件处理程序中:

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();

画布需要匹配视频的尺寸(而不是<video>元素)才能获得正确的图像。此外,我们正在设置画布的内部.height.width属性,而不是画布高度/宽度 CSS 样式值。

快照的值是一个数据 URI,它基本上只是一个字符串,以 data:image/jpeg;base64 开头,然后是 base64 数据。

我们最终的JS代码应该看起来像这样:

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);

庆祝!

你的图像在base64中!将其发送到您的服务器,将其作为<img>元素的src,或者其他什么。

例如,您可以将其解码为二进制并直接将其写入文件(先修剪前缀),该文件将成为JPEG图像文件。

您还可以使用它在上传视频时提供视频预览。如果要将其作为<img>src,请使用完整的数据 URI(不要删除前缀)。