Improve performance in converting video blob to gif - javascript

I have a program(Here) that converts a video blob into a gif using getusermedia. To take advantage of library (GIFEncoder) that let's me convert canvas frames into frames of a gif, I am first drawing the vide blob
to the canvas using ctx.drawImage then I am drawing the frames to the gif.
Is there any more efficient way to do this?
Code:
<!Doctype html>
<html>
<head>
<style type="text/css">
body {
}
</style>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="LZWEncoder.js"></script>
<script type="text/javascript" src="NeuQuant.js"></script>
<script type="text/javascript" src="GIFEncoder.js"></script>
<script type="text/javascript" src="b64.js"></script>
</head>
<body>
<title>RecorderGif</title>
<header>
<h1>RecordGif</h1>
</header>
<article>
<video id="video" width="320" height="200" style="display:none" autoplay=""></video>
<section>
<button id="btnStart">Start video</button>
<button id="btnStop">Stop video</button>
<button id="btnSave">Download</button>
</section>
<canvas id="canvas" width="320" height="240"></canvas>
</article>
<script type="text/javascript">//<![CDATA[
var encoder = new GIFEncoder();
encoder.setRepeat(0);
encoder.setDelay(100);
encoder.start();
window.onload = function() {
//Compatibility
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
var data = []
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
video = document.getElementById("video"),
btnStart = document.getElementById("btnStart"),
btnStop = document.getElementById("btnStop"),
btnSave = document.getElementById("btnSave")
videoObj = {
video: true,
};
btnStart.addEventListener("click", function() {
var localMediaStream;
if (navigator.getUserMedia) {
navigator.getUserMedia(videoObj, function(stream) {
video.src = (navigator.webkitGetUserMedia) ? window.webkitURL.createObjectURL(stream) : stream;
localMediaStream = stream;
var addFrame = setInterval(function() {
data.push(canvas.toDataURL('image/png'))
},100);
}, function(error) {
console.error("Video capture error: ", error.code);
});
btnStop.addEventListener("click", function() {
clearInterval(addFrame)
localMediaStream.stop();
});
btnSave.addEventListener("click", function() {
for (var i = 0; i < data.length; i++) {
var frame = new Image();
frame.src=data[i]
context.drawImage(frame,0,0)
encoder.addFrame(context);
};
encoder.finish();
var binary_gif = encoder.stream().getData() //notice this is different from the as3gif package!
var data_url = 'data:image/gif;base64,'+encode64(binary_gif);
var gif = window.open('about:blank','','width=320,height=240')
gif.document.location.href=data_url
});
setInterval(function() {context.drawImage(video, 0, 0, 320, 240)},100);
}
});
};
//]]>
</script>
</body>
</html>

As there is no native support for GIF encoding in the browsers you very much depend on JavaScript solutions such as the one you're using.
The only way to improve the encoding is to go through the source code and see if you can optimize parts of it such as utilizing typed arrays (if not), conform to engine specifics knowing what works well with the engine compilers (ie. things like using simulated types, full constructor, non-morphing objects etc.).
Another tip is to encode the GIF when the video is not playing (stepping it forward "manually" for each frame using currentPosition and the event for it) and with an off-screen video element. This may leave some more resources for the browser and the engine to run the script faster (depending partly on the system and its support for hardware decoding video - there will still be an overhead updating the video on-screen which may or may not affect the encoding process).
My 2.5 cents..

Related

Adding Quality Selector to plyr when using HLS Stream

