2015-09-21 15 views
7

Sto lavorando a un progetto sul lato client che consente a un utente di fornire un file video e applicare le manipolazioni di base ad esso. Sto cercando di estrarre i frame dal video in modo affidabile. Al momento ho un <video> quale sto caricando il video selezionato in, e poi tirando fuori ogni fotogramma come segue:JavaScript: estrarre i frame video in modo affidabile

  1. cercare l'inizio
  2. in pausa il video
  3. Disegnare <video> ad un <canvas>
  4. Cattura la cornice dalla tela con .toDataUrl()
  5. Cercare avanti di 1/30 secondi (1 fotogramma).
  6. sciacquare e ripetere

Questo è un processo piuttosto inefficiente, e più in particolare, si sta rivelando inaffidabili come sto spesso rimanere bloccati fotogrammi. Questo sembra non aver aggiornato l'effettivo elemento <video> prima di disegnare sul canvas.

Preferisco non dover caricare il video originale sul server solo per dividere i frame e quindi scaricarli nuovamente sul client.

Qualsiasi suggerimento per un modo migliore di farlo è molto apprezzato. L'unica avvertenza è che ho bisogno che funzioni con qualsiasi formato supportato dal browser (la decodifica in JS non è una grande opzione).

+0

invece di cercare 1/30s in avanti, si dovrebbe allegare una funzione sul 'timeupdate' evento del video. Ma chiaramente, se non hai bisogno di farlo sul lato client, non utilizzare un browser per questo, ffmpeg o qualsiasi strumento video sarà più potente per questo. – Kaiido

+0

@Kalido Preferisco piuttosto gestire il lato server, ma il video proviene dal client e ho bisogno dei frame sul client, quindi se posso evitare un ciclo di upload/download, starei molto meglio. Inoltre, per quanto riguarda la ricerca, ho riscontrato problemi con i dispositivi lenti che perdono i frame perché non riescono a gestire i dati del frame abbastanza velocemente, tuttavia, non ho familiarità con l'evento 'timeupdate', quindi lo esaminerò . Inoltre, specificando il tempo di ricerca per ciascun fotogramma, posso controllare il framerate se necessario. –

+0

Ok, quindi per favore potresti chiarire questo punto (che è per un sito web, lato client) in una modifica alla tua domanda, che non era affatto chiaro. Inoltre, il framerate nel browser non è assolutamente costante, quindi non dovresti fare affidamento su di esso. Ma ad ogni nuova cornice dipinta, l'evento timeupdate dovrebbe attivarsi, quindi credo che sarà più affidabile. – Kaiido

risposta

7

Mostly taken from this great answer da GameAlchemist:

Dal browser non rispetta framerate video, ma invece 'l'uso di alcuni trucchi per fare una corrispondenza tra il frame-rate del film e il refresh rate dello schermo' , la tua ipotesi che ogni 30esimo di secondo, una nuova cornice verrà dipinta è abbastanza impreciso.
Tuttavia, lo timeupdate event deve essere attivato quando il tempo corrente è cambiato e possiamo presumere che sia stato dipinto un nuovo fotogramma.

Quindi, vorrei farlo in questo modo:

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.autoplay = true; 
 
    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]); 
 
}
<input type="file" accept="video/*" /> 
 
<p id="progress"></p>

+1

La mia ipotesi non è che ci sarà un nuovo frame ogni 30 secondi, ma se ottengo 1 frame ogni 1/30 secondi, finirò con 30 FPS. L'errore che ho fatto presuppone che i frame che ottengo saranno effettivamente il frame corretto e attuale. Apprezzo l'aiuto.L'evento timeupdate è molto utile. TY :) –

+0

Questo metodo non è esattamente affidabile: restituisce 4172 frame, quindi 4573 in una seconda esecuzione, in un video che ha solo 250 frame secondo ffmpeg. 10 secondi @ 25 fps: http://www.w3schools.com/html/mov_bbb.mp4 –

+0

@DineshBolkensteyn, hai letto l'intestazione di questa risposta e la risposta collegata? No, questo metodo non estrae in modo affidabile tutti i frame video presenti nel file, solo quelli che sono stati dipinti dal browser, che non rispettano il framerate del video. – Kaiido

Problemi correlati