const byteToKBScale = 0.0009765625; const displayedSize=500; const scale = window.devicePixelRatio; const yearDisplay=document.getElementById('yearDisplay'); yearDisplay.innerHTML=new Date().getFullYear(); function encode64(input) { var output = '', i = 0, l = input.length, key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', chr1, chr2, chr3, enc1, enc2, enc3, enc4; while (i < l) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) enc3 = enc4 = 64; else if (isNaN(chr3)) enc4 = 64; output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); } return output; } var inputVideoClipFile=document.getElementById('inputVideoClipFile'); var inputVideoClipFileBtn=document.getElementById('inputVideoClipFileBtn'); var inputVideoDetails=document.getElementById('inputVideoDetails'); inputVideoClipFileBtn.addEventListener('click', () => { let clickEvent = new MouseEvent('click', { view: window, bubbles: false, cancelable: false }); inputVideoClipFile.dispatchEvent(clickEvent); }); var loadingBar=document.getElementById('loadingBar'); var continueCallback=true; var FPS=0; function toggleImageSmoothing(_CANVAS, isEnabled) { _CANVAS.getContext('2d').mozImageSmoothingEnabled = isEnabled; _CANVAS.getContext('2d').webkitImageSmoothingEnabled = isEnabled; _CANVAS.getContext('2d').msImageSmoothingEnabled = isEnabled; _CANVAS.getContext('2d').imageSmoothingEnabled = isEnabled; } function scaleCanvas(_CANVAS, videoObj, vidHeight, vidWidth, scale) { _CANVAS['style']['height'] = `${vidHeight}px`; _CANVAS['style']['width'] = `${vidWidth}px`; let cWidth=vidWidth*scale; let cHeight=vidHeight*scale; _CANVAS.width=cWidth; _CANVAS.height=cHeight; toggleImageSmoothing(_CANVAS, true); _CANVAS.getContext('2d').scale(scale, scale); } function readFileAsDataURL(file) { return new Promise((resolve,reject) => { let fileredr = new FileReader(); fileredr.onload = () => resolve(fileredr.result); fileredr.onerror = () => reject(fileredr); fileredr.readAsDataURL(file); }); } const loadVideo = (url) => new Promise((resolve, reject) => { var vid = document.createElement('video'); vid.addEventListener('canplay', () => resolve(vid)); vid.addEventListener('error', (err) => reject(err)); vid.src = url; }); inputVideoClipFile.addEventListener('change', async(evt) => { if (!window.FileReader) { alert('Your browser does not support HTML5 "FileReader" function required to open a file.'); return; } let file = evt.target.files[0]; if(!file) return; let fileName=file.name; let fileType=file.type; let fileSize=(file.size/1024).toFixed(2); let b64Str = await readFileAsDataURL(file); let videoObj=await loadVideo(b64Str); videoObj.autoplay=false; videoObj.muted=true; videoObj.loop=false; let exactVideoDuration=videoObj.duration; let vidDuration=parseInt(exactVideoDuration); let vidHeight=videoObj.videoHeight; // 720 let vidWidth=videoObj.videoWidth; // 1280 videoObj.height=vidHeight; videoObj.width=vidWidth; videoObj['style']['height']=`${vidHeight}px`; videoObj['style']['width']=`${vidWidth}px`; document.getElementById('inputVideoPreview').appendChild(videoObj); let videoDetails='ℹ '+[ `File Name: ${fileName}`, `Type: ${fileType}`, `Size: ${fileSize} ㎅`, `Frame(ᴡ ⨯ ʜ): ${vidWidth} ᵖˣ ⨯ ${vidHeight} ᵖˣ`, `Length: 00:00:${ vidDuration >=10 ? vidDuration : ('0'+vidDuration)}` ].join(' │ ') +''; inputVideoDetails.innerHTML=videoDetails; let _CANVAS = document.createElement('canvas'); scaleCanvas(_CANVAS, videoObj, vidHeight, vidWidth, scale); document.getElementById('hiddenCanvas').appendChild(_CANVAS); // =============== calculate displayed sizes ==================== let totalFrames=33; if(exactVideoDuration <= 10) { totalFrames=33; } else if(exactVideoDuration <= 12) { totalFrames=25; } else if(exactVideoDuration <= 15) { totalFrames=20; } else if(exactVideoDuration <= 25) { totalFrames=12; } else if(exactVideoDuration <= 30) { totalFrames=10; } else if(exactVideoDuration <= 35) { totalFrames=8; } else if(exactVideoDuration <= 42) { totalFrames=7; } else if(exactVideoDuration <= 60) { totalFrames=5; } let sizeBenchmark=vidHeight; if(vidWidth>vidHeight) { sizeBenchmark=vidWidth; } let scaleRatio=parseFloat(displayedSize/sizeBenchmark); let displayedHeight=scaleRatio*vidHeight; let displayedWidth=scaleRatio*vidWidth; videoObj['style']['height']=`${displayedHeight}px`; videoObj['style']['width']=`${displayedWidth}px`; scaleCanvas(_CANVAS, videoObj, displayedHeight, displayedWidth, scale); var encoder = new GIFEncoder(vidWidth, vidHeight); encoder.setRepeat(0); // 0 for repeat, -1 for no-repeat encoder.setDelay(0); // frame delay in ms // 500 encoder.setQuality(16); // [1,30] | Best=1 | >20 not much speed improvement. 10 is default. // Sets frame rate in frames per second var startTime=0; var frameIndex=0; var staticFrames=''; var jsonArrRecords=[]; var jsonObjRecord={}; const step = async() => { // in milliseconds startTime=( startTime==0 ? Date.now() : 0); _CANVAS.getContext('2d').drawImage(videoObj, 0, 0, displayedWidth, displayedHeight); encoder.addFrame(_CANVAS.getContext('2d')); let frameB64Str=_CANVAS.toDataURL(); staticFrames+=`Frame #${frameIndex++}
`; if(FPS==0) { let ms_elapsed = ((Date.now()) - startTime); FPS=(frameIndex / ms_elapsed)*1000.0; console.log('FPS: '+FPS+' | Duration: '+exactVideoDuration); let encodeDelaySetting=( (FPS*exactVideoDuration) >= totalFrames ) ? 0 : (( (totalFrames*1.0)/exactVideoDuration)-FPS); encodeDelaySetting=Math.floor(encodeDelaySetting*1000); console.log(encodeDelaySetting); encoder.setDelay(encodeDelaySetting); } if(continueCallback) { videoObj.requestVideoFrameCallback(step); } }; videoObj.addEventListener('play', (vEvt) => { if(continueCallback) { videoObj.requestVideoFrameCallback(step); } encoder.start(); loadingBar['style']['display']='block'; loadingBar['style']['background-image']='url(img/loading.gif)'; loadingBar['style']['background-position']='center'; loadingBar['style']['background-repeat']='no-repeat'; loadingBar['style']['background-size']='contain'; loadingBar['style']['height']='2rem'; loadingBar['style']['width']='19rem'; }, false); videoObj.addEventListener('ended', (vEvt) => { loadingBar['style']['display']='none'; continueCallback=false; encoder.finish(); var fileType='image/gif'; var fileName = `gif-output-${(new Date().toGMTString().replace(/(\s|,|:)/g,''))}.gif`; var readableStream=encoder.stream(); var binary_gif =readableStream.getData(); var b64Str = 'data:'+fileType+';base64,'+encode64(binary_gif); var fileSize = readableStream.bin.length*byteToKBScale; fileSize=fileSize.toFixed(2); let dwnlnk = document.createElement('a'); dwnlnk.download = fileName; dwnlnk.innerHTML = `💾 Save`; dwnlnk.className = 'btn btn-outline-dark'; dwnlnk.href = b64Str; let htmlStr=''; htmlStr+=''; htmlStr+=''; htmlStr+=`

🎬 Output GIF

${fileName}
${staticFrames}
`; htmlStr+=''; htmlStr+=''; htmlStr+=''; htmlStr+=''; htmlStr+='ℹ '+[ `Type: ${fileType}`, `Size: ${fileSize} ㎅`, `# of Frame(s): ${frameIndex}`, `Frame (ᴡ ⨯ ʜ): ${ parseInt(vidWidth)} ᵖˣ ⨯ ${vidHeight} ᵖˣ`, `${dwnlnk.outerHTML}` ].join(' │ ') +''; htmlStr+=''; htmlStr+=''; inputVideoDetails.insertAdjacentHTML('afterend', htmlStr); }, false); videoObj.play(); });