У меня есть следующая функция, которая может создавать миниатюры из видео:

async function getThumbnailForVideo(videoUrl) {
  const video = document.createElement("video");
  const canvas = document.createElement("canvas");
  video.style.display = "none";
  canvas.style.display = "none";

  // Trigger video load
  await new Promise((resolve, reject) => {
    video.addEventListener("loadedmetadata", () => {
      video.width = video.videoWidth;
      video.height = video.videoHeight;
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      // Seek the video to 25%
      video.currentTime = video.duration * 0.25;
    });
    video.addEventListener("seeked", () => resolve());
    video.src = videoUrl;
  });

  // Draw the thumbnail
  canvas
    .getContext("2d")
    .drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
  const imageUrl = canvas.toDataURL("image/png");
  return imageUrl;
}

В сочетании с URL.createObjectURL я могу создать эскиз из выбранного пользователем видеофайла. Я создал следующий тестовый проект на StackBlitz для тестирования: Редактор приложений Предварительный просмотр приложения

Хотя кажется, что это нормально работает для Chrome и Safari, похоже, что Firefox не уважает EXIF-информацию видео и поэтому рисует ее неправильно.

В документации MDN для CanvasRenderingContext2D.drawImage прямо указано, что :

drawImage() будет игнорировать все метаданные EXIF ​​в изображениях, включая Ориентацию. Вы должны сами определить Ориентацию и использовать rotate (), чтобы исправить это.

Modernizr намекает на решение в своем определение функции exiforientation, если я смогу прочитать данные вращения из файла, так что мне нужно только выполнить дополнительные преобразования в Firefox .

Мне любопытно, есть ли более идемпотентное решение для рисования изображения из HTMLVideoElement во всех браузерах?

0
kiyui 22 Июл 2020 в 11:13

1 ответ

Лучший ответ

Таким образом, оказывается, что тест Modernizr exiforientation проверяет только то, учитывает ли элемент img данные EXIF ​​изображения, но не то, правильно ли отображается то же изображение, нарисованное на холсте.

Вместо этого я решил создать свой собственный тест, нарисовав известное видео на холсте и протестировав его. Я создал видео, используя ffmpeg:

ffmpeg -filter_complex \
        "color=color=#ffffff:duration=1us:size=4x4[bg]; \
         color=color=#ff0000:duration=1us:size=2x2[r]; \
         color=color=#00ff00:duration=1us:size=2x2[g]; \
         color=color=#0000ff:duration=1us:size=2x2[b]; \
         [bg][r]overlay=x=2:y=0:format=rgb:alpha=premultiplied[bg+r]; \
         [bg+r][g]overlay=x=0:y=2:format=rgb:alpha=premultiplied[bg+r+g]; \
         [bg+r+g][b]overlay=x=2:y=2:format=rgb:alpha=premultiplied[bg+r+g+b]" \
       -map "[bg+r+g+b]" \
       -y wrgb-0.mp4

ffmpeg -i wrgb-0.mp4 -c copy -metadata:s:v:0 rotate=180 -y wrgb-180.mp4

Используя одну и ту же демонстрацию, я вижу, что Chrome и Firefox создают разные превью видео на холст.

  • Chrome: wrgb-180 предварительный просмотр в Chrome, синий, зеленый, красный, белый
  • Firefox: wrgb-180 предварительный просмотр в Firefox, белый, красный, зеленый, синий

Затем мне просто нужна была функция, которая, учитывая массив значений RGBA с холста, выдавала бы узор на холсте:

function getColourPattern(rgbaData) {
  let pattern = "";
  for (let i = 0; i < rgbaData.length; i += 4) {
    const r = rgbaData[i] / 255;
    const g = rgbaData[i + 1] / 255;
    const b = rgbaData[i + 2] / 255;
    const w = (r + g + b) / 3;

    if (w > 0.9) {
      pattern += "w";
      continue;
    }

    switch (Math.max(r, g, b)) {
      case r:
        pattern += "r";
        break;
      case g:
        pattern += "g";
        break;
      case b:
        pattern += "b";
        break;
    }
  }

  return pattern;
}

Это возвращает bbggbbggrrwwrrww в Chrome и Safari и wwrrwwrrggbbggbb в Firefox (с отключенным отпечатком холста)

Затем я использовал basenc --base64 wrgb-180.mp4 -w 0, чтобы получить представление видео в формате base64, чтобы я мог встроить его в одну тестовую функцию:

export async function canvasUsesEXIF() {
  const videoUrl = `data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAvxtZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1OSByMjk5OSAyOTY0OTRhIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTEgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAPmWIhAAt/9pbuD7Z/gvI3kF2QzYeJnVbANgW8XnGVlnoDJNW7zJawMem6POfQ3cvmVl9l7mrZDdjuR26xB2/AAADAm1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAAoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIsdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAAQAAAAAAEAAAABAAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAAKAAAAAAAAQAAAAABpG1kaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAMgAAAAIAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAU9taW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEPc3RibAAAAKtzdHNkAAAAAAAAAAEAAACbYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAQASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADVhdmNDAWQACv/hABhnZAAKrNlfnnwEQAAAAwBAAAAMg8SJZYABAAZo6+PLIsD9+PgAAAAAEHBhc3AAAAABAAAAAQAAABhzdHRzAAAAAAAAAAEAAAABAAACAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAL0AAAAAQAAABRzdGNvAAAAAAAAAAEAAAAwAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1OC40NS4xMDA=`;
  const video = document.createElement("video");
  const canvas = document.createElement("canvas");
  video.style.display = "none";
  canvas.style.display = "none";

  await new Promise((resolve, reject) => {
    video.addEventListener("canplay", () => {
      video.width = video.videoWidth;
      video.height = video.videoHeight;
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      video.currentTime = 0;
    });
    video.addEventListener("seeked", () => resolve());
    video.src = videoUrl;
  });

  const context = canvas.getContext("2d");
  context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
  const { data } = context.getImageData(0, 0, 4, 4);

  return getColourPattern(data) === "bbggbbggrrwwrrww";
}

Теперь, предполагая, что у вас есть метаданные поворота видео, вы сможете проверить, нужно ли вам вручную повернуть его на холсте 🤓

Изменить 1 :

Это должно избавить Firefox в Windows от выдачи ошибки NS_ERROR_NOT_AVAILABLE.

9c9
<     video.addEventListener("loadedmetadata", () => {
---
>     video.addEventListener("canplay", () => {
0
kiyui 8 Сен 2020 в 10:03
Это прекрасно работает в Chrome на Windows, но Chrome на Android выдает видеоошибку: PIPELINE_ERROR_DECODE: повторная инициализация видеодекодера не удалась.
 – 
Bruno Marotta
17 Авг 2020 в 14:39
Возможно, вы могли бы попробовать использовать другое видео? В Firefox для Android и Windows я столкнулся с отдельной проблемой, которая была устранена путем замены loadedmetadata на canplay.
 – 
kiyui
8 Сен 2020 в 10:00