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?