Streaming MXF library for Node.js.
npm install kelvinadon
kelvinadump is provided. Reading part of a stream, say just a single partition, will also work subject to sufficient metadata being provided. Otherwise, this package is a low-level library and does not contain any of the following facilities:
kelvinadump:
sudo to the above.
myfile.mxf and see all the KLV structures inside of it, install kelvinadon as a global dependency and run:
javascript
KLVPacket {
key: '060e2b34-0205-0101-0d01-020101020400',
length: 136,
value: [ ],
lengthLength: 4,
filePos: 0,
meta:
{ Symbol: 'HeaderPartitionClosedComplete',
Name: 'Header Partition Closed Complete',
Identification: 'urn:smpte:ul:060e2b34.027f0101.0d010201.01020400',
UUID: '060e2b34-027f-0101-0d01-020101020400',
Description: 'Header Partition Closed Complete',
Kind: 'LEAF',
NamespaceName: 'http://www.smpte-ra.org/reg/395/2014/13/1/aaf',
MetaType: 'ClassDefinition',
ParentClass: 'HeaderPartitionPack',
KLVSyntax: '05' },
detail:
{ ObjectClass: 'HeaderPartitionClosedComplete',
MajorVersion: 1,
MinorVersion: 3,
KAGSize: 1,
ThisPartition: 0,
PreviousPartition: 0,
FooterPartition: 13597676801,
HeaderByteCount: 28867,
IndexByteCount: 0,
IndexStreamID: 0,
BodyOffset: 0,
EssenceStreamID: 0,
OperationalPattern: 'MXFOP1aSingleItemSinglePackageMultiTrackStreamInternal',
EssenceContainers:
[ 'MXFGCFrameWrappedBroadcastWaveAudioData',
'MXFGCGenericEssenceMultipleMappings',
'MXFGCAVCByteStreamWithVideoStream0SIDFrameWrapped' ] } }
`
Each packet has:
* key 16-byte identifier;
* length length of the following value;
* value array of chunks of data that make up the value of the packet;
* lengthLength number of bytes used to carry the length value;
* filePos absolute file position of this KLV packet in the overall stream;
* meta if the key is recognized, the meta definition of the corresponding data type;
* detail application of the meta definition used to decode the buffer and interpret the data it contains;
* props (not shown) for local sets, an array or arrays describing each local tag found in a local set.
Some command line options have been provided to enable a user to configure the structure of the output. Run kelvinadump --help for more details.
From another application
Kelvinadon has two modes that it can be used in within an application:
1. As an event emitter, with the data within the stream split into _partitions_, _metadata_, _indexes_ and _essence by track_.
2. Via highland.js, allowing back pressure to be applied via the building blocks provided.
$3
To use kelvinadon as a Node.js event emitter, install it as a local dependency and follow the example below:
`javascript
var klv = require('kelvinadon');
var fs = require('fs');
// Create a new MXF event emitter for a Node.js readable stream
// Files, HTTP and other stream sources are possible.
var mxfEvents = new klv.MXFEmitter(fs.createReadStream('myfile.mxf'));
// Listen for errors
mxfEvents.on('error', function (e) { console.error(e); });
// Receive any header metadata sets (optional)
mxfEvents.on('metadata', function (preface) {
// Process a structured object containing the metadata preface & its children
});
// Listen for information on a picture track
mxfEvents.on('picture0', function (data) {
// Data is an object containing the Buffer value, length, track details, descriptor and
// associated timecode track for the media
});
// Listen for information on a sound track
mxfEvents.on('sound0', function (data) {
// Data is an object containing the Buffer value, length, track details, descriptor and
// associated timecode track for the media
});
// Event called at the end of the stream
mxfEvents.on('done', function () { console.log('Streaming complete.'); });
`
The track names are available with the metadata event by calling mxfEvents.getTrackList(). Typically, these are picture0, sound0, sound1, ... , data0 etc.. Alternatively, you can use the essence event to listen for all essence elements in the stream. Note that a sound track may contain more than one channel.
The design is such that events are fairly self-contained, providing enough information that a decoder could process them standalone, including the count of the index of this element within its stream, the source package identifier, file descriptor, track details and the start timecode of the same package. Here is an example of the event data sent for picture data:
`javascript
{ trackNumber: '15010500',
value: ,
length: 568832,
element: [ 0, 1 ],
sourcePackageID:
[ '060a2b34-0101-0105-0101-0f2013000000',
'1831346c-5001-254c-a5b7-c5e06f674dde' ],
count: 0,
track:
{ ObjectClass: 'TimelineTrack',
InstanceID: '52598e7a-ed47-7640-80f4-6e2e2edc789d',
TrackName: 'V1',
TrackID: 1001,
EssenceTrackNumber: 352388352,
EditRate: [ 25, 1 ],
Origin: 0,
TrackSegment:
{ ObjectClass: 'Sequence',
InstanceID: '30a3d791-5bfb-6a47-aca1-0970ed3582ae',
ComponentDataDefinition: 'PictureEssenceTrack',
ComponentLength: 20561,
ComponentObjects:
[ { ObjectClass: 'SourceClip',
InstanceID: '761faa1f-3a79-cf4b-a527-1290027a91d8',
ComponentDataDefinition: 'PictureEssenceTrack',
ComponentLength: 20561,
StartPosition: 0,
SourceTrackID: 0,
SourcePackageID:
[ '00000000-0000-0000-0000-000000000000',
'00000000-0000-0000-0000-000000000000' ] } ] } },
description:
{ ObjectClass: 'MPEGVideoDescriptor',
InstanceID: 'fdbf8fda-54c5-9244-952f-bc1ac12261c4',
ContainerFormat: 'MXFGCAVCByteStreamWithVideoStream0SIDFrameWrapped',
SampleRate: [ 25, 1 ],
ImageAspectRatio: [ 16, 9 ],
ActiveFormatDescriptor: 84,
PictureCompression: 'H264MPEG4AVCHigh422IntraRP2027ConstrainedClass100108050iCoding',
SignalStandard: 'SignalStandard_SMPTE274M',
FrameLayout: 'SeparateFields',
ColorSiting: 'CoSiting',
ComponentDepth: 10,
BlackRefLevel: 64,
WhiteRefLevel: 940,
ColorRange: 897,
CodingEquations: 'CodingEquations_ITU709',
StoredWidth: 1920,
StoredHeight: 540,
VideoLineMap: [ 21, 584 ],
DisplayWidth: 1920,
DisplayHeight: 540,
SampledWidth: 1920,
SampledHeight: 540,
HorizontalSubsampling: 2,
VerticalSubsampling: 1,
LinkedTrackID: 1001,
EssenceLength: 20561 },
startTimecode:
{ ObjectClass: 'Timecode',
InstanceID: 'a2f8e74e-6008-3b4e-bc05-9dbcfe43b392',
ComponentDataDefinition: 'SMPTE12MTimecodeTrackInactiveUserBits',
ComponentLength: 20561,
FramesPerSecond: 25,
DropFrame: false,
StartTimecode: 899250 } }
`
Note that kelvinadon looks up any label definitions, converting them to their more readable _symbols_.
$3
Under the bonnet, the file dumper kelvinadump and event emitter are highland streams. The building blocks of these streams are pipelines exposed by the kelvinadon module. For example, to build a highland version of the event emitter example:
`javascript
var H = require('highland');
var fs = require('fs');
var klv = require('klevinadon');
var base = H(fs.createReadStream(process.argv[2]))
.through(klv.kelviniser())
.through(klv.metatiser())
.through(klv.stripTheFiller())
.through(klv.detailing())
.through(klv.puppeteer())
.through(klv.trackCacher());
base.fork()
.through(klv.essenceFilter('picture0')) // Track names as per event model
.doto(H.log)
.errors(function (e) { console.error(e); })
.done(function () { console.log('Finished reading picture data.'); });
base.fork()
.through(klv.essenceFilter('sound0')) // Track names as per event model
.doto(H.log)
.errors(function (e) { console.error(e); })
.done(function () { console.log('Finished reading sound data.') });
base.resume();
`
#### Reading
The highland pipeline reading stages available are described below and should be applied in the given order:
1. kelviniser Turns a byte stream into a stream of raw KLV packets. The input stream is any node readable stream, including HTTP response objects. Also tried with FTP streams.
2. metatiser Reads the keys of the KLV stream and adds meta definitions to the KLV packets. See the lib folder for the meta dictionaries in use.
3. stripTheFiller Remove any filler elements from the stream (optional).
4. detailing Use the meta definition to extract the details from the Buffer value, such as decoding a local set to a metadata value.
5. puppeteer Collapse metadata classes into a single preface-object-with-children by resolving local references. The children are removed from the stream and the preface is sent on down the stream without its KLV wrapper.
6. trackCacher Cache track details and give names to each metadata track. An object with class TrackCache is created and sent on down the stream.
7. partitionFilter, metadataFilter, indexFilter and essenceFilter. Filter the stream so that it contains only KLV packets of the given type. A track name can be passed to essenceFilter to make it track specific.
If a consumer that is slower than the producer is added to the stream, the producer (e.g. file reader stream) will be slowed down via back pressure.
Also provided is emmyiser, the highland side-effect that is the basis of event emitter. This must be placed in the pipeline after the trackCacher.
#### Writing
Most highland pipeline reading stages have mirror writing stages that can be used to convert a stream of Javascript objects with embedded buffers back to binary KLV. For example, the kelviniser stage has a mirror state kelvinator that takes the key, length and value of a KLVPacket object and converts it to a Javascript Buffer. The writing stages are:
1. kelvinator mirrors kelviniser: Convert a stream of KLVPacket objects into a stream of Javascript buffers that can be piped into a Node.JS stream, such as a file writing stream, HTTP response or streaming object store API.
2. packetator mirrors detailing: Convert a stream of _detail_ objects representing local sets, fixed-length packs and essence elements into a stream of KLV packets, creating the key, length and value from the detail.
3. pieceMaker mirrors puppeteer: Explode a nested preface-object-with-children into a primer pack, a flat preface with _instance uid_ references and a sequence of separate local sets.
The aim is that a stream of MXF can be passed through kelviniser to puppeteer (without stripTheFiller) to make Javascript objects and back through pieceMaker to kelvinator and the same stream of MXF is produced. Minor tweaks could be made in the middle but note that at this time, no logic is provided to fix up fillers and calculate the impact of a change of offset values or index tables.
Some reading functions have no mirror and that is because they don't need one. Also note that the writing functions are not currently setting the file position property (filePos) of KLV packets.
Meta dictionaries
The dictionary used to read the files is stored in file lib/RegDefs.json.gz. This is generated automatically from the published registers using script util/updateRegisters.js, e.g.:
node util/updateRegisters.js
If you have a local clone of this repository, you can update this metadata.
Some additional definitions are required to override some shortcomings in the published registers and enable full MXF data processing. These are stored in file lib/OverrideDefs.json which is in turn generated by script util/makeMXFDefs.js. A user may add their own extensions on file lib/ExtensionDefs.json following the patterns established in the other two files.
With this approach, kelvinadump can encode and decode registered descriptive frameworks, for example:
`Javascript
{ ObjectClass: 'DM_AS_11_UKDPP_Framework',
InstanceID: 'b8743c4a-5ab4-5b41-af46-1b8b95c0e090',
UKDPP_Audio_Loudness_Standard: 'Loudness_EBU_R_128',
UKDPP_Textless_Elements_Exist: false,
UKDPP_Picture_Ratio: [ 16, 9 ],
UKDPP_Tertiary_Audio_Language: 'zxx',
UKDPP_PSE_Pass: 'PSE_Not_tested',
UKDPP_Total_Number_Of_Parts: 1,
UKDPP_Programme_Text_Language: 'eng',
UKDPP_Genre: 'Test Material',
...
}
``