ffmeg as worker can be found at https://github.com/Kagami/ffmpeg.js/
Final build can be obtained via wget https://unpkg.com/[email protected]/ffmpeg-worker-mp4.js
ffmeg as worker can be found at https://github.com/Kagami/ffmpeg.js/
Final build can be obtained via wget https://unpkg.com/[email protected]/ffmpeg-worker-mp4.js
| <style> | |
| * {font-family: sans-serif;} | |
| </style> | |
| <progress id="progress" value="0" max="60" min="0" style="width: 300px"></progress> | |
| <br> | |
| <canvas id="canvas" width="150" height="150"></canvas> | |
| <video id="awesome" width="150" height="150" controls autoplay loop></video> | |
| <br> | |
| Status: <span id="status">Idle</span> | |
| <a style="display:none" id="download" download="clock.webm">Download WebM</a> | |
| <pre id="ffmsg"></pre> | |
| <div id="images"></div> | |
| <script> | |
| // use requestanimation frame, woo! | |
| (function() { | |
| var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || | |
| window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; | |
| window.requestAnimationFrame = requestAnimationFrame; | |
| })(); | |
| //stolen wholesale off mozilla's wiki | |
| // the actual demo code, yaaay | |
| var last_time = +new Date; | |
| var progress = document.getElementById('progress'); | |
| const images = [] | |
| const $ = id => document.getElementById( id ) | |
| const worker = new Worker('/ffmpeg-worker-mp4.js') | |
| function pad(n, width, z) { | |
| z = z || '0'; | |
| n = n + ''; | |
| return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; | |
| } | |
| function nextFrame(){ | |
| progress.value++; | |
| var context = clock(last_time += 1000); | |
| const img = new Image() | |
| , mimeType = 'image/jpeg' | |
| const imgString = $('canvas').toDataURL(mimeType,1) | |
| const data = convertDataURIToBinary( imgString ) | |
| images.push({ | |
| name: `img${ pad( images.length, 3 ) }.jpeg`, | |
| data | |
| }) | |
| img.src = imgString | |
| $('images').appendChild( img ) | |
| if(progress.value / progress.max < 1){ | |
| requestAnimationFrame(nextFrame); | |
| $('status').innerHTML = "Drawing Frames"; | |
| }else{ | |
| $('status').innerHTML = "Compiling Video"; | |
| requestAnimationFrame(finalizeVideo); // well, should probably use settimeout instead | |
| } | |
| } | |
| // https://semisignal.com/tag/ffmpeg-js/ | |
| function convertDataURIToBinary(dataURI) { | |
| var base64 = dataURI.replace(/^data[^,]+,/,''); | |
| var raw = window.atob(base64); | |
| var rawLength = raw.length; | |
| var array = new Uint8Array(new ArrayBuffer(rawLength)); | |
| for (i = 0; i < rawLength; i++) { | |
| array[i] = raw.charCodeAt(i); | |
| } | |
| return array; | |
| }; | |
| //**blob to dataURL** | |
| function blobToDataURL(blob, callback) { | |
| var a = new FileReader(); | |
| a.onload = function(e) {callback(e.target.result);} | |
| a.readAsDataURL(blob); | |
| } | |
| let start_time | |
| function finalizeVideo(){ | |
| start_time = +new Date; | |
| const msgs = $('ffmsg') | |
| let messages = ''; | |
| worker.onmessage = function(e) { | |
| var msg = e.data; | |
| switch (msg.type) { | |
| case "stdout": | |
| case "stderr": | |
| messages += msg.data + "\n"; | |
| break; | |
| case "exit": | |
| console.log("Process exited with code " + msg.data); | |
| //worker.terminate(); | |
| break; | |
| case 'done': | |
| const blob = new Blob([msg.data.MEMFS[0].data], { | |
| type: "video/mp4" | |
| }); | |
| done( blob ) | |
| break; | |
| } | |
| msgs.innerHTML = messages | |
| }; | |
| // https://trac.ffmpeg.org/wiki/Slideshow | |
| // https://semisignal.com/tag/ffmpeg-js/ | |
| worker.postMessage({ | |
| type: 'run', | |
| TOTAL_MEMORY: 268435456, | |
| //arguments: 'ffmpeg -framerate 24 -i img%03d.jpeg output.mp4'.split(' '), | |
| arguments: ["-r", "20", "-i", "img%03d.jpeg", "-c:v", "libx264", "-crf", "1", "-vf", "scale=150:150", "-pix_fmt", "yuv420p", "-vb", "20M", "out.mp4"], | |
| //arguments: '-r 60 -i img%03d.jpeg -c:v libx264 -crf 1 -vf -pix_fmt yuv420p -vb 20M out.mp4'.split(' '), | |
| MEMFS: images | |
| }); | |
| // Updated recommented arguments | |
| /* | |
| worker.postMessage({ | |
| type: 'run', | |
| TOTAL_MEMORY: 268435456, | |
| arguments: [ | |
| //"-r", opts.state.frameRate.toString(), | |
| "-framerate", opts.state.frameRate.toString(), | |
| "-frames:v", imgs.length.toString(), | |
| "-an", // disable sound | |
| "-i", "img%03d.jpeg", | |
| "-c:v", "libx264", | |
| "-crf", "17", // https://trac.ffmpeg.org/wiki/Encode/H.264 | |
| "-filter:v", | |
| `scale=${w}:${h}`, | |
| "-pix_fmt", "yuv420p", | |
| "-b:v", "20M", | |
| "out.mp4"], | |
| MEMFS: imgs | |
| });*/ | |
| /*video.compile(false, function(output){ | |
| $('awesome').src = url; //toString converts it to a URL via Object URLs, falling back to DataURL | |
| $('download').style.display = ''; | |
| $('download').href = url; | |
| });*/ | |
| } | |
| function done(output) { | |
| const url = webkitURL.createObjectURL(output); | |
| var end_time = +new Date; | |
| $('status').innerHTML = "Compiled Video in " + (end_time - start_time) + "ms, file size: " + Math.ceil(output.size / 1024) + "KB"; | |
| $('awesome').src = url; //toString converts it to a URL via Object URLs, falling back to DataURL | |
| $('download').style.display = ''; | |
| $('download').href = url; | |
| } | |
| nextFrame(); | |
| function clock(time){ | |
| var now = new Date(); | |
| now.setTime(time); | |
| var ctx = document.getElementById('canvas').getContext('2d'); | |
| ctx.save(); | |
| ctx.fillStyle = 'white' | |
| ctx.fillRect(0,0,150,150); // videos cant handle transprency | |
| ctx.translate(75,75); | |
| ctx.scale(0.4,0.4); | |
| ctx.rotate(-Math.PI/2); | |
| ctx.strokeStyle = "black"; | |
| ctx.fillStyle = "white"; | |
| ctx.lineWidth = 8; | |
| ctx.lineCap = "round"; | |
| // Hour marks | |
| ctx.save(); | |
| for (var i=0;i<12;i++){ | |
| ctx.beginPath(); | |
| ctx.rotate(Math.PI/6); | |
| ctx.moveTo(100,0); | |
| ctx.lineTo(120,0); | |
| ctx.stroke(); | |
| } | |
| ctx.restore(); | |
| // Minute marks | |
| ctx.save(); | |
| ctx.lineWidth = 5; | |
| for (i=0;i<60;i++){ | |
| if (i%5!=0) { | |
| ctx.beginPath(); | |
| ctx.moveTo(117,0); | |
| ctx.lineTo(120,0); | |
| ctx.stroke(); | |
| } | |
| ctx.rotate(Math.PI/30); | |
| } | |
| ctx.restore(); | |
| var sec = now.getSeconds(); | |
| var min = now.getMinutes(); | |
| var hr = now.getHours(); | |
| hr = hr>=12 ? hr-12 : hr; | |
| ctx.fillStyle = "black"; | |
| // write Hours | |
| ctx.save(); | |
| ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec ) | |
| ctx.lineWidth = 14; | |
| ctx.beginPath(); | |
| ctx.moveTo(-20,0); | |
| ctx.lineTo(80,0); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| // write Minutes | |
| ctx.save(); | |
| ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec ) | |
| ctx.lineWidth = 10; | |
| ctx.beginPath(); | |
| ctx.moveTo(-28,0); | |
| ctx.lineTo(112,0); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| // Write seconds | |
| ctx.save(); | |
| ctx.rotate(sec * Math.PI/30); | |
| ctx.strokeStyle = "#D40000"; | |
| ctx.fillStyle = "#D40000"; | |
| ctx.lineWidth = 6; | |
| ctx.beginPath(); | |
| ctx.moveTo(-30,0); | |
| ctx.lineTo(83,0); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.arc(0,0,10,0,Math.PI*2,true); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(95,0,10,0,Math.PI*2,true); | |
| ctx.stroke(); | |
| ctx.fillStyle = "#555"; | |
| ctx.arc(0,0,3,0,Math.PI*2,true); | |
| ctx.fill(); | |
| ctx.restore(); | |
| ctx.beginPath(); | |
| ctx.lineWidth = 14; | |
| ctx.strokeStyle = '#325FA2'; | |
| ctx.arc(0,0,142,0,Math.PI*2,true); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| return ctx; | |
| } | |
| </script> |
Great gist sir. Do you think you can help me use ffmpeg like I descibed in this question please?