How to save the frames of video on Local Disk? - javascript

I am extracting frames from video , it is extracting and displaying all the frames on canvas means on the web browser . I am trying to save that frame on disk using local-storage .set Item but not working , Kindly Help , I want to save that frames on my Local disk . How to do this , My code is shown below :
<!DOCTYPE html>
<html>
<body>
<video width="400" controls>
<source src="180419_Boxing_17_13.mp4" type="video/mp4">
Your browser does not support HTML5 video.
</video>
<p>These are the frames' images generated by getVideoImage():</p>
<ol id="olFrames"></ol>
<script type="text/JavaScript">
function getVideoImage(path, secs, callback) {
var me = this, video = document.createElement('video');
video.onloadedmetadata = function() {
if ('function' === typeof secs) {
secs = secs(this.duration);
}
this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration);
};
video.onseeked = function(e) {
var canvas = document.createElement('canvas');
canvas.height = video.videoHeight;
canvas.width = video.videoWidth;
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, 500, 500);
var img = new Image();
img.src = canvas.toDataURL();
callback.call(me, img, this.currentTime, e);
var data = ctx.getImageData(x, y, img.width, img.height).data;
localStorage.setItem('image', data);
};
video.onerror = function(e) {
callback.call(me, undefined, undefined, e);
};
video.src = path;
}
function showImageAt(secs) {
var duration;
getVideoImage(
'180419_Boxing_17_13.mp4',
function(totalTime) {
duration = totalTime;
return secs;
},
function(img, secs, event) {
if (event.type == 'seeked') {
var li = document.createElement('li');
li.innerHTML += '<b>Frame at second ' + secs + ':</b><br />';
li.appendChild(img);
document.getElementById('olFrames').appendChild(li);
if (duration >= ++secs) {
showImageAt(secs);
};
}
}
);
}
showImageAt(0);
</script>
</body>
</html>

Related

how to make a video preview with canvas?

this is my first question here!
im trying to make a website that show videos that i make and i want my viewers can see a preview of video when they hover mouse on the a element or element .
i just do it but i have some problems and questions. lemme show you code example first and then i ask my questions.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="thumbs" style="width:216px;height:468px;border: 5px solid blue;background-color:red;"><canvas onmouseover="getid1(this);" onmouseout="getid2(this);" id="canvas1"></canvas></div>
<script>
var thumbsList = [];
var delay = 100;
var i = 0;
var video = document.createElement("video");
var a = 0;
var m = document.getElementById("canvas1");
var mtx = m.getContext("2d");
var generate = true;
var animstop = false;
var vidFrames = 64;
m.width = 216;
m.height = 468;
video.preload = "auto";
video.src = "aaaa.mp4";
function stranim(bb){
if(generate){
animstop = false;
video.currentTime = i;
generateVid();
generate = false;
}else{
animstop = false;
startAnim();
}
}
function stpanim(bb) {
clearTimeout();
animstop = true;
mtx.drawImage(thumbsList[0], 0, 0);
}
// if i replace this section with generateVid() the code is working
/*video.addEventListener('seeked', function() {
var d = (video.duration / vidFrames) * 2;
generateThumbnail();
i += video.duration / vidFrames;
if (i <= video.duration) {
video.currentTime = i;
generateVid()
if(d == i){
startAnim();
}
}
});*/
function generateVid() {
var d = (video.duration / vidFrames) * 2;
generateThumbnail();
i += video.duration / vidFrames;
if (i <= video.duration) {
video.currentTime = i;
generateVid();
if(d == i){
startAnim();
}
}
}
function generateFirstThumbnail() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 216;
c.height = 468;
ctx.drawImage(video, 0, 0, 216, 468);
thumbsList.push(c);
thumbs.appendChild(m);
mtx.drawImage(thumbsList[0], 0, 0);
i += video.duration / vidFrames;
}
function generateThumbnail() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 216;
c.height = 468;
ctx.drawImage(video, 0, 0, 216, 468);
thumbsList.push(c);
}
function startAnim() {
var currentFrame = 0;
function anim() {
if(currentFrame != (vidFrames - 1) && animstop == false){
currentFrame = (currentFrame + 1) % thumbsList.length;
mtx.drawImage(thumbsList[currentFrame], 0, 0);
setTimeout(anim, delay);
}
}
anim();
}
function getid1(obj) {
stranim(obj.id)
}
function getid2(obj) {
stpanim(obj.id)
}
window.onload = function(){
generateFirstThumbnail();
};
</script>
</body>
</html>
the questions are :
1- why my generatevid() function is not working? this function is just like that eventlistener.
2- i want to add this code on a class to use it on multiple videos but idk how and i think this cant be done with js classes.( i search so many sites for it)
3-i can use 2d arrays for saving my video previews but is it dont make my site lower on load speed or it dont fill the ram of user?
4-there is any way that i make this code better and easyer?(i dont want to use jquery for no reason).
For a pure js approach you can check this not mine btw: https://codepen.io/tepexic/pen/BazYgJe
html:
<input type="file" accept="video/*" id="input-tag"/>
<hr>
<video controls id="video-tag">
<source id="video-source" src="splashVideo">
Your browser does not support the video tag.
</video>
JS
const videoSrc = document.querySelector("#video-source");
const videoTag = document.querySelector("#video-tag");
const inputTag = document.querySelector("#input-tag");
inputTag.addEventListener('change', readVideo)
function readVideo(event) {
console.log(event.target.files)
if (event.target.files && event.target.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
console.log('loaded')
videoSrc.src = e.target.result
videoTag.load()
}.bind(this)
reader.readAsDataURL(event.target.files[0]);
}
}
But for other approach i would probably use http://ffmpeg.org/ library to generate a preview video (e.g 10 seconds video) and https://videojs.com library for video player so you have a control when user hover the player.
I just did some test on your code, your video is getting seeked only once. No event is raised after that, which means your thumbnail will remain same.
Here is how I was able to fix it
function generateVid() {
var d = (video.duration / vidFrames) * 2;
generateThumbnail();
i += video.duration / vidFrames;
if (i <= video.duration) {
video.currentTime = i;
console.log("starting seeking");
if(d == i){
startAnim();
}
}
}
video.onseeked = (event) => {
if (animstop == false)
{
setTimeout(generateVid, delay);
}
console.log("seeked");
};

