UNPKG

kontainer-js

Version:

A media file format generator/parser that exposes a React-like API.

570 lines (440 loc) 16 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _get2 = require('babel-runtime/helpers/get'); var _get3 = _interopRequireDefault(_get2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _IsoBmff = require('./IsoBmff'); var _IsoBmff2 = _interopRequireDefault(_IsoBmff); var _Matroska = require('./Matroska'); var _Matroska2 = _interopRequireDefault(_Matroska); var _Reader = require('./core/Reader'); var _Reader2 = _interopRequireDefault(_Reader); var _Writer = require('./core/Writer'); var _Writer2 = _interopRequireDefault(_Writer); var _Buffer = require('./core/Buffer'); var _Buffer2 = _interopRequireDefault(_Buffer); var _Component = require('./core/Component'); var _Component2 = _interopRequireDefault(_Component); var _Visitor = require('./core/Visitor'); var _Stream = require('./core/Stream'); var _Error = require('./core/Error'); var _MediaFormat = require('./core/MediaFormat'); var _Util = require('./core/Util'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var SUPPORTED_FORMATS = [_Matroska2.default, _IsoBmff2.default]; var DEFAULT_FORMAT = _IsoBmff2.default; var currentFormat = _IsoBmff2.default; function use(format) { var fmt = SUPPORTED_FORMATS.find(function (f) { return f.name === format; }); if (fmt) { currentFormat = fmt; } else { console.error('[Kontainer.use] Unsupported format: "' + format + '"'); currentFormat = DEFAULT_FORMAT; } } function checkContainerFormat(buffer) { var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; var fmt = SUPPORTED_FORMATS.find(function (f) { return f.canParse(buffer, offset); }); if (fmt) { return fmt.name; } else { return 'unknown'; } } function detectFormat(buffer) { var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; var fmt = SUPPORTED_FORMATS.find(function (f) { return f.canParse(buffer, offset); }); if (fmt) { currentFormat = fmt; return true; } else { return false; } } function traverse(context, element, buffer) { var offset = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3]; var type = void 0, props = void 0, children = void 0, instance = void 0, propTypes = void 0, base = offset, err = void 0; if (!element) { console.warn('Kontainer.render: null element.'); return 0; } type = element.type; props = element.props; children = props.children; instance = element.instance; //console.log(`traverse enter. type=${type.COMPACT_NAME}`); if (!instance) { // Validate props propTypes = type.propTypes; if (propTypes) { if (!(0, _keys2.default)(propTypes).every(function (key) { err = propTypes[key](props, key, type.COMPACT_NAME); if (err) { return false; } return true; })) { (0, _Util.throwException)('Kontainer.render: Prop validation failed: ' + err.message); } } // Validate context type.validate(context, props); // Instantiation instance = element.instance = new type(props); } // Write self to the array buffer. base += instance.serialize(buffer, offset); // Write children to the array buffer. children.forEach(function (child) { base += traverse(context, child, buffer, base); }); // Update the size. instance.setSize(base - offset, buffer, offset); //console.log(`traverse exit. type=${type.COMPACT_NAME} size=${instance.getSize()}`); return instance.getSize(); } function render(element) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var size = void 0, buffer = void 0, context = {}; try { // Culculate the entire byte size. size = traverse(context, element); } catch (err) { console.error('render: An error occurred in culculating the buffer size: ' + err.stack); return null; } if (options.dryRun) { return size; } if (size === 0) { return null; } // Write to the array buffer. buffer = new _Buffer2.default(size); traverse(context, element, buffer.getView()); return buffer.getData(); } function validateChild(context, child) { var childSpec = child.type.spec; var childName = child.type.COMPACT_NAME; var checkList = context.mandatoryCheckList; var quantityTable = context.quantityTable; var container = void 0, quantity = void 0; // Container check. if (childSpec.container && childSpec.container !== '*') { if (childSpec.container instanceof Array) { container = childSpec.container; } else { container = [childSpec.container]; } if (container.indexOf(context.container) === -1) { return [false, '"' + childName + '" cannot be a child of "' + context.container + '"']; } } // Mandatory check. checkList[childName] = true; // Quantity check. if ((quantity = childSpec.quantity) !== _Component2.default.QUANTITY_ANY_NUMBER) { // Increment if (quantityTable[childName] === void 0) { quantityTable[childName] = 1; } else { quantityTable[childName]++; } // Validate if (quantity === _Component2.default.QUANTITY_EXACTLY_ONE) { if (quantityTable[childName] !== 1) { return [false, 'Quantity of ' + childName + ' should be exactly one.']; } } else if (quantity === _Component2.default.QUANTITY_ZERO_OR_ONE) { if (quantityTable[childName] > 1) { return [false, 'Quantity of ' + childName + ' should be zero or one.']; } } } return [true, null]; } function createElement(type) { var componentClass = void 0, element = void 0, context = {}, spec = void 0, result = void 0, errorMessage = void 0, checkList = void 0; // Validate type. if (typeof type === 'string') { componentClass = currentFormat.getComponentClass(type); if (!componentClass) { console.error('createElement: invalid type: "' + type + '"'); return null; } } else if (!type || !(type instanceof _Component2.default)) { console.error('createElement: "type" should be a subclass of the Component.'); return null; } else { componentClass = type; } // Create element. for (var _len = arguments.length, otherParams = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { otherParams[_key - 1] = arguments[_key]; } if (!(element = _MediaFormat.createElement.apply(undefined, [componentClass].concat(otherParams)))) { return null; } // Validate children. spec = componentClass.spec; context = { container: componentClass.COMPACT_NAME, mandatoryCheckList: {}, quantityTable: {} }; if (!element.props.children.every(function (child) { var _validateChild = validateChild(context, child); var _validateChild2 = (0, _slicedToArray3.default)(_validateChild, 2); result = _validateChild2[0]; errorMessage = _validateChild2[1]; return result; })) { console.error('createElement: Breaking the composition rule: ' + errorMessage); return null; } checkList = context.mandatoryCheckList; spec.mandatoryList.forEach(function (boxType) { if (boxType instanceof Array) { if (boxType.some(function (box) { return checkList[box]; })) { return; } boxType = boxType.join('", or "'); } else { if (checkList[boxType]) { return; } } console.error('createElement: Breaking the composition rule: "' + boxType + '" is required as a child of "' + context.container + '"'); element = null; }); return element; } function parse(buffer, offset, visitor, options) { var readBytesNum = void 0; var props = void 0; var base = offset; var componentClass = void 0, componentSize = void 0; // Read the first bytes as we don't know the type and the size. var _currentFormat$parseT = currentFormat.parseTypeAndSize(buffer, offset, options); var _currentFormat$parseT2 = (0, _slicedToArray3.default)(_currentFormat$parseT, 3); readBytesNum = _currentFormat$parseT2[0]; componentClass = _currentFormat$parseT2[1]; componentSize = _currentFormat$parseT2[2]; if (!componentClass) { visitor.offset = base; return componentSize; } var bytesToSkip = 0; if (componentSize === -1) { // Unknown size. Naive code. bytesToSkip = currentFormat.skipBytes(buffer, offset + readBytesNum); } var componentEnd = componentSize === -1 ? buffer.length : offset + componentSize; var componentName = componentClass.COMPACT_NAME; //console.log(`parse enter.: type=${componentName} size=${componentSize} offset=${offset}`); if (componentSize === -1) { try { var _componentClass$parse = componentClass.parse(buffer, offset); var _componentClass$parse2 = (0, _slicedToArray3.default)(_componentClass$parse, 2); readBytesNum = _componentClass$parse2[0]; props = _componentClass$parse2[1]; } catch (e) { props = { size: -1 }; } readBytesNum += bytesToSkip; } else { var _componentClass$parse3 = componentClass.parse(buffer, offset); var _componentClass$parse4 = (0, _slicedToArray3.default)(_componentClass$parse3, 2); readBytesNum = _componentClass$parse4[0]; props = _componentClass$parse4[1]; } base += readBytesNum; visitor.offset = base; visitor.enter(componentClass, props); while (base < componentEnd) { readBytesNum = parse(buffer, base, visitor, options); base += readBytesNum; } visitor.exit(); visitor.offset = base; //console.log(`parse exit.: type=${componentName} readBytesNum=${Math.min(base - offset, componentSize)}`); return Math.min(base - offset, componentSize); } function createElementFromBuffer(buffer) { var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var base = offset; if (buffer instanceof ArrayBuffer) { buffer = new Uint8Array(buffer); } if (detectFormat(buffer, base) === false) { console.error('createElementFromBuffer: Unknown format'); return null; } var endOfBuffer = base + buffer.length; var visitor = new _Visitor.ElementVisitor(); try { while (base < endOfBuffer) { var readBytesNum = parse(buffer, base, visitor, options); base += readBytesNum; } } catch (err) { if (err.message !== _Error.BufferReadError.ERROR_MESSAGE) { console.error('createElementFromBuffer: An error occurred in parsing the buffer: ' + err.stack); } return null; } //console.log(`createElementFromBuffer: Done. ${base - offset} bytes read.`); if (visitor.results.length === 0) { return null; } else if (visitor.results.length === 1) { return visitor.results[0]; } return _MediaFormat.createElement.apply(undefined, [currentFormat.getRootWrapperClass(), null].concat((0, _toConsumableArray3.default)(visitor.results))); } function transform(visitor) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var vtor = void 0, formatSet = false; if (visitor instanceof _Visitor.Visitor) { vtor = visitor; } else { // Received a filter function var TransformVisitor = function (_ElementVisitor) { (0, _inherits3.default)(TransformVisitor, _ElementVisitor); function TransformVisitor() { (0, _classCallCheck3.default)(this, TransformVisitor); return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(TransformVisitor).apply(this, arguments)); } (0, _createClass3.default)(TransformVisitor, [{ key: 'visit', value: function visit(type, props, children) { visitor && visitor(type.COMPACT_NAME, props, children); return (0, _get3.default)((0, _getPrototypeOf2.default)(TransformVisitor.prototype), 'visit', this).call(this, type, props, children); } }]); return TransformVisitor; }(_Visitor.ElementVisitor); vtor = new TransformVisitor(); } return new _Stream.TransformStream(function (buffer, offset, cb) { var base = vtor.offset; var buf = buffer.getData(); if (buf instanceof ArrayBuffer) { buf = new Uint8Array(buf); } if (!formatSet && detectFormat(buf, base) === false) { // Unknow format or the buffer is insufficient. cb('done', null); return; } formatSet = true; cb('format', currentFormat.name); var endOfBuffer = buf.length; try { while (base < endOfBuffer) { var readBytesNum = parse(buf, base, vtor, options); base += readBytesNum; } } catch (err) { if (err.message === _Error.BufferReadError.ERROR_MESSAGE) { cb('progress', err.pendingData); } else { console.error('transform: An error occurred in parsing the buffer: ' + err.stack); } cb('done', null); return; } while (vtor.stack.length) { vtor.exit(); } if (this.options.objectMode) { cb('done', null); } else { cb('done', render(_MediaFormat.createElement.apply(undefined, [currentFormat.getRootWrapperClass(), null].concat((0, _toConsumableArray3.default)(vtor.results))))); } }, options); } function createObjectStream(visitor) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var TransformVisitor = function (_ElementVisitor2) { (0, _inherits3.default)(TransformVisitor, _ElementVisitor2); function TransformVisitor() { (0, _classCallCheck3.default)(this, TransformVisitor); return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(TransformVisitor).apply(this, arguments)); } (0, _createClass3.default)(TransformVisitor, [{ key: 'visit', value: function visit(type, props, children) { var element = (0, _get3.default)((0, _getPrototypeOf2.default)(TransformVisitor.prototype), 'visit', this).call(this, type, props, children); var depth = this.depth(); if (depth === 0) { element.setFormatInfo(currentFormat); } visitor && visitor(type.COMPACT_NAME, element, depth); return element; } }]); return TransformVisitor; }(_Visitor.ElementVisitor); var vtor = new TransformVisitor(); options.objectMode = true; return transform(vtor, options); } exports.default = { use: use, checkContainerFormat: checkContainerFormat, get mode() { return currentFormat.name; }, render: render, createElement: createElement, createElementFromBuffer: createElementFromBuffer, transform: transform, createObjectStream: createObjectStream, ElementVisitor: _Visitor.ElementVisitor, DumpVisitor: _Visitor.DumpVisitor };