I am using plyr as wrapper around HTML5 video tag and using Hls.js to stream my .m3u8 video .
I was going around a lot of issues on plyr to enable quality selectors and came arounf multiple PR's which had this question but was closed saying the implementation is merged, till i came around this PR which says it's still open, but there was a custom implementation in the Comments which assured that it works . I was trying that implementation locally in order to check if we can add a quality selector but seems like i am missing something/ or the implementation dosent work .
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HLS Demo</title>
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.10/plyr.css" />
<style>
body {
max-width: 1024px;
}
</style>
</head>
<body>
<video preload="none" id="player" autoplay controls crossorigin></video>
<script src="https://cdn.plyr.io/3.5.10/plyr.js"></script>
<script src="https://cdn.jsdelivr.net/hls.js/latest/hls.js"></script>
<script>
(function () {
var video = document.querySelector('#player');
var playerOptions= {
quality: {
default: '720',
options: ['720']
}
};
var player;
player = new Plyr(video,playerOptions);
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource('https://content.jwplatform.com/manifests/vM7nH0Kl.m3u8');
//hls.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED,function(event,data) {
// uncomment to see data here
// console.log('levels', hls.levels); we get data here but not able to see in settings .
playerOptions.quality = {
default: hls.levels[hls.levels.length - 1].height,
options: hls.levels.map((level) => level.height),
forced: true,
// Manage quality changes
onChange: (quality) => {
console.log('changes',quality);
hls.levels.forEach((level, levelIndex) => {
if (level.height === quality) {
hls.currentLevel = levelIndex;
}
});
}
};
});
}
// Start HLS load on play event
player.on('play', () => hls.startLoad());
// Handle HLS quality changes
player.on('qualitychange', () => {
console.log('changed');
if (player.currentTime !== 0) {
hls.startLoad();
}
});
})();
</script>
</body>
</html>
The above snippet works please run , but also if you uncomment the
line in HLS Manifest you will see we get data in levels and also pass
the data to player options but it dosent come up in settings.How can
we add a quality selector to plyr when using Hls stream .
I made a lengthy comment about this on github [1].
Working example: https://codepen.io/datlife/pen/dyGoEXo
The main idea to fix this is:
Configure Plyr options properly to allow the switching happen.
Let HLS perform the quality switching, not Plyr. Hence, we only need a single source tag in video tag.
<video>
<source
type="application/x-mpegURL"
<!-- contain all the stream -->
src="https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8">
</video>
[1] https://github.com/sampotts/plyr/issues/1741#issuecomment-640293554

OpenCV.JS Uncaught ReferenceError: cv is not defined

I am working on a computer vision project using OpneCV.JS and springboot using Thymeleaf as my HTML5 template engine. I have been following the OpenCJ.Js tutorial here. I am suppose to get two output, one that will display the VideoInput and the other one the canvas that will display the Canvaoutput where the face tracking will take place.
However, the Video display and its working as expected. However, the Canvas display did not show. When I inspect my code in the chrome browser, I realize that I am getting an Uncaught Reference error which says CV is not defined.
Can somebody assist to tell me if their is anything I am doing wrong in my Code.
Below is my code
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout" layout:decorate="layout">
<head>
<script async type="text/javascript" th:src="#{/opencv/opencv.js}"></script>
<title>Self-Service Portal</title>
</head>
<body>
<h2>Trying OpenCV Javascript Computer Vision</h2>
<p id="status">Loading with OpenCV.js...</p>
<video id="video" autoplay="true" play width="300" height="225"></video> <br/>
<canvas id="canvasOutput" autoplay="true" width="300" height="225"></canvas>
<!--div>
<div class="inputoutput">
<img id="imageSrc" alt="No Image" />
<div class="caption">ImageScr<input type="file" id="fileInput" name="file" /></div>
</div>
<div class="inputoutput">
<canvas id="canvasOutput" ></canvas>
<div class="caption">canvasOutput</div>
</div>
</div-->
<script type="text/javascript">
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
})
.then(function(stream) {
video.srcObject = stream;
video.play();
})
.catch(function(err) {
console.log("An error occured While accessing media! " + err);
});
let video = document.getElementById('video');
let src = new cv.Mat(video.height, video.width, cv.CV_8UC4);
let dst = new cv.Mat(video.height, video.width, cv.CV_8UC4);
let gray = new cv.Mat();
let cap = new cv.VideoCapture(video);
let faces = new cv.RectVector();
let classifier = new cv.CascadeClassifier();
//load pre-trained classifiers
classifier.load('haarcascade_frontalface_default.xml');
const FPS = 30;
function processVideo() {
try {
if (!streaming) {
// clean and stop.
src.delete();
dst.delete();
gray.delete();
faces.delete();
classifier.delete();
return;
}
let begin = Date.now();
// start processing.
cap.read(src);
src.copyTo(dst);
cv.cvtColor(dst, gray, cv.COLOR_RGBA2GRAY, 0);
// detect faces.
classifier.detectMultiScale(gray, faces, 1.1, 3, 0);
// draw faces.
for (let i = 0; i < faces.size(); ++i) {
let face = faces.get(i);
let point1 = new cv.Point(face.x, face.y);
let point2 = new cv.Point(face.x + face.width, face.y + face.height);
cv.rectangle(dst, point1, point2, [255, 0, 0, 255]);
}
cv.imshow('canvasOutput', dst);
// schedule the next one.
let delay = 1000 / FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
}
catch (err) {
utils.printError(err);
}
};
//schedule the first one.
setTimeout(processVideo, 0);
</script>
<!--script async src="/opencv/opencv.js" onload="onOpenCvReady;" type="text/javascript"></script-->
</body>
</html>
The cv is not defined error can be fixed by assigning cv to the window cv object like, let cv = window.cv
Turning off async would not be ideal because the OpenCV JS library is large and it would affect the time to initial load. Maybe assign a state variable that changes when it finishes loading and run a check on this variable and update the UI accordingly
the code you find in the examples on the opencv page usually focuses on the functionality of the library and sometimes they do not have all the necessary functions to work. The examples on the opencv page usually use a utils.js file that contains the additional functions to work and this is not so obvious.
Most likely your problem will be solved with the answer of this forum.
Additionally I have created a codesandbox that contains all the necessary functions for the video examples to work. Normally they will work for you if you replace the code under the comment
/ ** Your example code here * /
by your facial recognition code.
The async on
<script async type="text/javascript" th:src="#{/opencv/opencv.js}"></script>
...means it won't hold up parsing and processing of the following markup while waiting for the file to load; details. So your inline script later in the file can run before that file is loaded and its JavaScript is run.
Remove async and move that script tag to the bottom of the document, just before your inline script that uses what it defines.