Playing multiple fractions of HTML5 videos at a time

I am trying to write a function that will take an id of a div and play a given video from for example 2/10th of the way through to 3/10th of the way through and return an event so I can do something else.
I am not to clever with events as yet, and the video duration is not working out for me, any help appreciated. I have jQuery installed.
Some improvement since last time...
Updated, just need the listener to work next.
var finished_event = new Event('finished');
$(document).ready(function(){
class Player {
constructor(pos, vid, part, parts){
this.pos = pos;
this.vid = vid;
this.part = part;
this.parts = parts;
}
start(){
var it = this;
var html = '<video class="video-fit" controls>';
html += '<source src="';
html += it.vid;
html += '" type="video/mp4">';
html += '</video>';
$(it.pos).html(html);
var video = $(it.pos + ' video')[0];
video.addEventListener('loadeddata', function(){
var dur = video.duration;
var fra = dur / it.parts;
var beg = it.part * fra;
var end = it.part * (fra + 1);
video.currentTime = beg;
video.play();
video.addEventListener('timeupdate', function() {
if (video.currentTime >= end) {
video.pause();
this.dispatchEvent(finished_event);
}
}, false);
}, false);
return it;
}
}
$('body').click(function(){
let player_1 = new Player('#pos-1', 'videos/576p.mp4', 5, 10);
player_1.start();
let player_2 = new Player('#pos-2', 'videos/576p.mp4', 5, 10);
player_2.start();
let player_3 = new Player('#pos-3', 'videos/576p.mp4', 5, 10);
player_3.start();
let player_4 = new Player('#pos-4', 'videos/576p.mp4', 5, 10);
player_4.start();
//.addEventListener('finished', function(){ console.log('Finished'); }, false)
});
});
The timeupdate event fires a lot, so it could fire multiple times within the this.currentTime >= end check.
Not changing your code too much, you would want to set a playing state/var and on canplay event change timestamp and then play, this would, in turn, trigger a seeked event which then you can set the video as playing, then on timeupdate, check your timing plus is the video playing.. and for the last video you would need to check ended event as it won't enter into your check this.currentTime >= end as end is +1 second.
Then to separate the event functions from the Player class, id would use an array and watch it for changes, then by pushing a function to the array/stack you can then do a check within the function each time its invoked if the stack matches the video length, suppose you could do it other ways.
(function () {
let video =
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/twitter_cat.mp4';
let videos = [
{ id: '#pos-1', video, part: 1, parts: 10 },
{ id: '#pos-2', video, part: 2, parts: 10 },
{ id: '#pos-3', video, part: 3, parts: 10 },
{ id: '#pos-4', video, part: 4, parts: 10 },
];
const playqueue = [];
Object.defineProperty(playqueue, 'push', {
value: function (...args) {
var arr = Array.prototype.push.apply(this, args)
if (typeof args[0] === 'function') args[0]();
return arr;
}
});
function finished_event() {
if (playqueue.length === videos.length) {
console.log('Do something, all videos have stopped playing');
}
}
function video_finished_event(player) {
console.log(
'Do something, ' + JSON.stringify(player) + ' has stopped playing'
);
playqueue.push(finished_event);
}
class Player {
constructor(pos, vid, part, parts) {
this.pos = pos;
this.vid = vid;
this.part = part;
this.parts = parts;
}
start() {
var it = this;
var html = '<video class="video-fit" data-vid="' + it.pos + '" controls>';
html += '<source src="';
html += it.vid;
html += '" type="video/mp4">';
html += '</video>';
$(it.pos).html(html);
var video = $('[data-vid="' + it.pos + '"]')[0];
video.addEventListener(
'loadeddata',
function () {
var fra = video.duration / it.parts;
var beg = it.part * fra;
var end = it.part * (fra + 1);
video.addEventListener('canplay', function () {
if (!this.playing) {
this.currentTime = beg;
this.play();
}
});
video.addEventListener('seeked', function () {
this.playing = true;
});
video.addEventListener('timeupdate', function () {
if (this.playing && this.currentTime >= end) {
this.pause();
this.playing = false;
video_finished_event(it);
}
});
video.addEventListener('ended', function () {
if (this.playing) {
this.pause();
this.playing = false;
video_finished_event(it);
}
})
},
false
);
return it;
}
}
videos.forEach(video => {
video.player = new Player(video.id, video.video, video.part, video.parts);
video.player.start();
});
})();
.videos div {
display: inline-block;
width: calc(25vw - 15px);
height: 100%;
}
.videos div video {
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="videos">
<div id="pos-1"></div>
<div id="pos-2"></div>
<div id="pos-3"></div>
<div id="pos-4"></div>
</div>
I suggest you to put an unique id on video, here i choose vpos-1/2/3/4/...
and i display video from beg to end:
var html = '<video ' + 'id="v' + it.pos + '" class="video-fit">';
html += '<source src="';
html += it.vid;
html += '" type="video/mp4">';
html += '</video>';
$(it.pos).html(html);
var video = document.getElementById('v' + it.pos);
$(video).bind('timeupdate', function() {
if(video.currentTime > end){
video.pause();
}
});
$(video).on('play', function() {
video.playbackRate = 1.0;
//calcul the value of beg
video.currentTime = beg;
video.play();//suppress if you have autoplay
});
$(video).bind('ended', function() {
// nothing yet
});
if you want to autoplay and/or display controls i suggest you to add
controls="controls" autoplay
to tag video
if you want to remove video: just do src=''
It stops the video and it saves the memory

Fabric JS: Performance of very large images (20mb+)

I'm using Fabric JS to manipulate very large images (20mb+). I have found that Fabric is considerably slower at handling large images in a canvas as compared to using the standard Canvas API.
The below code snippet has two input buttons, one for adding an image to canvas using standard Canvas API and the other using Fabric JS. Each method will also convert the canvas to a data url using toDataUrl(). Each method also logs three times: start time, time when img.onload function completes, and when toDataUrl() completes.
Here is a table comparing import+export times that I tested for varying image sizes:
import times for 500kb to 50mb photos
Here is a graph displaying the performance of Fabric import+export times vs Canvas API: graph
Questions:
Why is Fabric performance so much slower than Canvas API at importing+exporting large images on a canvas?
Is there a way to increase Fabric performance when using large images?
Are my test cases accurately representing Fabric performance?
// Standard Import
function handleFiles(e) {
var t0 = performance.now();
console.log('Standard Import')
console.log('Start Time: ', Math.round(t0/1000))
var promise = new Promise(function(resolve) {
var URL = window.webkitURL || window.URL;
var ctx = document.getElementById('canvas').getContext('2d');
canvas = document.getElementById('canvas');
var url = URL.createObjectURL(e.target.files[0]);
var img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve("done")
};
img.src = url;
});
promise.then(function(result) {
var t1 = performance.now()
console.log('Done img.onload() Elapsed Time: ', Math.round(t1 - t0)/1000);
var dataURL = canvas.toDataURL('image/png')
return
}).then(function(result){
t2 = performance.now()
console.log('Done canvas.ToDataURL() Elapsed Time: ', Math.round(t2 - t0)/1000)
return
});
}
// Fabric Import
function handleFilesFabric(e) {
var t0 = performance.now();
console.log('Fabric Import')
console.log('Start Time: ', Math.round(t0/1000))
var promise = new Promise(function(resolve) {
var canvas = new fabric.Canvas('canvas');
var reader = new FileReader();
reader.onload = function (event){
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function () {
var image = new fabric.Image(imgObj);
canvas.setHeight(imgObj.height);
canvas.setWidth(imgObj.width);
canvas.add(image);
canvas.renderAll();
canvas.forEachObject(function(object){
object.selectable = false;
});
resolve("done")
}
}
reader.readAsDataURL(e.target.files[0]);
});
promise.then(function(result) {
var t1 = performance.now()
console.log('Done img.onload() Elapsed Time: ', Math.round(t1 - t0)/1000);
var dataURL = canvas.toDataURL('image/png')
return
}).then(function(result){
t2 = performance.now()
console.log('Done canvas.ToDataURL() Elapsed Time: ', Math.round(t2 - t0)/1000)
return
});
}
window.onload = function() {
// Standard Import
var input = document.getElementById('input');
input.addEventListener('change', handleFiles, false);
// Fabric Import
var input2 = document.getElementById('input2');
input2.addEventListener('change', handleFilesFabric, false);
};
canvas {
border: 2px solid;
}
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Upload & Display Image w/ Canvas</title>
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.min.js"></script>
</head>
<body>
<h1> Upload & Display Image</h1>
<canvas width="400" height="400" id="canvas"></canvas>
<br>
<b>Standard Add Image</b><br>
<input type="file" id="input"/>
<br>
<b>Fabric Add Image</b><br>
<input type="file" id="input2"/>
<script src="js/index.js"></script>
</body>
</html>
I refactored the code a bit to be more fair:
Same way of loading the image and both event run after the other on 2 different canvases.
Also remove additional fabricJS functionality that for this test do not make sense. I think that for bigger image the differnce should be lower now, can you try in the snippet with a big image?
By the way you cannot compare a URL.createObjectUrl from a file with a file reader on a dataUrl. Is just unfair.
createObjectUrl create a reference in memory to the file you uploaded.
ReadAsDataUrl read the file, encode in base64, create a string object, then the browser has to read that string again, decode from base64.
The difference could also be in the fact fabricJS paint the image with drawImage and 9 args, while you used the 3 args version.
// Standard Import
fabric.Object.prototype.objectCaching = false;
function handleFiles(e) {
var t0 = performance.now();
var promise = new Promise(function(resolve) {
var URL = window.webkitURL || window.URL;
var ctx = document.getElementById('canvas').getContext('2d');
canvas = document.getElementById('canvas');
var url = URL.createObjectURL(e.target.files[0]);
var img = new Image();
img.onload = function() {
console.log('Standard Import')
console.log('Start Time: ', Math.round(t0/1000))
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve("done")
};
img.src = url;
});
promise.then(function(result) {
var t1 = performance.now()
console.log('Done img.onload() Elapsed Time: ', Math.round(t1 - t0)/1000);
var dataURL = canvas.toDataURL('image/png')
return
}).then(function(result){
t2 = performance.now()
console.log('Done canvas.ToDataURL() Elapsed Time: ', Math.round(t2 - t0)/1000)
return
});
}
// Fabric Import
function handleFilesFabric(e) {
var t0 = performance.now();
var canvas = new fabric.StaticCanvas('canvas2', {enableRetinaScaling: false, renderOnAddRemove: false });
var promise = new Promise(function(resolve) {
var reader = new FileReader();
var URL = window.webkitURL || window.URL;
var url = URL.createObjectURL(e.target.files[0]);
var imgObj = new Image();
imgObj.onload = function () {
console.log('Fabric Import')
console.log('Start Time: ', Math.round(t0/1000))
var image = new fabric.Image(imgObj);
canvas.setDimensions({ width: imgObj.width, height: imgObj.height});
canvas.add(image);
resolve("done")
}
imgObj.src = url;
});
promise.then(function(result) {
var t1 = performance.now()
console.log('Done img.onload() Elapsed Time: ', Math.round(t1 - t0)/1000);
canvas.renderAll();
var dataURL = canvas.lowerCanvasEl.toDataURL('image/png')
return
}).then(function(result){
t2 = performance.now()
console.log('Done canvas.ToDataURL() Elapsed Time: ', Math.round(t2 - t0)/1000)
return
});
}
window.onload = function() {
// Standard Import
var input = document.getElementById('input');
input.addEventListener('change', handleFiles, false);
input.addEventListener('change', handleFilesFabric, false);
};
canvas {
border: 2px solid;
}
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Upload & Display Image w/ Canvas</title>
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.min.js"></script>
</head>
<body>
<h1> Upload & Display Image</h1>
<canvas width="400" height="400" id="canvas"></canvas>
<canvas width="400" height="400" id="canvas2"></canvas>
<br>
<b>Standard Add Image</b><br>
<input type="file" id="input"/>
<br>
</body>
</html>

