
Cara mengkonversi video ke gif menggunakan javascript
Kali ini saya akan share trik cara merubah video menjadi gif hanya menggunakan javascript/jquery tanpa server side.
Kemarin saya sempat browsing cara membuat animasi gif menggunakan javascript, sayapun menemukan Animated_GIF.js yaitu sebuah librari javascript untuk membuat animasi gif dari objek gambar.
Saya pun langsung mempunyai ide bagaimana cara membuat gif dari hasil mengcapture video tiap milidetik lalu Animated_GIF akan menjadikannya sebuah animasi gif.
Ok langsung saja berikut step by stepnya:
HTML
Membuat form untuk input video, setting durasi video yang akan dikaptur dan kecepatan, panjang, lebar gif yang akan dibuat.
Duration <input type="txt" id="video_duration" value="10"/> seccond<br>
Speed <input type="txt" id="gif_speed" value="100"/> miliseccond<br>
Width <input type="txt" id="capture_width" value="400"/> pixel<br>
Height <input type="txt" id="capture_height" value="200"/> pixel<br>
<input type="file" id="videoFile" accept="video/*"/><br>
<div id="message"></div>
<video id="video" controls preload="none" width="400" onloadedmetadata="$(this).trigger('video_really_ready')"></video>
<hr>
<div id="persen"></div>
<div id="imageGif"></div>
<div id="screen"></div>
JS
1. Include Animated_GIF dan jQuery
<script defer src="//sole.github.io/Animated_GIF/dist/Animated_GIF.js"></script>
<script type="text/javascript" src="//code.jquery.com/jquery-2.2.0.min.js"></script>
2. Proses untuk input, mengcapture video
var URL = window.URL || window.webkitURL;
var displayMessage = function (message, isError) {
var element = document.querySelector('#message');
element.innerHTML = message;
element.className = isError ? 'error' : 'info';
}
var VideoSnapper = {
captureAsCanvas: function(video, options, handle) {
// Create canvas and call handle function
var callback = function() {
// Create canvas
var canvas = $('<canvas />').attr({
width: options.width,
height: options.height
})[0];
// Get context and draw screen on it
canvas.getContext('2d').drawImage(video, 0, 0, options.width, options.height);
// Seek video back if we have previous position
if (prevPos) {
// Unbind seeked event - against loop
$(video).unbind('seeked');
// Seek video to previous position
video.currentTime = prevPos;
}
// Call handle function (because of event)
handle.call(this, canvas.toDataURL('image/png'));
}
// If we have time in options
if (options.time && !isNaN(parseInt(options.time))) {
// Save previous (current) video position
var prevPos = video.currentTime;
// Seek to any other time
video.currentTime = options.time;
// Wait for seeked event
$(video).bind('seeked', callback);
return;
}
// Otherwise callback with video context - just for compatibility with calling in the seeked event
return callback.apply(video);
}
};
function playSelectedFile (event) {
$('#screen').empty();
var file = this.files[0];
var type = file.type;
var videoNode = document.getElementById('video');
var canPlay = videoNode.canPlayType(type);
if (canPlay === '') canPlay = 'no';
var message = 'Can play type "' + type + '": ' + canPlay;
var isError = canPlay === 'no';
displayMessage(message, isError);
if (isError) {
return;
}
var fileURL = URL.createObjectURL(file);
videoNode.src = fileURL;
videoNode.removeAttribute("controls");
videoNode.play();
}
document.getElementById('videoFile').addEventListener('change', playSelectedFile, false);
$(function() {
$('video').bind('video_really_ready', function() {
var video = this;
var duration = $('#video_duration').val();
var t = 1;
var opt = {
speed: $('#gif_speed').val(),
width: $('#capture_width').val(),
height: $('#capture_height').val()
};
var refreshIntervalId = setInterval(function(){
if(t > duration){
$('#persen').html('100 %');
clearInterval(refreshIntervalId);
gif(opt);
return
}
var canvases = $('canvas'),
w = opt.width,
h = opt.height;
VideoSnapper.captureAsCanvas(video, {
width: w,
height: h,
time: t
}, function(canvas) {
var k = DataUriToBinary(canvas),
l = new Blob([k], {
type: 'image/png'
}),
m = window.URL.createObjectURL(l);
$('#screen').append('<img src="' + m + '" class="videoImage"/>');
var b = (t / duration) * 100;
$('#persen').html(b + ' %');
t = (t + 0.2);
})
}, 1500);
video.pause();
});
});
3. Proses untuk membuat animasi gif
function gif(opt) {
var imgs = document.querySelectorAll('.videoImage');
var imageWidth = opt.width;
var imageHeight = opt.height;
var tasks = [];
function buildImageCallback(img) {
return function(gif) {
img.src = gif;
};
}
function getBuildGIFTask(img) {
return function(doneCallback) {
var ag = new Animated_GIF({
repeat: null, // Don't repeat
});
ag.setSize(img.clientWidth, img.clientHeight);
ag.addFrame(img);
var img2 = document.createElement('img');
if(img.nextSibling) {
img.parentNode.insertBefore(img2, img.nextSibling);
} else {
img.parentNode.appendChild(img2);
}
ag.getBase64GIF(function(gif) {
var originalSrc = img.src;
img.addEventListener('mouseenter', function() {
img.src = gif;
}, false);
img.addEventListener('mouseleave', function() {
img.src = originalSrc;
}, false);
doneCallback();
});
};
}
function runTasks(tasks) {
var nextTaskIndex = 0;
runNextTask();
//
function runNextTask() {
if(nextTaskIndex < tasks.length) {
console.log('running task', nextTaskIndex);
var task = tasks[nextTaskIndex];
task(function() {
nextTaskIndex++;
setTimeout(runNextTask, 100);
});
}
}
}
//
tasks.push(function(doneCallback) {
var agAll = new Animated_GIF({
repeat: 0, // repeat 0 = Repeat forever
});
agAll.setSize(imageWidth, imageHeight);
agAll.setDelay(opt.speed);
for(var i = 0; i < imgs.length; i++) {
var img = imgs[i];
agAll.addFrame(img);
}
var imgAll = document.createElement('img');
var lastRenderProgress = Date.now();
agAll.onRenderProgress(function(progress) {
var t = Date.now();
lastRenderProgress = t;
});
agAll.getBase64GIF(function(image) {
imgAll.src = image;
doneCallback();
});
imgAll.style.display = 'block';
document.getElementById('imageGif').appendChild(imgAll);
});
for(var i = 0; i < imgs.length; i++) {
tasks.push(getBuildGIFTask(imgs[i]));
}
runTasks(tasks, function() {
alert('All done!');
});
}
Full code
Duration <input type="txt" id="video_duration" value="10"/> seccond<br>
Speed <input type="txt" id="gif_speed" value="100"/> miliseccond<br>
Width <input type="txt" id="capture_width" value="400"/> pixel<br>
Height <input type="txt" id="capture_height" value="200"/> pixel<br>
<input type="file" id="videoFile" accept="video/*"/><br>
<div id="message"></div>
<video id="video" controls preload="none" width="400" onloadedmetadata="$(this).trigger('video_really_ready')"></video>
<hr>
<div id="persen"></div>
<div id="imageGif"></div>
<div id="screen"></div>
<script defer src="//sole.github.io/Animated_GIF/dist/Animated_GIF.js"></script>
<script type="text/javascript" src="//code.jquery.com/jquery-2.2.0.min.js"></script>
<script>
var URL = window.URL || window.webkitURL;
var displayMessage = function (message, isError) {
var element = document.querySelector('#message');
element.innerHTML = message;
element.className = isError ? 'error' : 'info';
}
var VideoSnapper = {
captureAsCanvas: function(video, options, handle) {
// Create canvas and call handle function
var callback = function() {
// Create canvas
var canvas = $('<canvas />').attr({
width: options.width,
height: options.height
})[0];
// Get context and draw screen on it
canvas.getContext('2d').drawImage(video, 0, 0, options.width, options.height);
// Seek video back if we have previous position
if (prevPos) {
// Unbind seeked event - against loop
$(video).unbind('seeked');
// Seek video to previous position
video.currentTime = prevPos;
}
// Call handle function (because of event)
handle.call(this, canvas.toDataURL('image/png'));
}
// If we have time in options
if (options.time && !isNaN(parseInt(options.time))) {
// Save previous (current) video position
var prevPos = video.currentTime;
// Seek to any other time
video.currentTime = options.time;
// Wait for seeked event
$(video).bind('seeked', callback);
return;
}
// Otherwise callback with video context - just for compatibility with calling in the seeked event
return callback.apply(video);
}
};
function playSelectedFile (event) {
$('#screen').empty();
var file = this.files[0];
var type = file.type;
var videoNode = document.getElementById('video');
var canPlay = videoNode.canPlayType(type);
if (canPlay === '') canPlay = 'no';
var message = 'Can play type "' + type + '": ' + canPlay;
var isError = canPlay === 'no';
displayMessage(message, isError);
if (isError) {
return;
}
var fileURL = URL.createObjectURL(file);
videoNode.src = fileURL;
videoNode.removeAttribute("controls");
videoNode.play();
}
document.getElementById('videoFile').addEventListener('change', playSelectedFile, false);
$(function() {
$('video').bind('video_really_ready', function() {
var video = this;
var duration = $('#video_duration').val();
var t = 1;
var opt = {
speed: $('#gif_speed').val(),
width: $('#capture_width').val(),
height: $('#capture_height').val()
};
var refreshIntervalId = setInterval(function(){
if(t > duration){
$('#persen').html('100 %');
clearInterval(refreshIntervalId);
gif(opt);
return
}
var canvases = $('canvas'),
w = opt.width,
h = opt.height;
VideoSnapper.captureAsCanvas(video, {
width: w,
height: h,
time: t
}, function(canvas) {
var k = DataUriToBinary(canvas),
l = new Blob([k], {
type: 'image/png'
}),
m = window.URL.createObjectURL(l);
$('#screen').append('<img src="' + m + '" class="videoImage"/>');
var b = (t / duration) * 100;
$('#persen').html(b + ' %');
t = (t + 0.2);
})
}, 1500);
video.pause();
});
});
function gif(opt) {
var imgs = document.querySelectorAll('.videoImage');
var imageWidth = opt.width;
var imageHeight = opt.height;
var tasks = [];
function buildImageCallback(img) {
return function(gif) {
img.src = gif;
};
}
function getBuildGIFTask(img) {
return function(doneCallback) {
var ag = new Animated_GIF({
repeat: null, // Don't repeat
});
ag.setSize(img.clientWidth, img.clientHeight);
ag.addFrame(img);
var img2 = document.createElement('img');
if(img.nextSibling) {
img.parentNode.insertBefore(img2, img.nextSibling);
} else {
img.parentNode.appendChild(img2);
}
ag.getBase64GIF(function(gif) {
var originalSrc = img.src;
img.addEventListener('mouseenter', function() {
img.src = gif;
}, false);
img.addEventListener('mouseleave', function() {
img.src = originalSrc;
}, false);
doneCallback();
});
};
}
function runTasks(tasks) {
var nextTaskIndex = 0;
runNextTask();
//
function runNextTask() {
if(nextTaskIndex < tasks.length) {
console.log('running task', nextTaskIndex);
var task = tasks[nextTaskIndex];
task(function() {
nextTaskIndex++;
setTimeout(runNextTask, 100);
});
}
}
}
//
tasks.push(function(doneCallback) {
var agAll = new Animated_GIF({
repeat: 0, // repeat 0 = Repeat forever
});
agAll.setSize(imageWidth, imageHeight);
agAll.setDelay(opt.speed);
for(var i = 0; i < imgs.length; i++) {
var img = imgs[i];
agAll.addFrame(img);
}
var imgAll = document.createElement('img');
var lastRenderProgress = Date.now();
agAll.onRenderProgress(function(progress) {
var t = Date.now();
lastRenderProgress = t;
});
agAll.getBase64GIF(function(image) {
imgAll.src = image;
doneCallback();
});
imgAll.style.display = 'block';
document.getElementById('imageGif').appendChild(imgAll);
});
for(var i = 0; i < imgs.length; i++) {
tasks.push(getBuildGIFTask(imgs[i]));
}
runTasks(tasks, function() {
alert('All done!');
});
}
</script>
Cara ini saya baru coba di browser pc dan hasilnya berjalan lancar berikut DEMO nya. Untuk di browser versi mobile silahkan sobat coba sendiri saja ya. Sekian dan terimakasih selamat mencoba.