Files
video-frames/index.js

145 lines
4.5 KiB
JavaScript
Raw Normal View History

2022-04-29 10:04:41 +05:30
module.exports = async options => {
2022-04-29 11:15:53 +05:30
let index = 0
let error = false
let seekResolve
let extractOffsets = false
const frames = []
const isNumber = n => {
return +n + '' === n + ''
}
const isTimestamp = timestamp => {
2022-05-05 12:36:03 +05:30
return isNumber(timestamp) && +timestamp >= 0 && +timestamp <= video.duration
2022-04-29 11:15:53 +05:30
}
2022-04-30 12:38:59 +05:30
const isPrototypeOf = (constructor, value) => {
return (value instanceof constructor)
}
2022-04-29 11:15:53 +05:30
const fallbackToDefault = (property, defaultValue) => {
2022-05-05 12:36:03 +05:30
options[property] = hasOwnProperty.call(options, property) ? options[property] : defaultValue
2022-04-29 11:15:53 +05:30
}
2022-04-29 16:50:08 +05:30
const hasOwnProperty = Object.prototype.hasOwnProperty
2022-04-29 11:15:53 +05:30
// Buffer Video Element
2022-05-05 12:36:03 +05:30
const video = document.createElement('video')
2022-04-29 11:15:53 +05:30
video.src = options.url
video.crossOrigin = 'anonymous'
video.onseeked = async () => {
if (seekResolve) { seekResolve() }
}
video.onerror = () => {
error = true
}
2022-05-05 12:36:03 +05:30
while ((video.duration === Infinity || isNaN(video.duration)) && video.readyState < 2) {
2022-04-29 11:15:53 +05:30
await new Promise(resolve => setTimeout(resolve, 100))
2022-05-05 12:36:03 +05:30
video.currentTime = 10000000 * Math.random()
2022-04-30 12:38:59 +05:30
if (error) { break }
2022-04-29 11:15:53 +05:30
}
// Set options to default values if not set
2022-05-05 12:36:03 +05:30
fallbackToDefault('format', 'image/png')
fallbackToDefault('offsets', [])
fallbackToDefault('startTime', 0)
fallbackToDefault('endTime', video.duration)
fallbackToDefault('count', 1)
fallbackToDefault('onLoad', false)
fallbackToDefault('onProgress', false)
2022-04-29 11:15:53 +05:30
// Filter out invalid offsets
2022-05-05 12:36:03 +05:30
if (!isPrototypeOf(Array, options.offsets)) { options.offsets = [] } else {
options.offsets = options.offsets.filter(offset => {
2022-04-29 11:15:53 +05:30
return isTimestamp(offset)
})
}
2022-05-05 12:36:03 +05:30
if (options.offsets.length !== 0) { extractOffsets = true }
2022-04-29 11:15:53 +05:30
// Check if start and end times are valid
2022-05-05 12:36:03 +05:30
if (!isTimestamp(options.startTime)) { options.startTime = 0 }
2022-04-29 11:15:53 +05:30
2022-05-05 12:36:03 +05:30
if (!isTimestamp(options.endTime)) { options.endTime = video.duration }
2022-04-29 11:15:53 +05:30
2022-05-05 12:36:03 +05:30
if (options.startTime >= options.endTime) {
options.startTime = options.endTime
options.count = 1
2022-04-29 11:15:53 +05:30
}
2022-05-05 21:33:20 +05:30
// Float values
options.startTime = +options.startTime
options.endTime = +options.endTime
2022-04-29 11:15:53 +05:30
// Convert count value to a positive integer (floor() or 0 if string)
2022-05-05 12:36:03 +05:30
options.count = Math.abs(~~options.count)
if (options.count === 0) { options.count = 1 }
if (extractOffsets) { options.count = options.offsets.length }
2022-04-29 11:15:53 +05:30
2022-04-30 12:38:59 +05:30
// Starting at startTime and ending at endTime - interval
2022-05-05 12:36:03 +05:30
const interval = (options.endTime - options.startTime) / options.count
2022-04-29 11:15:53 +05:30
// Set Width and Height
2022-05-05 12:36:03 +05:30
let isWidthSet = hasOwnProperty.call(options, 'width')
let isHeightSet = hasOwnProperty.call(options, 'height')
2022-04-29 11:15:53 +05:30
const videoDimensionRatio = video.videoWidth / video.videoHeight
// Reset Width and Height if not valid
2022-05-05 12:36:03 +05:30
if (isWidthSet && !isNumber(options.width)) { isWidthSet = false }
2022-04-29 11:15:53 +05:30
2022-05-05 12:36:03 +05:30
if (isHeightSet && !isNumber(options.height)) { isHeightSet = false }
2022-04-29 11:15:53 +05:30
if (!isWidthSet && !isHeightSet) {
// Both Width and Height not set
2022-05-05 12:36:03 +05:30
options.width = 128 // Default Value (randomly set)
options.height = options.width / videoDimensionRatio
2022-04-29 11:15:53 +05:30
} else if (isWidthSet && !isHeightSet) {
// Width set but Height not set
2022-05-05 12:36:03 +05:30
options.height = options.width / videoDimensionRatio
2022-04-29 11:15:53 +05:30
} else if (!isWidthSet && isHeightSet) {
// Height set but Width not set
2022-05-05 12:36:03 +05:30
options.width = options.height * videoDimensionRatio
2022-04-29 11:15:53 +05:30
}
// Float values
2022-05-05 12:36:03 +05:30
options.width = +options.width
options.height = +options.height
2022-04-29 11:15:53 +05:30
2022-04-30 12:38:59 +05:30
// Reset onLoad and onProgress functions if not valid
2022-05-05 12:36:03 +05:30
if (!isPrototypeOf(Function, options.onLoad)) { options.onLoad = false }
2022-04-30 12:38:59 +05:30
2022-05-05 12:36:03 +05:30
if (!isPrototypeOf(Function, options.onProgress)) { options.onProgress = false }
2022-04-30 12:38:59 +05:30
2022-05-05 12:36:03 +05:30
if (options.onLoad) { options.onLoad() }
2022-04-30 12:38:59 +05:30
2022-04-29 11:15:53 +05:30
// Buffer Canvas Element
2022-05-05 12:36:03 +05:30
const canvas = document.createElement('canvas')
2022-04-29 11:15:53 +05:30
const context = canvas.getContext('2d')
2022-05-05 12:36:03 +05:30
canvas.width = options.width
canvas.height = options.height
2022-04-29 11:15:53 +05:30
2022-04-29 11:39:56 +05:30
const extract = async resolve => {
2022-05-05 12:36:03 +05:30
while (index < options.count) {
video.currentTime = extractOffsets ? options.offsets[index] : options.startTime + index * interval
2022-04-29 11:15:53 +05:30
await new Promise(resolve => { seekResolve = resolve })
2022-05-05 12:36:03 +05:30
context.clearRect(0, 0, canvas.width, canvas.height)
context.drawImage(video, 0, 0, canvas.width, canvas.height)
2022-04-30 12:38:59 +05:30
frames.push({
2022-05-05 12:36:03 +05:30
offset: video.currentTime,
image: canvas.toDataURL(options.format)
2022-04-30 12:38:59 +05:30
})
2022-04-29 11:15:53 +05:30
index++
2022-05-05 12:36:03 +05:30
if (options.onProgress) { options.onProgress(index, options.count) }
2022-04-29 11:15:53 +05:30
}
resolve(frames)
2022-04-29 11:39:56 +05:30
}
return new Promise(resolve => {
if (error) { resolve([]) }
2022-04-30 12:38:59 +05:30
2022-04-29 11:39:56 +05:30
extract(resolve)
2022-04-29 11:15:53 +05:30
})
}