How can we mix canvas stream with audio stream using mediaRecorder [duplicate]

This question already has answers here:
MediaStream Capture Canvas and Audio Simultaneously
(2 answers)
Closed 6 years ago.
I have a canvas stream using canvas.captureStream(). I have another video stream from webrtc video call. Now i want to mix canvas stream with audio tracks of the video stream.How can i do that?
Use the MediaStream constructor available in Firefox and Chrome 56, to combine tracks into a new stream:
let stream = new MediaStream([videoTrack, audioTrack]);
The following works for me in Firefox (Use https fiddle in Chrome, though it errors on recording):
navigator.mediaDevices.getUserMedia({audio: true})
.then(stream => record(new MediaStream([stream.getTracks()[0],
whiteNoise().getTracks()[0]]), 5000)
.then(recording => {
stop(stream);
video.src = link.href = URL.createObjectURL(new Blob(recording));
link.download = "recording.webm";
link.innerHTML = "Download recording";
log("Playing "+ recording[0].type +" recording:");
})
.catch(log))
.catch(log);
var whiteNoise = () => {
let ctx = canvas.getContext('2d');
ctx.fillRect(0, 0, canvas.width, canvas.height);
let p = ctx.getImageData(0, 0, canvas.width, canvas.height);
requestAnimationFrame(function draw(){
for (var i = 0; i < p.data.length; i++) {
p.data[i++] = p.data[i++] = p.data[i++] = Math.random() * 255;
}
ctx.putImageData(p, 0, 0);
requestAnimationFrame(draw);
});
return canvas.captureStream(60);
}
var record = (stream, ms) => {
var rec = new MediaRecorder(stream), data = [];
rec.ondataavailable = e => data.push(e.data);
rec.start();
log(rec.state + " for "+ (ms / 1000) +" seconds...");
var stopped = new Promise((y, n) =>
(rec.onstop = y, rec.onerror = e => n(e.error || e.name)));
return Promise.all([stopped, wait(ms).then(_ => rec.stop())]).then(_ => data);
};
var stop = stream => stream.getTracks().forEach(track => track.stop());
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var log = msg => div.innerHTML += "<br>" + msg;
<div id="div"></div><br>
<canvas id="canvas" width="160" height="120" hidden></canvas>
<video id="video" width="160" height="120" autoplay></video>
<a id="link"></a>