Count number of faces using javascript

Is there a way that I can count the number of faces in a live camera.
For example I've three persons in front of a webcam and I'm having 2 pages say, Success.html and error .html and as part of the underlying way to judge the redirection to Success or error pages the condition would be, if only one face is detected, it should be sent to success, else, it should be sent to error .
I'm using tracking.js to detect the face in my web page and currently my code is as below.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>tracking.js - face with camera</title>
<link rel="stylesheet" href="assets/demo.css">
<script src="js/tracking-min.js"></script>
<script src="js/data/face-min.js"></script>
<script src="js/dat.gui.min.js"></script>
<script src="assets/stats.min.js"></script>
<style>
video, canvas {
margin-left: 230px;
margin-top: 120px;
position: absolute;
}
</style>
</head>
<body>
<div class="demo-title">
<p>
tracking.js -
get user's webcam and detect faces
</p>
</div>
<div class="demo-frame">
<div class="demo-container">
<video id="video" width="320" height="240" preload autoplay loop
muted></video>
<canvas id="canvas" width="320" height="240"></canvas>
</div>
</div>
<script>
window.onload = function() {
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var tracker = new tracking.ObjectTracker('face');
tracker.setInitialScale(4);
tracker.setStepSize(2);
tracker.setEdgesDensity(0.1);
tracking.track('#video', tracker, {
camera : true
});
tracker.on('track', function(event) {
context.clearRect(0, 0, canvas.width, canvas.height);
event.data
.forEach(function(rect) {
console.log('face detected');
});
});
</script>
</body>
</html>
Here in my console I'm able to print, face detected.
please let me know how can I detect multiples faces in this window.
Thanks
From the documentation:
myTracker.on('track', function(event) {
if (event.data.length === 0) {
// No targets were detected in this frame.
} else {
event.data.forEach(function(data) {
// Plots the detected targets here.
});
}
});
It seems to be event.data.length that gives you the number of tracked elements.
Fom the docs at https://trackingjs.com/docs.html#trackers
myTracker.on('track', function(event) {
if (event.data.length === 0) {
// No targets were detected in this frame.
} else {
event.data.forEach(function(data) {
// Plots the detected targets here.
});
}
});
Use event.data.length to count the amount of faces.
On your piece of code :
tracker.on('track', function(event) {
context.clearRect(0, 0, canvas.width, canvas.height);
// console log amount of elements found
console.log(event.data.length);
event.data.forEach(function(rect) {
console.log('face detected');
});
});

How to save a canvas image with a specific filename

I have a web page that can show a live stream and then be able to snap shot the live stream.
I need to have a button that can save the image in the canvas with a specific file name of my own (it can be the date for today).
Badly need it. Thanks to those who will respond! :)
Here's the code:
<!DOCTYPE html>
<html>
<head>
<title>Video Live Stream</title>
<link rel="stylesheet" type="text/css" href="demo'.css"/>
</head>
<body>
<center>
<style>
<p>
video { border: 20px solid #ccc; display: block; margin: 0 0 -5px 0; }
#canvas { margin-top: 20px; border: 1px solid #ccc; display: block; }
</p>
</style>
</br>
<h3>Live Stream with Snap Shot</h3>
<div align="center">
<video style="position:relative;top:-75px;" id="video" width="600" height="600" autoplay="">
</video>
</div>
<div align="center">
<button style="position:relative;top:-60px;" id="snap" class="sexyButton">Snap Photo</button>
<canvas style="position:relative;top:-60px;" class="input" id="canvas" width="640" height="480"></canvas>
</div>
Download!
<script>
// Put event listeners into place
window.addEventListener("DOMContentLoaded", function() {
// Grab elements, create settings, etc.
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
video = document.getElementById("video"),
videoObj = { "video": true },
errBack = function(error) {
console.log("Video capture error: ", error.code);
};
// Put video listeners into place
if(navigator.getUserMedia) { // Standard
navigator.getUserMedia(videoObj, function(stream) {
video.src = stream;
video.play();
}, errBack);
} else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
navigator.webkitGetUserMedia(videoObj, function(stream){
video.src = window.webkitURL.createObjectURL(stream);
video.play();
}, errBack);
} else if(navigator.mozGetUserMedia) { // WebKit-prefixed
navigator.mozGetUserMedia(videoObj, function(stream){
video.src = window.URL.createObjectURL(stream);
video.play();
}, errBack);
}
// Trigger photo take
document.getElementById("snap").addEventListener("click", function() {
context.drawImage(video, 0, 0, 640, 480);
});
}, false);
</script>
</center>
</body>
</html>
Automatic file Download
You can not guarantee a filename but you can suggest a filename. The client may override the filename as it is just a download request and subject to the clients download policies. Event if the user allows the file to save with the name given the browser may still change the name if the file exist in the download directory by appending to the filename (or other stuff depending on the browser).
Here is a simple download image function. It will attempt to save (via download) to the client's file system the image as a PNG with the given filename. Will take an image or canvas image.
Update
As pointed out by markE the download attribute that I use to set the filename is not supported on all browsers. So only browser that support the attribute will let you set the download name.
Further the function given will also not download if the MouseEvent object is not supported. I have added a legacy method fireEvent for older browsers namely IE otherwise the function will not download.
function saveAsPNG(image, filename){ // No IE <11 support. Chrome URL bug for large images may crash
var anchorElement, event, blob;
function image2Canvas(image){ // converts an image to canvas
function createCanvas(width, height){ // creates a canvas of width height
var can = document.createElement("canvas");
can.width = width;
can.height = height;
return can;
};
var newImage = createCanvas(img.width, img.height); // create new image
newImage.ctx = newImage.getContext("2d"); // get image context
newImage.ctx.drawImage(image, 0, 0); // draw the image onto the canvas
return newImage; // return the new image
}
if(image.toDataURL === undefined){ // does the image have the toDataURL function
image = image2Canvas(image); // No then convert to canvas
}
// if msToBlob and msSaveBlob then use them to save. IE >= 10
if(image.msToBlob !== undefined && navigator.msSaveBlob !== undefined){
blob = image.msToBlob();
navigator.msSaveBlob(blob, filename + ".png");
return;
}
anchorElement = document.createElement('a'); // Create a download link
anchorElement.href = image.toDataURL(); // attach the image data URL
// check for download attribute
if ( anchorElement.download !== undefined ) {
anchorElement.download = filename + ".png"; // set the download filename
if (typeof MouseEvent === "function") { // does the browser support the object MouseEvent
event = new MouseEvent( // yes create a new mouse click event
"click", {
view : window,
bubbles : true,
cancelable : true,
ctrlKey : false,
altKey : false,
shiftKey : false,
metaKey : false,
button : 0,
buttons : 1,
}
);
anchorElement.dispatchEvent(event); // simulate a click on the download link.
} else
if (anchorElement.fireEvent) { // if no MouseEvent object try fireEvent
anchorElement.fireEvent("onclick");
}
}
}
To use the function
saveAsPNG(canvas,"MyTest"); // Will attempt to save the canvas as "MyTest.png"
This does not guarantee that the file will be saved or that the filename will be what you want. If you want to guarantee a filename you must then save the file inside a zip file. This is a lot of work and still does not guarantee that the file will be saved. Security dictates that the client must have the right to accept or reject any download request. There is no way to side step this.
I just found a way to save the canvas image with the filename of your own. You can find it at
http://eligrey.com/demos/FileSaver.js/
edit the canvas part:
<section id="image-demo">
<form id="canvas-options">
<label>Filename: <input type="text" class="filename" id="canvas-filename" placeholder="filename"/>.png</label>
<input type="submit" value="Save"/>
<input type="button" id="canvas-clear" value="Clear"/>
</form>
and insert the following scripts:
<script type="application/ecmascript" async="" src="Blob.js"/>
<script type="application/ecmascript" async="" src="canvas-toBlob.js"/>
<script type="application/ecmascript" async="" src="FileSaver.js"/>
<script type="application/ecmascript">
It worked!

