kontainer-js
Version:
A media file format generator/parser that exposes a React-like API.
570 lines (440 loc) • 16 kB
JavaScript
'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
};