canvas and img working in chrome but not firefox

I have searched a few results regarding this issue but none of them works for me, so I am posting here to get some helps. Basically my issue is when I clicked on the generate button, I want the image from canvas to be displayed in a img element. However, the image will show up in chrome but not firefox! Below is my coding...
<body onload="onLoad();">
<canvas id="aimage">Your browser does not support the canvas tag.</canvas>
<input type="button" value="Generate" onclick="genImage();" />
<img id="cImg" name="cImg" src="${param.src}" />
...
</body>
And the javascript...
var tcanvas;
var scanvas;
var tcontext;
var imageURL;
function onLoad() {
tcanvas = document.getElementById("aimage");
tcontext = tcanvas.getContext("2d");
scanvas = document.getElementById("cImg");
imageURL = "${param.src}";
update();
}
function update() {
var image = new Image();
image.onload = function () {
if (image.width != tcanvas.width)
tcanvas.width = image.width;
if (image.height != tcanvas.height)
tcanvas.height = image.height;
tcontext.clearRect(0, 0, tcanvas.width, tcanvas.height);
tcontext.drawImage(image, 0, 0, tcanvas.width, tcanvas.height);
}
image.crossOrigin = 'anon';
image.src = imageURL;
}
function genImage() {
var url = tcanvas.toDataURL("image/jpeg");
scanvas.crossOrigin = 'anon';
scanvas.src = url;
if(scanvas.width > 1000){
scanvas.width = 1000;
}
if(scanvas.height > 1000){
scanvas.height = 1000;
}
}
try this:
var scanvas, tcontext, tcanvas;
function genImage() {
var url = tcanvas.toDataURL("image/jpeg");
scanvas.src = url;
if (scanvas.width > 1000) {
scanvas.width = 1000;
}
if (scanvas.height > 1000) {
scanvas.height = 1000;
}
}
window.onload = function () {
tcanvas = document.getElementById("aimage");
tcontext = tcanvas.getContext("2d");
scanvas = document.getElementById('cImg');
var img = new Image();
img.onload = function () {
tcontext.drawImage(img, 0, 0, img.width, img.height)
};
img.src = "yourImage.jpg";
}

Categories

Resources