[removed] Extract video frames reliably

前端 未结 2 1472
死守一世寂寞
死守一世寂寞 2020-11-27 13:01

I\'m working on a client-side project which lets a user supply a video file and apply basic manipulations to it. I\'m trying to extract the frames from the video reliably.

相关标签:
2条回答
  • 2020-11-27 13:33

    Here's a working function that was tweaked from this question:

    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.src = videoObjectUrl;
    
        // workaround chromium metadata bug (https://stackoverflow.com/q/38062864/993683)
        while((video.duration === Infinity || isNaN(video.duration)) && video.readyState < 2) {
          await new Promise(r => setTimeout(r, 1000));
          video.currentTime = 10000000*Math.random();
        }
        let duration = video.duration;
    
        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;
    
        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);
      });
    });
    

    }

    Usage:

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

    Note that there's currently no easy way to determine the actual/natural frame rate of a video unless perhaps you use ffmpeg.js, but that's a 10+ megabyte javascript file (since it's an emscripten port of the actual ffmpeg library, which is obviously huge).

    0 讨论(0)
  • 2020-11-27 13:55

    Mostly taken from this great answer by GameAlchemist :

    Since browsers doesn't respect videos' framerates, but instead "use of some tricks to make a match between the frame-rate of the movie and the refresh-rate of the screen", your assumption that every 30th of a second, a new frame will be painted is quite inaccurate.
    However, the timeupdate event should fire when the currentTime has changed, and we can assume that a new frame was painted.

    So, I would do it like so :

    document.querySelector('input').addEventListener('change', extractFrames, false);
    
    function extractFrames() {
      var video = document.createElement('video');
      var array = [];
      var canvas = document.createElement('canvas');
      var ctx = canvas.getContext('2d');
      var pro = document.querySelector('#progress');
    
      function initCanvas(e) {
        canvas.width = this.videoWidth;
        canvas.height = this.videoHeight;
      }
    
      function drawFrame(e) {
        this.pause();
        ctx.drawImage(this, 0, 0);
        /* 
        this will save as a Blob, less memory consumptive than toDataURL
        a polyfill can be found at
        https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
        */
        canvas.toBlob(saveFrame, 'image/jpeg');
        pro.innerHTML = ((this.currentTime / this.duration) * 100).toFixed(2) + ' %';
        if (this.currentTime < this.duration) {
          this.play();
        }
      }
    
      function saveFrame(blob) {
        array.push(blob);
      }
    
      function revokeURL(e) {
        URL.revokeObjectURL(this.src);
      }
      
      function onend(e) {
        var img;
        // do whatever with the frames
        for (var i = 0; i < array.length; i++) {
          img = new Image();
          img.onload = revokeURL;
          img.src = URL.createObjectURL(array[i]);
          document.body.appendChild(img);
        }
        // we don't need the video's objectURL anymore
        URL.revokeObjectURL(this.src);
      }
      
      video.muted = true;
    
      video.addEventListener('loadedmetadata', initCanvas, false);
      video.addEventListener('timeupdate', drawFrame, false);
      video.addEventListener('ended', onend, false);
    
      video.src = URL.createObjectURL(this.files[0]);
      video.play();
    }
    <input type="file" accept="video/*" />
    <p id="progress"></p>

    0 讨论(0)
提交回复
热议问题