mediaelement
Version:
One file. Any browser. Same UI.
282 lines (241 loc) • 7.4 kB
JavaScript
;
import mejs from '../core/mejs';
/**
* Indicate if FPS is dropFrame (typically non-integer frame rates: 29.976)
*
* @param {Number} fps - Frames per second
* @return {Boolean}
*/
export function isDropFrame(fps = 25) {
return !(fps % 1 === 0);
}
/**
* Format a numeric time in format '00:00:00'
*
* @param {Number} time - Ideally a number, but if not or less than zero, is defaulted to zero
* @param {Boolean} forceHours
* @param {Boolean} showFrameCount
* @param {Number} fps - Frames per second
* @param {Number} secondsDecimalLength - Number of decimals to display if any
* @param {String} timeFormat
* @return {String}
*/
export function secondsToTimeCode(time, forceHours = false, showFrameCount = false, fps = 25, secondsDecimalLength = 0, timeFormat = 'hh:mm:ss') {
time = !time || typeof time !== 'number' || time < 0 ? 0 : time;
let
dropFrames = Math.round(fps * 0.066666), // Number of drop frames to drop on the minute marks (6%)
timeBase = Math.round(fps),
framesPer24Hours = Math.round(fps * 3600) * 24,
framesPer10Minutes = Math.round(fps * 600),
frameSep = isDropFrame(fps) ? ';' : ':',
hours,
minutes,
seconds,
frames,
f = Math.round(time * fps)
;
if (isDropFrame(fps)) {
if (f < 0) {
f = framesPer24Hours + f;
}
f = f % framesPer24Hours;
const d = Math.floor(f / framesPer10Minutes);
const m = f % framesPer10Minutes;
f = f + dropFrames * 9 * d;
if (m > dropFrames) {
f = f + dropFrames * (Math.floor((m - dropFrames) / (Math.round(timeBase * 60 - dropFrames))));
}
const timeBaseDivision = Math.floor(f / timeBase);
hours = Math.floor(Math.floor(timeBaseDivision / 60) / 60);
minutes = Math.floor(timeBaseDivision / 60) % 60;
if (showFrameCount) {
seconds = timeBaseDivision % 60;
} else {
seconds = Math.floor((f / timeBase) % 60).toFixed(secondsDecimalLength);
}
}
else {
hours = Math.floor(time / 3600) % 24;
minutes = Math.floor(time / 60) % 60;
if (showFrameCount) {
seconds = Math.floor(time % 60);
} else {
seconds = Math.floor(time % 60).toFixed(secondsDecimalLength);
}
}
hours = hours <= 0 ? 0 : hours;
minutes = minutes <= 0 ? 0 : minutes;
seconds = seconds <= 0 ? 0 : seconds;
seconds = seconds === 60 ? 0 : seconds;
minutes = minutes === 60 ? 0 : minutes;
const timeFormatFrags = timeFormat.split(':');
const timeFormatSettings = {};
for (let i = 0, total = timeFormatFrags.length; i < total; ++i) {
let unique = '';
for (let j = 0, t = timeFormatFrags[i].length; j < t; j++) {
if (unique.indexOf(timeFormatFrags[i][j]) < 0) {
unique += timeFormatFrags[i][j];
}
}
if (~['f', 's', 'm', 'h'].indexOf(unique)) {
timeFormatSettings[unique] = timeFormatFrags[i].length;
}
}
let result = (forceHours || hours > 0) ? `${(hours < 10 && timeFormatSettings.h > 1 ? `0${hours}` : hours)}:` : '';
result += `${(minutes < 10 && timeFormatSettings.m > 1 ? `0${minutes}` : minutes)}:`;
result += `${(seconds < 10 && timeFormatSettings.s > 1 ? `0${seconds}` : seconds)}`;
if (showFrameCount) {
frames = (f % timeBase).toFixed(0);
frames = frames <= 0 ? 0 : frames;
result += (frames < 10 && timeFormatSettings.f) ? `${frameSep}0${frames}` : `${frameSep}${frames}`;
}
return result;
}
/**
* Convert a '00:00:00' time string into seconds
*
* @param {String} time
* @param {Number} fps - Frames per second
* @return {Number}
*/
export function timeCodeToSeconds (time, fps = 25) {
if (typeof time !== 'string') {
throw new TypeError('Time must be a string');
}
if (time.indexOf(';') > 0) {
time = time.replace(';', ':');
}
if (!/\d{2}(\:\d{2}){0,3}/i.test(time)) {
throw new TypeError('Time code must have the format `00:00:00`');
}
const parts = time.split(':');
let
output,
hours = 0,
minutes = 0,
seconds = 0,
frames = 0,
totalMinutes = 0,
dropFrames = Math.round(fps * 0.066666), // Number of drop frames to drop on the minute marks (6%)
timeBase = Math.round(fps),
hFrames = timeBase * 3600,
mFrames = timeBase * 60
;
switch (parts.length) {
default:
case 1:
seconds = parseInt(parts[0], 10);
break;
case 2:
minutes = parseInt(parts[0], 10);
seconds = parseInt(parts[1], 10);
break;
case 3:
hours = parseInt(parts[0], 10);
minutes = parseInt(parts[1], 10);
seconds = parseInt(parts[2], 10);
break;
case 4:
hours = parseInt(parts[0], 10);
minutes = parseInt(parts[1], 10);
seconds = parseInt(parts[2], 10);
frames = parseInt(parts[3], 10);
break;
}
if (isDropFrame(fps)) {
totalMinutes = (60 * hours) + minutes;
output = ((hFrames * hours) + (mFrames * minutes) + (timeBase * seconds) + frames) - (dropFrames * (totalMinutes - (Math.floor(totalMinutes / 10))));
} else {
output = (( hFrames * hours ) + ( mFrames * minutes ) + fps * seconds + frames) / fps;
}
return parseFloat(output.toFixed(3));
}
/**
* Calculate the time format to use
*
* There is a default format set in the options but it can be incomplete, so it is adjusted according to the media
* duration. Format: 'hh:mm:ss:ff'
* @param {*} time - Ideally a number, but if not or less than zero, is defaulted to zero
* @param {Object} options
* @param {Number} fps - Frames per second
*/
export function calculateTimeFormat (time, options, fps = 25) {
time = !time || typeof time !== 'number' || time < 0 ? 0 : time;
const
hours = Math.floor(time / 3600) % 24,
minutes = Math.floor(time / 60) % 60,
seconds = Math.floor(time % 60),
frames = Math.floor(((time % 1) * fps).toFixed(3)),
lis = [
[frames, 'f'],
[seconds, 's'],
[minutes, 'm'],
[hours, 'h']
]
;
let
format = options.timeFormat,
firstTwoPlaces = (format[1] === format[0]),
separatorIndex = firstTwoPlaces ? 2 : 1,
separator = format.length < separatorIndex ? format[separatorIndex] : ':',
firstChar = format[0],
required = false
;
for (let i = 0, len = lis.length; i < len; i++) {
if (~format.indexOf(lis[i][1])) {
required = true;
}
else if (required) {
let hasNextValue = false;
for (let j = i; j < len; j++) {
if (lis[j][0] > 0) {
hasNextValue = true;
break;
}
}
if (!hasNextValue) {
break;
}
if (!firstTwoPlaces) {
format = firstChar + format;
}
format = lis[i][1] + separator + format;
if (firstTwoPlaces) {
format = lis[i][1] + format;
}
firstChar = lis[i][1];
}
}
options.timeFormat = format;
}
/**
* Convert Society of Motion Picture and Television Engineers (SMTPE) time code into seconds
*
* @param {String} SMPTE
* @return {Number}
*/
export function convertSMPTEtoSeconds (SMPTE) {
if (typeof SMPTE !== 'string') {
throw new TypeError('Argument must be a string value');
}
SMPTE = SMPTE.replace(',', '.');
const decimalLen = ~SMPTE.indexOf('.') ? SMPTE.split('.')[1].length : 0;
let
secs = 0,
multiplier = 1
;
SMPTE = SMPTE.split(':').reverse();
for (let i = 0, total = SMPTE.length; i < total; i++) {
multiplier = 1;
if (i > 0) {
multiplier = Math.pow(60, i);
}
secs += Number(SMPTE[i]) * multiplier;
}
return Number(secs.toFixed(decimalLen));
}
mejs.Utils = mejs.Utils || {};
mejs.Utils.secondsToTimeCode = secondsToTimeCode;
mejs.Utils.timeCodeToSeconds = timeCodeToSeconds;
mejs.Utils.calculateTimeFormat = calculateTimeFormat;
mejs.Utils.convertSMPTEtoSeconds = convertSMPTEtoSeconds;