From 0cd02a4c5610d2e72e6bd1d3087331f75b9e339c Mon Sep 17 00:00:00 2001 From: RHSebregts Date: Thu, 13 Jan 2022 17:31:15 +0100 Subject: [PATCH 1/3] proper git ignore --- .gitignore | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 949d576..669cdb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,105 @@ -*.js +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories node_modules/ -lib/ +!node_modules/*/*/atem.js +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port \ No newline at end of file From 856beabf07d3a50a76a29c00fb25f4e18efa192b Mon Sep 17 00:00:00 2001 From: RHSebregts Date: Thu, 13 Jan 2022 17:32:10 +0100 Subject: [PATCH 2/3] added timecode. this output was not generated by coffeescript since the version used was very outdated. Please check if everything works for your needs. --- lib/atem.js | 813 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 813 insertions(+) create mode 100644 lib/atem.js diff --git a/lib/atem.js b/lib/atem.js new file mode 100644 index 0000000..6ab8be8 --- /dev/null +++ b/lib/atem.js @@ -0,0 +1,813 @@ +// Generated by CoffeeScript 1.7.1 +var ATEM, DEBUG, EventEmitter, FileUploader, PNG, crypto, dgram, fs, + __bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; + +fs = require('fs'); + +crypto = require('crypto'); + +dgram = require('dgram'); + +EventEmitter = require('events').EventEmitter; + +PNG = require('pngjs').PNG; + +DEBUG = process.env['ATEM_DEBUG'] ? process.env['ATEM_DEBUG'] === 'true' : false; + +ATEM = (function() { + var AUDIO_GAIN_RATE, COMMAND_CONNECT_HELLO, COMMAND_CONNECT_HELLO_ANSWER, DEFAULT_PORT, RECONNECT_INTERVAL; + + DEFAULT_PORT = 9910; + + RECONNECT_INTERVAL = 5000; + + COMMAND_CONNECT_HELLO = [0x10, 0x14, 0x53, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + COMMAND_CONNECT_HELLO_ANSWER = [0x80, 0x0C, 0x53, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00]; + + AUDIO_GAIN_RATE = 65381; + + ATEM.Model = { + 'TVS': 0x01, + '1ME': 0x02, + '2ME': 0x03, + 'PS4K': 0x04, + '1ME4K': 0x05, + '2ME4K': 0x06, + '2MEBS4K': 0x07 + }; + + ATEM.TransitionStyle = { + MIX: 0x00, + DIP: 0x01, + WIPE: 0x02, + DVE: 0x03, + STING: 0x04 + }; + + ATEM.TallyState = { + None: 0x00, + Program: 0x01, + Preview: 0x02 + }; + + ATEM.ConnectionState = { + None: 0x00, + SynSent: 0x01, + Established: 0x02, + Closed: 0x03 + }; + + ATEM.PacketFlag = { + Sync: 0x01, + Connect: 0x02, + Repeat: 0x04, + Error: 0x08, + Ack: 0x16 + }; + + ATEM.prototype.state = { + topology: { + numberOfMEs: null, + numberOfSources: null, + numberOfColorGenerators: null, + numberOfAUXs: null, + numberOfDownstreamKeys: null, + numberOfStingers: null, + numberOfDVEs: null, + numberOfSuperSources: null + }, + tallys: [], + channels: {}, + video: { + ME: [], + downstreamKeyOn: [], + downstreamKeyTie: [], + auxs: {}, + }, + audio: { + hasMonitor: null, + numberOfChannels: null, + channels: {} + } + }; + + ATEM.prototype.connectionState = ATEM.ConnectionState.Closed; + + ATEM.prototype.localPackedId = 1; + + ATEM.prototype.sessionId = []; + + ATEM.prototype.remotePacketId = []; + + function ATEM(options) { + if (options == null) { + options = {}; + } + this._receivePacket = __bind(this._receivePacket, this); + this.forceOldStyle = options.forceOldStyle || false; + this.localPort = options.localPort || 1024 + Math.floor(Math.random() * 64511); + this.event = new EventEmitter; + this.commandEvent = new EventEmitter; + this.event.on('ping', (function(_this) { + return function(err) { + return _this.lastConnectAt = new Date().getTime(); + }; + })(this)); + this.socket = dgram.createSocket('udp4'); + this.socket.on('message', this._receivePacket); + this.socket.bind(this.localPort); + setInterval((function(_this) { + return function() { + if (_this.lastConnectAt + RECONNECT_INTERVAL > new Date().getTime()) { + return; + } + if (_this.connectionState === ATEM.ConnectionState.Established) { + _this.connectionState = ATEM.ConnectionState.Closed; + _this.event.emit('disconnect', null, null); + } + _this.localPackedId = 1; + _this.sessionId = []; + return _this.connect(_this.address, _this.port); + }; + })(this), RECONNECT_INTERVAL); + } + + ATEM.prototype.connect = function(address, port, local_port) { + this.address = address; + this.port = port != null ? port : DEFAULT_PORT; + if (local_port == null) { + local_port = 0; + } + this._sendPacket(COMMAND_CONNECT_HELLO); + return this.connectionState = ATEM.ConnectionState.SynSent; + }; + + ATEM.prototype.on = function(name, callback) { + return this.event.on(name, callback); + }; + + ATEM.prototype.once = function(name, callback) { + return this.event.once(name, callback); + }; + + ATEM.prototype._sendAck = function() { + var buffer; + buffer = new Buffer(12); + buffer.fill(0); + buffer[0] = 0x80; + buffer[1] = 0x0C; + buffer[2] = this.sessionId[0]; + buffer[3] = this.sessionId[1]; + buffer[4] = this.remotePacketId[0]; + buffer[5] = this.remotePacketId[1]; + buffer[9] = 0x41; + return this._sendPacket(buffer); + }; + + ATEM.prototype._sendCommand = function(command, payload) { + var buffer; + if (!Buffer.isBuffer(payload)) { + payload = new Buffer(payload); + } + buffer = new Buffer(20 + payload.length); + buffer.fill(0); + buffer[0] = (20 + payload.length) / 256 | 0x08; + buffer[1] = (20 + payload.length) % 256; + buffer[2] = this.sessionId[0]; + buffer[3] = this.sessionId[1]; + buffer[10] = this.localPackedId / 256; + buffer[11] = this.localPackedId % 256; + buffer[12] = (8 + payload.length) / 256; + buffer[13] = (8 + payload.length) % 256; + buffer[16] = command.charCodeAt(0); + buffer[17] = command.charCodeAt(1); + buffer[18] = command.charCodeAt(2); + buffer[19] = command.charCodeAt(3); + payload.copy(buffer, 20); + this._sendPacket(buffer); + return this.localPackedId++; + }; + + ATEM.prototype._sendPacket = function(buffer) { + if (!Buffer.isBuffer(buffer)) { + buffer = new Buffer(buffer); + } + if (DEBUG) { + console.log('SEND', buffer); + } + return this.socket.send(buffer, 0, buffer.length, this.port, this.address); + }; + + ATEM.prototype._receivePacket = function(message, remote) { + var flags, length; + length = ((message[0] & 0x07) << 8) | message[1]; + if (length !== remote.size) { + return; + } + if (DEBUG) { + console.log('RECV', message); + } + flags = message[0] >> 3; + this.sessionId = [message[2], message[3]]; + this.remotePacketId = [message[10], message[11]]; + if (flags & ATEM.PacketFlag.Connect && !(flags & ATEM.PacketFlag.Repeat)) { + this._sendPacket(COMMAND_CONNECT_HELLO_ANSWER); + } + if (flags & ATEM.PacketFlag.Sync && length > 12) { + this._parseCommand(message.slice(12)); + this.event.emit('stateChanged', null, this.state); + } + if (flags & ATEM.PacketFlag.Sync && length === 12 && this.connectionState === ATEM.ConnectionState.SynSent) { + this.connectionState = ATEM.ConnectionState.Established; + this.event.emit('connect', null, null); + } + if (flags & ATEM.PacketFlag.Sync && this.connectionState === ATEM.ConnectionState.Established) { + this._sendAck(); + return this.event.emit('ping', null, null); + } + }; + + ATEM.prototype._parseCommand = function(buffer) { + var key, length, name, value, _ref, _results; + length = this._parseNumber(buffer.slice(0, 2)); + name = this._parseString(buffer.slice(4, 8)); + if (DEBUG) { + console.log('COMMAND', "" + name + "(" + length + ")", buffer.slice(0, length)); + } + this._setStatus(name, buffer.slice(0, length).slice(8)); + if (buffer.length > length) { + this._parseCommand(buffer.slice(length)); + } + if (this.forceOldStyle) { + _ref = this.state.video.ME[0]; + _results = []; + for (key in _ref) { + value = _ref[key]; + _results.push(this.state.video[key] = value); + } + return _results; + } + }; + + ATEM.prototype._setStatus = function(name, buffer) { + var aux, channel, channelMappings, i, leftGain, me, numberOfChannels, offset, rightGain, _i, _j, _k, _l, _m, _ref, _ref1, _results, _results1, _results2; + this.commandEvent.emit(name, null, buffer); + switch (name) { + case '_ver': + this.state._ver0 = buffer[1]; + return this.state._ver1 = buffer[3]; + case '_pin': + this.state._pin = this._parseString(buffer); + return this.state.model = buffer[40]; + case '_top': + this.state.topology.numberOfMEs = buffer[0]; + this.state.topology.numberOfSources = buffer[1]; + this.state.topology.numberOfColorGenerators = buffer[2]; + this.state.topology.numberOfAUXs = buffer[3]; + this.state.topology.numberOfDownstreamKeys = buffer[4]; + this.state.topology.numberOfStingers = buffer[5]; + this.state.topology.numberOfDVEs = buffer[6]; + this.state.topology.numberOfSuperSources = buffer[7]; + _results = []; + for (me = _i = 0, _ref = this.state.topology.numberOfMEs; 0 <= _ref ? _i < _ref : _i > _ref; me = 0 <= _ref ? ++_i : --_i) { + _results.push(this.state.video.ME[me] = { + upstreamKeyState: [], + upstreamKeyNextState: [] + }); + } + return _results; + break; + case '_MeC': + me = buffer[0]; + return this.state.video.ME[me].numberOfKeyers = buffer[1]; + case 'InPr': + channel = this._parseNumber(buffer.slice(0, 2)); + return this.state.channels[channel] = { + name: this._parseString(buffer.slice(2, 22)), + label: this._parseString(buffer.slice(22, 26)) + }; + case 'PrgI': + me = buffer[0]; + return this.state.video.ME[me].programInput = this._parseNumber(buffer.slice(2, 4)); + case 'PrvI': + me = buffer[0]; + return this.state.video.ME[me].previewInput = this._parseNumber(buffer.slice(2, 4)); + case 'TrPr': + me = buffer[0]; + return this.state.video.ME[me].transitionPreview = buffer[1] > 0 ? true : false; + case 'TrPs': + me = buffer[0]; + this.state.video.ME[me].transitionPosition = this._parseNumber(buffer.slice(4, 6)) / 10000; + return this.state.video.ME[me].transitionFrameCount = buffer[2]; + case 'FtbS': + me = buffer[0]; + return this.state.video.ME[me].fadeToBlack = buffer[1] > 0 ? true : false; + case 'TrSS': + me = buffer[0]; + this.state.video.ME[me].transitionStyle = buffer[1]; + this.state.video.ME[me].upstreamKeyNextBackground = (buffer[2] >> 0 & 1) === 0x01; + _results1 = []; + for (i = _j = 0, _ref1 = this.state.video.ME[me].numberOfKeyers; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { + _results1.push(this.state.video.ME[me].upstreamKeyNextState[i] = (buffer[2] >> (i + 1) & 1) === 0x01); + } + return _results1; + break; + case 'KeOn': + me = buffer[0]; + return this.state.video.ME[me].upstreamKeyState[buffer[1]] = buffer[2] === 1 ? true : false; + case 'DskS': + return this.state.video.downstreamKeyOn[buffer[0]] = buffer[1] === 1 ? true : false; + case 'DskP': + return this.state.video.downstreamKeyTie[buffer[0]] = buffer[1] === 1 ? true : false; + case 'TlIn': + return this.state.tallys = this._bufferToArray(buffer.slice(2)); + case 'AuxS': + aux = buffer[0]; + return this.state.video.auxs[aux] = this._parseNumber(buffer.slice(2, 4)); + case '_AMC': + this.state.audio.numberOfChannels = this._parseNumber(buffer[0]); + return this.state.audio.hasMonitor = buffer[1] === 1; + case 'AMIP': + channel = this._parseNumber(buffer.slice(0, 2)); + return this.state.audio.channels[channel] = { + on: buffer[8] === 1 ? true : false, + afv: buffer[8] === 2 ? true : false, + gain: this._parseNumber(buffer.slice(10, 12)) / AUDIO_GAIN_RATE, + rawGain: this._parseNumber(buffer.slice(10, 12)), + rawPan: this._parseNumber(buffer.slice(12, 14)) + }; + case 'AMMO': + return this.state.audio.master = { + afv: buffer[4] === 1 ? true : false, + gain: this._parseNumber(buffer.slice(0, 2)) / AUDIO_GAIN_RATE, + rawGain: this._parseNumber(buffer.slice(0, 2)) + }; + case 'Time': + buffer.slice() + return this.state.video.timecode = { + hours: buffer[0], + minutes: buffer[1], + seconds: buffer[2], + frames: buffer[3] + } + case 'VidM': + buffer.slice() + return this.state.video.videomode = buffer[0] + case 'AMLv': + numberOfChannels = this._parseNumber(buffer.slice(0, 2)); + channelMappings = []; + offset = 4; + for (i = _k = 0; _k <= 1; i = ++_k) { + leftGain = this._parseNumber(buffer.slice(offset + 1, +(offset + 3) + 1 || 9e9)); + rightGain = this._parseNumber(buffer.slice(offset + 5, +(offset + 7) + 1 || 9e9)); + this._merge(this.state.audio.master, { + leftLevel: leftGain / 8388607, + rightLevel: rightGain / 8388607 + }); + offset += 16; + } + for (i = _l = 0; 0 <= numberOfChannels ? _l < numberOfChannels : _l > numberOfChannels; i = 0 <= numberOfChannels ? ++_l : --_l) { + channelMappings.push(buffer[offset] << 8 | buffer[offset + 1]); + offset += 2; + } + _results2 = []; + for (i = _m = 0; 0 <= numberOfChannels ? _m < numberOfChannels : _m > numberOfChannels; i = 0 <= numberOfChannels ? ++_m : --_m) { + leftGain = this._parseNumber(buffer.slice(offset + 1, +(offset + 3) + 1 || 9e9)); + rightGain = this._parseNumber(buffer.slice(offset + 5, +(offset + 7) + 1 || 9e9)); + this._merge(this.state.audio.channels[channelMappings[i]], { + leftLevel: leftGain / 8388607, + rightLevel: rightGain / 8388607 + }); + _results2.push(offset += 16); + } + return _results2; + } + }; + + ATEM.prototype._parseNumber = function(bytes) { + var byte, i, num, _i, _len; + num = 0; + for (i = _i = 0, _len = bytes.length; _i < _len; i = ++_i) { + byte = bytes[i]; + num += byte; + if (i < bytes.length - 1) { + num = num << 8; + } + } + return num; + }; + + ATEM.prototype._parseString = function(bytes) { + var char, str, _i, _len; + str = ''; + for (_i = 0, _len = bytes.length; _i < _len; _i++) { + char = bytes[_i]; + if (char === 0) { + break; + } + str += String.fromCharCode(char); + } + return str; + }; + + ATEM.prototype._bufferToArray = function(buffers) { + var arr, buffer, _i, _len; + arr = []; + for (_i = 0, _len = buffers.length; _i < _len; _i++) { + buffer = buffers[_i]; + arr.push(buffer); + } + return arr; + }; + + ATEM.prototype._numberToBytes = function(number, numberOfBytes) { + var bytes, i, shift, _i; + bytes = []; + for (i = _i = 0; 0 <= numberOfBytes ? _i < numberOfBytes : _i > numberOfBytes; i = 0 <= numberOfBytes ? ++_i : --_i) { + shift = numberOfBytes - i - 1; + bytes.push((number >> (8 * shift)) & 0xFF); + } + return bytes; + }; + + ATEM.prototype._stringToBytes = function(str) { + var bytes, i, _i, _ref; + bytes = []; + for (i = _i = 0, _ref = str.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + bytes.push(str.charCodeAt(i)); + } + return bytes; + }; + + ATEM.prototype._merge = function(obj1, obj2) { + var key2, _results; + if (obj2 == null) { + obj2 = {}; + } + _results = []; + for (key2 in obj2) { + if (obj2.hasOwnProperty(key2)) { + _results.push(obj1[key2] = obj2[key2]); + } else { + _results.push(void 0); + } + } + return _results; + }; + + ATEM.prototype.changeProgramInput = function(input, me) { + if (me == null) { + me = 0; + } + return this._sendCommand('CPgI', [me, 0x00, input >> 8, input & 0xFF]); + }; + + ATEM.prototype.changePreviewInput = function(input, me) { + if (me == null) { + me = 0; + } + return this._sendCommand('CPvI', [me, 0x00, input >> 8, input & 0xFF]); + }; + + ATEM.prototype.fadeToBlack = function(me) { + if (me == null) { + me = 0; + } + return this._sendCommand('FtbA', [me, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.autoTransition = function(me) { + if (me == null) { + me = 0; + } + return this._sendCommand('DAut', [me, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.cutTransition = function(me) { + if (me == null) { + me = 0; + } + return this._sendCommand('DCut', [me, 0xef, 0xbf, 0x5f]); + }; + + ATEM.prototype.changeTransitionPosition = function(position, me) { + if (me == null) { + me = 0; + } + this._sendCommand('CTPs', [me, 0x00, position / 256, position % 256]); + if (position >= 10000) { + return this._sendCommand('CTPs', [me, 0x00, 0x00, 0x00]); + } + }; + + ATEM.prototype.changeTransitionPreview = function(state, me) { + if (me == null) { + me = 0; + } + return this._sendCommand('CTPr', [me, state, 0x00, 0x00]); + }; + + ATEM.prototype.changeTransitionType = function(type, me) { + if (me == null) { + me = 0; + } + return this._sendCommand('CTTp', [0x01, me, type, 0x00]); + }; + + ATEM.prototype.changeUpstreamKeyState = function(number, state, me) { + if (me == null) { + me = 0; + } + return this._sendCommand('CKOn', [me, number, state, 0x00]); + }; + + ATEM.prototype.changeUpstreamKeyNextBackground = function(state, me) { + var i, stateBit, _i, _ref; + if (me == null) { + me = 0; + } + this.state.video.ME[me].upstreamKeyNextBackground = state; + stateBit = this.state.video.ME[me].upstreamKeyNextBackground; + for (i = _i = 0, _ref = this.state.video.ME[me].numberOfKeyers; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + stateBit += this.state.video.ME[me].upstreamKeyNextState[i] << (i + 1); + } + return this._sendCommand('CTTp', [0x02, me, 0x00, stateBit]); + }; + + ATEM.prototype.changeUpstreamKeyNextState = function(number, state, me) { + var i, stateBit, _i, _ref; + if (me == null) { + me = 0; + } + this.state.video.ME[me].upstreamKeyNextState[number] = state; + stateBit = this.state.video.ME[me].upstreamKeyNextBackground; + for (i = _i = 0, _ref = this.state.video.ME[me].numberOfKeyers; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + stateBit += this.state.video.ME[me].upstreamKeyNextState[i] << (i + 1); + } + return this._sendCommand('CTTp', [0x02, me, 0x00, stateBit]); + }; + + ATEM.prototype.changeAuxInput = function(aux, input) { + return this._sendCommand('CAuS', [0x01, aux, input >> 8, input & 0xFF]); + }; + + ATEM.prototype.changeDownstreamKeyOn = function(number, state) { + return this._sendCommand('CDsL', [number, state, 0xff, 0xff]); + }; + + ATEM.prototype.changeDownstreamKeyTie = function(number, state) { + return this._sendCommand('CDsT', [number, state, 0xff, 0xff]); + }; + + ATEM.prototype.autoDownstreamKey = function(number) { + return this._sendCommand('DDsA', [number, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.changeAudioMasterGain = function(gain) { + gain = gain * AUDIO_GAIN_RATE; + return this._sendCommand('CAMM', [0x01, 0x00, gain / 256, gain % 256, 0x00, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.changeAudioChannelGain = function(channel, gain) { + gain = gain * AUDIO_GAIN_RATE; + return this._sendCommand('CAMI', [0x02, 0x00, channel / 256, channel % 256, 0x00, 0x00, gain / 256, gain % 256, 0x00, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.changeAudioChannelState = function(channel, status) { + return this._sendCommand('CAMI', [0x01, 0x00, channel >> 8, channel & 0xFF, status, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.sendAudioLevelNumber = function(enable) { + if (enable == null) { + enable = true; + } + return this._sendCommand('SALN', [enable, 0x00, 0x00, 0x00]); + }; + + ATEM.prototype.startRecordMacro = function(number, name, description) { + var bytes, descriptionLength, nameLength; + nameLength = (name != null ? name.length : void 0) || 0; + descriptionLength = (description != null ? description.length : void 0) || 0; + bytes = [0x00, number]; + bytes = bytes.concat(this._numberToBytes(nameLength, 2)); + bytes = bytes.concat(this._numberToBytes(descriptionLength, 2)); + if (nameLength > 0) { + bytes = bytes.concat(this._stringToBytes(name)); + } + if (descriptionLength > 0) { + bytes = bytes.concat(this._stringToBytes(description)); + } + return this._sendCommand('MSRc', bytes); + }; + + ATEM.prototype.stopRecordMacro = function() { + return this._sendCommand('MAct', [0xFF, 0xFF, 0x02, 0x81]); + }; + + ATEM.prototype.runMacro = function(number) { + return this._sendCommand('MAct', [0x00, number, 0x00, 0x7d]); + }; + + ATEM.prototype.deleteMacro = function(number) { + return this._sendCommand('MAct', [0x00, number, 0x05, 0x00]); + }; + + ATEM.prototype.lockMediaPool = function(bankIndex, frameIndex) { + var payload; + payload = [bankIndex / 256, bankIndex % 256, frameIndex / 256, frameIndex % 256, 0x00, 0x01, 0x00, 0x46]; + return this._sendCommand('PLCK', payload); + }; + + ATEM.prototype.unlockMediaPool = function(bankIndex) { + var payload; + payload = [bankIndex / 256, bankIndex % 256, 0x00, 0xbf]; + return this._sendCommand('LOCK', payload); + }; + + ATEM.prototype.fileSendNotice = function(id, bankIndex, frameIndex, size, mode) { + var payload; + if (mode == null) { + mode = 1; + } + payload = []; + payload[0] = id[0]; + payload[1] = id[1]; + payload[2] = bankIndex / 256; + payload[3] = bankIndex % 256; + payload[4] = 0; + payload[5] = 0; + payload[6] = frameIndex / 256; + payload[7] = frameIndex % 256; + payload = payload.concat(this._numberToBytes(size, 4)); + payload[12] = 0; + payload[13] = mode; + payload[14] = 0; + payload[15] = 0; + return this._sendCommand('FTSD', payload); + }; + + ATEM.prototype.sendFileData = function(id, buffer) { + var payload; + payload = new Buffer(buffer.length + 4); + payload[0] = id[0]; + payload[1] = id[1]; + payload[2] = buffer.length / 256; + payload[3] = buffer.length % 256; + buffer.copy(payload, 4); + return this._sendCommand('FTDa', payload); + }; + + ATEM.prototype.sendFileDescription = function(id, name, hash) { + var payload; + payload = new Buffer(212); + payload.fill(0); + payload[0] = id[0]; + payload[1] = id[1]; + payload.write(name, 2, 192, 'ascii'); + hash.copy(payload, 194); + return this._sendCommand('FTFD', payload); + }; + + return ATEM; + +})(); + +FileUploader = (function() { + FileUploader.lastId = null; + + FileUploader.prototype.chunkBufferOffset = 0; + + function FileUploader(atem) { + this.atem = atem; + if (!this.atem) { + console.log('Must set atem'); + } + } + + FileUploader.prototype.uploadFromPNGFile = function(file, bankIndex, frameIndex) { + return this.uploadFromPNGBuffer(fs.readFileSync(file)); + }; + + FileUploader.prototype.uploadFromPNGBuffer = function(pngBuffer, bankIndex, frameIndex) { + if (bankIndex == null) { + bankIndex = 0; + } + if (frameIndex == null) { + frameIndex = 0; + } + return new PNG({ + filterType: 4 + }).parse(pngBuffer, (function(_this) { + return function(err, parsed) { + var hashObject; + if (err != null) { + console.log('PNG Parse Error!', err); + return; + } + if (_this.chunkBufferOffset !== 0) { + console.log('Already Used Instance!'); + return; + } + while ((_this.id == null) || FileUploader.lastId === _this.id) { + _this.id = crypto.randomBytes(2); + } + _this.lastId = _this.id; + _this.width = parsed.width; + _this.height = parsed.height; + _this.buffer = _this.convertPNGToYUV422(parsed.width, parsed.height, parsed.data); + hashObject = crypto.createHash('md5'); + hashObject.update(_this.buffer); + _this.hash = hashObject.digest(); + _this.atem.commandEvent.once('LKST', function(err, payload) { + var locked, lockedBankIndex; + if (DEBUG) { + console.log('=> lock data', payload); + } + lockedBankIndex = payload[1]; + locked = payload[2] === 1; + if (lockedBankIndex === bankIndex && locked) { + return _this.atem.fileSendNotice(_this.id, bankIndex, frameIndex, _this.buffer.length); + } + }); + _this.atem.commandEvent.once('FTCD', function(err, payload) { + if (DEBUG) { + console.log('=> send description', payload); + } + return _this.atem.sendFileDescription(_this.id, '', _this.hash); + }); + _this.atem.commandEvent.on('FTCD', function(err, payload) { + if (DEBUG) { + console.log('=> continue data', payload, _this.chunkBufferOffset, _this.chunkBufferSize, _this.chunkCount, _this.buffer.length - _this.chunkBufferOffset); + } + _this.chunkCount = payload[9]; + _this.chunkBufferSize = _this.atem._parseNumber(payload.slice(6, 8)) - 4; + if (_this.chunkIntervalId != null) { + clearInterval(_this.chunkIntervalId); + } + return _this.chunkIntervalId = setInterval(function() { + if (--_this.chunkCount === 0) { + clearInterval(_this.chunkIntervalId); + } + if (_this.chunkBufferOffset + _this.chunkBufferSize > _this.buffer.length) { + _this.atem.sendFileData(_this.id, _this.buffer.slice(_this.chunkBufferOffset, _this.buffer.length)); + clearInterval(_this.chunkIntervalId); + } else { + _this.atem.sendFileData(_this.id, _this.buffer.slice(_this.chunkBufferOffset, _this.chunkBufferOffset + _this.chunkBufferSize)); + } + return _this.chunkBufferOffset += _this.chunkBufferSize; + }, 1); + }); + _this.atem.commandEvent.once('FTDC', function(err, payload) { + if (DEBUG) { + console.log('=> data close', payload); + } + return _this.atem.unlockMediaPool(bankIndex); + }); + _this.atem.commandEvent.once('FTDE', function(err, payload) { + if (DEBUG) { + return console.log('=> data error', payload); + } + }); + return _this.atem.lockMediaPool(bankIndex, frameIndex); + }; + })(this)); + }; + + FileUploader.prototype.convertPNGToYUV422 = function(width, height, data) { + var a1, a2, b1, b2, buffer, g1, g2, i, r1, r2, u1, v2, y1, y2; + buffer = new Buffer(width * height * 4); + i = 0; + while (i < width * height * 4) { + r1 = data[i + 0]; + g1 = data[i + 1]; + b1 = data[i + 2]; + a1 = data[i + 3] * 3.7; + r2 = data[i + 4]; + g2 = data[i + 5]; + b2 = data[i + 6]; + a2 = data[i + 7] * 3.7; + y1 = (((66 * r1 + 129 * g1 + 25 * b1 + 128) >> 8) + 16) * 4 - 1; + u1 = (((-38 * r1 - 74 * g1 + 112 * b1 + 128) >> 8) + 128) * 4 - 1; + y2 = (((66 * r2 + 129 * g2 + 25 * b2 + 128) >> 8) + 16) * 4 - 1; + v2 = (((112 * r2 - 94 * g2 - 18 * b2 + 128) >> 8) + 128) * 4 - 1; + buffer[i + 0] = a1 >> 4; + buffer[i + 1] = ((a1 & 0x0f) << 4) | (u1 >> 6); + buffer[i + 2] = ((u1 & 0x3f) << 2) | (y1 >> 8); + buffer[i + 3] = y1 & 0xff; + buffer[i + 4] = a2 >> 4; + buffer[i + 5] = ((a2 & 0x0f) << 4) | (v2 >> 6); + buffer[i + 6] = ((v2 & 0x3f) << 2) | (y2 >> 8); + buffer[i + 7] = y2 & 0xff; + i = i + 8; + } + return buffer; + }; + + return FileUploader; + +})(); + +module.exports = ATEM; + +module.exports.FileUploader = FileUploader; \ No newline at end of file From 5a680c6d270d646a8dd50101511f21ff4b6d7850 Mon Sep 17 00:00:00 2001 From: RHSebregts Date: Thu, 13 Jan 2022 17:32:18 +0100 Subject: [PATCH 3/3] init commit? --- lib/debug.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 lib/debug.js diff --git a/lib/debug.js b/lib/debug.js new file mode 100644 index 0000000..88de49e --- /dev/null +++ b/lib/debug.js @@ -0,0 +1,12 @@ +// Generated by CoffeeScript 1.7.1 +var ATEM, ATEM_ADDR, ATEM_PORT, sw; + +ATEM = require('./atem.coffee'); + +ATEM_ADDR = process.env['ATEM_ADDR'] || '172.16.0.2'; + +ATEM_PORT = process.env['ATEM_PORT'] || 9910; + +sw = new ATEM; + +sw.connect(ATEM_ADDR, ATEM_PORT);