Initial commit
This commit is contained in:
20
LICENSE
Normal file
20
LICENSE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Utkarsh Verma
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
132
index.js
Normal file
132
index.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
const videoFrames = async options => {
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
let error = false;
|
||||||
|
let frames = [];
|
||||||
|
let interval;
|
||||||
|
let seekResolve;
|
||||||
|
let currentTime = 0;
|
||||||
|
let extractOffsets = false;
|
||||||
|
|
||||||
|
const isNumber = n => {
|
||||||
|
return +n + '' === n + '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTimestamp = timestamp => {
|
||||||
|
return isNumber(timestamp) && +timestamp >= 0 && +timestamp <= video.duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fallbackToDefault = (property, defaultValue) => {
|
||||||
|
options[property] = options.hasOwnProperty(property) ? options[property] : defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Buffer Video Element
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.src = options.url;
|
||||||
|
video.crossOrigin = 'anonymous';
|
||||||
|
video.onseeked = async () => {
|
||||||
|
if(seekResolve)
|
||||||
|
seekResolve();
|
||||||
|
};
|
||||||
|
video.onerror = () => {
|
||||||
|
error = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
while((video.duration === Infinity || isNaN(video.duration)) && video.readyState < 2 && !error) {
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
video.currentTime = 10000000 * Math.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set options to default values if not set
|
||||||
|
fallbackToDefault('format', 'image/png');
|
||||||
|
fallbackToDefault('offsets', []);
|
||||||
|
fallbackToDefault('startTime', 0);
|
||||||
|
fallbackToDefault('endTime', video.duration);
|
||||||
|
fallbackToDefault('count', 1);
|
||||||
|
|
||||||
|
// Filter out invalid offsets
|
||||||
|
if(options.offsets.constructor !== Array)
|
||||||
|
options.offsets = [];
|
||||||
|
else
|
||||||
|
options.offsets = options.offsets.filter(offset => {
|
||||||
|
return isTimestamp(offset);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(options.offsets.length !== 0)
|
||||||
|
extractOffsets = true;
|
||||||
|
|
||||||
|
// Check if start and end times are valid
|
||||||
|
if(!isTimestamp(options.startTime))
|
||||||
|
options.startTime = 0;
|
||||||
|
|
||||||
|
if(!isTimestamp(options.endTime))
|
||||||
|
options.endTime = video.duration;
|
||||||
|
|
||||||
|
if(options.startTime >= options.endTime) {
|
||||||
|
options.startTime = options.endTime;
|
||||||
|
options.count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert count value to a positive integer (floor() or 0 if string)
|
||||||
|
options.count = Math.abs(~~options.count);
|
||||||
|
|
||||||
|
// Starting at startTime + interval and ending at endTime - interval
|
||||||
|
interval = (options.endTime - options.startTime) / (options.count + 1);
|
||||||
|
|
||||||
|
// Set Width and Height
|
||||||
|
let isWidthSet = options.hasOwnProperty('width');
|
||||||
|
let isHeightSet = options.hasOwnProperty('height');
|
||||||
|
let videoDimensionRatio = video.videoWidth / video.videoHeight;
|
||||||
|
|
||||||
|
// Reset Width and Height if not valid
|
||||||
|
if(isWidthSet && !isNumber(options.width))
|
||||||
|
isWidthSet = false;
|
||||||
|
|
||||||
|
if(isHeightSet && !isNumber(options.height))
|
||||||
|
isHeightSet = false;
|
||||||
|
|
||||||
|
if(!isWidthSet && !isHeightSet) {
|
||||||
|
// Both Width and Height not set
|
||||||
|
options.width = 128;
|
||||||
|
options.height = options.width / videoDimensionRatio;
|
||||||
|
|
||||||
|
} else if(isWidthSet && !isHeightSet) {
|
||||||
|
// Width set but Height not set
|
||||||
|
options.height = options.width / videoDimensionRatio;
|
||||||
|
|
||||||
|
} else if(!isWidthSet && isHeightSet) {
|
||||||
|
// Height set but Width not set
|
||||||
|
options.width = options.height * videoDimensionRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float values
|
||||||
|
options.width = +options.width;
|
||||||
|
options.height = +options.height;
|
||||||
|
|
||||||
|
// Buffer Canvas Element
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
canvas.width = options.width;
|
||||||
|
canvas.height = options.height;
|
||||||
|
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
|
||||||
|
if(error)
|
||||||
|
resolve([]);
|
||||||
|
|
||||||
|
while(
|
||||||
|
(extractOffsets && index < options.offsets.length) ||
|
||||||
|
(!extractOffsets && index < options.count)
|
||||||
|
) {
|
||||||
|
video.currentTime = extractOffsets ? options.offsets[index] : options.startTime + (index + 1) * interval;
|
||||||
|
await new Promise(r => seekResolve = r);
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
frames.push(canvas.toDataURL(options.format));
|
||||||
|
index ++;
|
||||||
|
}
|
||||||
|
resolve(frames);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { videoFrames };
|
||||||
10
package.json
Normal file
10
package.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "video-frames",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": false,
|
||||||
|
"description": "Client side video frames extraction as base64 encoded images",
|
||||||
|
"author": "Utkarsh Verma",
|
||||||
|
"keywords": ["video", "frame", "client-side", "base64"],
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user