Cara mengkonversi video ke gif menggunakan javascript

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.

keyboard_arrow_up