Create animated gif from array of png data

I am working on a project where I'm using the getUserMedia to create a array of png image data from the webcam.
I am now trying to convert this array into one animated gif.
Thanks in advance,
PS: I would like to use only pure javascript but if I need a external library I will use one
demo Code:
<!Doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>getUserMedia API - jsFiddle demo by Vulpus</title>
<script type="text/javascript" src="LZWEncoder.js"></script>
<script type="text/javascript" src="NeuQuant.js"></script>
<script type="text/javascript" src="GIFEncoder.js"></script>
<script type="text/javascript" src="b64.js"></script>
</head>
<body>
<title>RecorderGif</title>
<header>
<h1>getUserMedia</h1>
</header>
<article>
<video id="video" width="320" height="200" style="display:none" autoplay=""></video>
<section>
<button id="btnStart">Start video</button>
<button id="btnStop">Stop video</button>
<button id="btnSave">Download</button>
</section>
<canvas id="canvas" width="320" height="240"></canvas>
</article>
<script type="text/javascript">//<![CDATA[
var encoder = new GIFEncoder();
encoder.setRepeat(0);
encoder.setDelay(250);
encoder.start();
window.onload = function() {
//Compatibility
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
var data = []
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
video = document.getElementById("video"),
btnStart = document.getElementById("btnStart"),
btnStop = document.getElementById("btnStop"),
btnSave = document.getElementById("btnSave")
videoObj = {
video: true,
};
btnStart.addEventListener("click", function() {
var localMediaStream;
if (navigator.getUserMedia) {
navigator.getUserMedia(videoObj, function(stream) {
video.src = (navigator.webkitGetUserMedia) ? window.webkitURL.createObjectURL(stream) : stream;
localMediaStream = stream;
var addFrame = setInterval(function() {
data.push(canvas.toDataURL('image/png'))
},100);
}, function(error) {
console.error("Video capture error: ", error.code);
});
btnStop.addEventListener("click", function() {
localMediaStream.stop();
clearInterval(addFrame)
});
btnSave.addEventListener("click", function() {
for (var i = 0; i < data.length; i++) {
var frame = new Image();
frame.src=data[i]
context.drawImage(frame,0,0)
encoder.addFrame(context);
setTimeout(function(){},100)
};
encoder.finish();
var binary_gif = encoder.stream().getData() //notice this is different from the as3gif package!
var data_url = 'data:image/gif;base64,'+encode64(binary_gif);
window.location.href=data_url;
});
setInterval(function() {context.drawImage(video, 0, 0, 320, 240)},100);
}
});
};
//]]>
</script>
</body>
</html>
You cannot create GIFs natively using canvas but you can use a libray called JSGif to do this:
https://github.com/antimatter15/jsgif
From the read me file:
Now we need to init the GIFEncoder.
var encoder = new GIFEncoder();
If you are making an animated gif, you need to add the following
encoder.setRepeat(0); //0 -> loop forever
//1+ -> loop n times then stop
encoder.setDelay(500); //go to next frame every n milliseconds
Now, you need to tell the magical thing that you're gonna start inserting frames (even if it's only one).
encoder.start();
And for the part that took the longest to port: adding a real frame.
encoder.addFrame(context);
For more details check out its documentation.

Categories