Как правильно нарисовать миниатюру видео на холсте во всех браузерах?

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

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 намекает на решение через его обнаружение функции эксиориентации, должен ли я иметь возможность читать данные поворота из файла, чтобы мне нужно было выполнять только дополнительные преобразования в Firefox .

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


person kiyui    schedule 22.07.2020    source источник


Ответы (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", () => {
person kiyui    schedule 25.07.2020
comment
Это прекрасно работает в Chrome на Windows, но Chrome на Android выдает видеоошибку: PIPELINE_ERROR_DECODE: повторная инициализация видеодекодера не удалась. - person Bruno Marotta; 17.08.2020
comment
@BrunoMarotta, возможно, вы могли бы попробовать использовать другое видео? В Firefox для Android и Windows я столкнулся с отдельной проблемой, которая была устранена путем замены loadedmetadata на canplay. - person kiyui; 08.09.2020