// This should work both in node and in the browsers, so that's what this wrapper is about ;(function(root, undefined) { /** * Timecode object constructor * @param {number|String|Date|Object} timeCode Frame count as number, "HH:MM:SS(:|;|.)FF", Date(), or object. * @param {number} [frameRate=29.97] Frame rate * @param {boolean} [dropFrame=true] Whether the timecode is drop-frame or not * @constructor * @returns {Timecode} timecode */ var Timecode = function ( timeCode, frameRate, dropFrame ) { // Make this class safe for use without "new" if (!(this instanceof Timecode)) return new Timecode( timeCode, frameRate, dropFrame); // Get frame rate if (typeof frameRate === 'undefined') this.frameRate = 29.97; else if (typeof frameRate === 'number' && frameRate>0) this.frameRate = frameRate; else throw new Error('Number expected as framerate'); if (this.frameRate!==23.976 && this.frameRate!==24 && this.frameRate!==25 && this.frameRate!==29.97 && this.frameRate!==30 && this.frameRate!==50 && this.frameRate!==59.94 && this.frameRate!==60 ) throw new Error('Unsupported framerate'); // If we are passed dropFrame, we need to use it if (typeof dropFrame === 'boolean') this.dropFrame = dropFrame; else this.dropFrame = (this.frameRate===29.97 || this.frameRate===59.94); // by default, assume DF for 29.97 and 59.94, NDF otherwise // Now either get the frame count, string or datetime if (typeof timeCode === 'number') { this.frameCount = Math.round(timeCode); this._frameCountToTimeCode(); } else if (typeof timeCode === 'string') { // pick it apart var parts = timeCode.match('^([012]\\d):(\\d\\d):(\\d\\d)(:|;|\\.)(\\d\\d)$'); if (!parts) throw new Error("Timecode string expected as HH:MM:SS:FF or HH:MM:SS;FF"); this.hours = parseInt(parts[1]); this.minutes = parseInt(parts[2]); this.seconds = parseInt(parts[3]); // do not override input parameters if (typeof dropFrame !== 'boolean') { this.dropFrame = parts[4]!==':'; } this.frames = parseInt(parts[5]); this._timeCodeToFrameCount(); } else if (typeof timeCode === 'object' && timeCode instanceof Date) { var midnight = new Date(timeCode.getFullYear(), timeCode.getMonth(), timeCode.getDate(),0,0,0); var midnight_tz = midnight.getTimezoneOffset() * 60 * 1000; var timecode_tz = timeCode.getTimezoneOffset() * 60 * 1000; this.frameCount = Math.round(((timeCode-midnight + (midnight_tz - timecode_tz))*this.frameRate)/1000); this._frameCountToTimeCode(); } else if (typeof timeCode === 'object' && timeCode.hours >= 0) { this.hours = timeCode.hours; this.minutes = timeCode.minutes; this.seconds = timeCode.seconds; this.frames = timeCode.frames; this._timeCodeToFrameCount(); } else if (typeof timeCode === 'undefined') { this.frameCount = 0; } else { throw new Error('Timecode() constructor expects a number, timecode string, or Date()'); } this._validate(timeCode); return this; }; /** * Validates timecode * @private * @param {number|String|Date|Object} timeCode for the reference */ Timecode.prototype._validate = function (timeCode) { // Make sure dropFrame is only for 29.97 & 59.94 if (this.dropFrame && this.frameRate!==29.97 && this.frameRate!==59.94) { throw new Error('Drop frame is only supported for 29.97 and 59.94 fps'); } // make sure the numbers make sense if (this.hours > 23 || this.minutes > 59 || this.seconds > 59 || this.frames >= this.frameRate || (this.dropFrame && this.seconds === 0 && this.minutes % 10 && this.frames < 2 * (this.frameRate / 29.97))) { throw new Error("Invalid timecode" + JSON.stringify(timeCode)); } }; /** * Calculate timecode based on frame count * @private */ Timecode.prototype._frameCountToTimeCode = function() { var fc = this.frameCount; // adjust for dropFrame if (this.dropFrame) { var df = this.frameRate===29.97 ? 2 : 4; // 59.94 skips 4 frames var d = Math.floor(this.frameCount / (17982*df/2)); var m = this.frameCount % (17982*df/2); if (m 0) { newFrameCount = (Math.round(this.frameRate*86400)) + newFrameCount; if (((newFrameCount / this.frameRate) / 3600) > rollOverMaxHours) { throw new Error('Rollover arithmetic exceeds max permitted'); } } if (newFrameCount<0) { throw new Error("Negative timecodes not supported"); } this.frameCount = newFrameCount; } else { if (!(t instanceof Timecode)) t = new Timecode(t, this.frameRate, this.dropFrame); return this.add(t.frameCount,negative,rollOverMaxHours); } this.frameCount = this.frameCount % (Math.round(this.frameRate*86400)); // wraparound 24h this._frameCountToTimeCode(); return this; }; Timecode.prototype.subtract = function(t, rollOverMaxHours) { return this.add(t,true,rollOverMaxHours); }; /** * Converts timecode to a Date() object * @returns {Date} date */ Timecode.prototype.toDate = function() { var ms = this.frameCount/this.frameRate*1000; var midnight = new Date(); midnight.setHours(0); midnight.setMinutes(0); midnight.setSeconds(0); midnight.setMilliseconds(0); var d = new Date( midnight.valueOf() + ms ); var midnight_tz = midnight.getTimezoneOffset() * 60 * 1000; var timecode_tz = d.getTimezoneOffset() * 60 * 1000; return new Date( midnight.valueOf() + ms + (timecode_tz-midnight_tz)); }; // Export it for Node or attach to root for in-browser /* istanbul ignore else */ if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { module.exports = Timecode; } else if (root) { root.Timecode = Timecode; } }(this));