diff --git a/src/atem.ts b/src/atem.ts index c4fb9c500..2318d253c 100644 --- a/src/atem.ts +++ b/src/atem.ts @@ -4,6 +4,7 @@ import { AtemSocket } from './lib/atemSocket' import { IPCMessageType, MacroAction } from './enums' import AbstractCommand from './commands/AbstractCommand' import * as Commands from './commands' +import * as DataTransferCommands from './commands/DataTransfer' import { MediaPlayer } from './state/media' import { DipTransitionSettings, @@ -18,6 +19,8 @@ import { import * as USK from './state/video/upstreamKeyers' import { InputChannel } from './state/input' import { DownstreamKeyerGeneral, DownstreamKeyerMask } from './state/video/downstreamKeyers' +import * as DT from './dataTransfer' +import { Util } from './lib/atemUtil' export interface AtemOptions { address?: string, @@ -36,6 +39,7 @@ export class Atem extends EventEmitter { event: EventEmitter state: AtemState private socket: AtemSocket + private dataTransferManager: DT.DataTransferManager private _log: (...args: any[]) => void private _sentQueue: {[packetId: string]: AbstractCommand } = {} @@ -55,6 +59,9 @@ export class Atem extends EventEmitter { address: (options || {}).address, port: (options || {}).port }) + this.dataTransferManager = new DT.DataTransferManager( + (command: AbstractCommand) => this.sendCommand(command) + ) this.socket.on('receivedStateChange', (command: AbstractCommand) => this._mutateState(command)) this.socket.on(IPCMessageType.CommandAcknowledged, ({trackingId}: {trackingId: number}) => this._resolveCommand(trackingId)) this.socket.on('error', (e) => this.emit('error', e)) @@ -332,11 +339,26 @@ export class Atem extends EventEmitter { return this.sendCommand(command) } + uploadMedia (props: DT.DataTransferProperties) { + const resolution = Util.getResolution(this.state.settings.videoMode) + return this.dataTransferManager.newTransfer( + props.type, + props.pool, + { name: props.name, description: props.description }, + Util.convertPNGToYUV422(resolution[0], resolution[1], props.data) + ) + } + private _mutateState (command: AbstractCommand) { if (typeof command.applyToState === 'function') { command.applyToState(this.state) this.emit('stateChanged', this.state, command) } + for (const commandName in DataTransferCommands) { + if (command.constructor.name === commandName) { + this.dataTransferManager.processAtemCommand(command) + } + } } private _resolveCommand (trackingId: number) { diff --git a/src/dataTransfer.ts b/src/dataTransfer.ts index 784c2b0f4..0498dcf92 100644 --- a/src/dataTransfer.ts +++ b/src/dataTransfer.ts @@ -3,17 +3,26 @@ import AbstractCommand from './commands/AbstractCommand' const TIMEOUT = 5000 +export interface DataTransferProperties { + type: Enums.StoragePool + pool: number, + name: string + description: string + data: Buffer +} + export class DataTransferManager { - private transfers: Array - private dataTransferQueue: Array - private sendCommand: (command: AbstractCommand) => void - private locks = [ false, false, false ] + transfers: Array = [] + locks = [ false, false, false ] + private _dataTransferQueue: Array = [] + private _sendCommand: (command: AbstractCommand) => void private lastTransferIndex = 0 - constructor () { + constructor (sendCommand: (command: AbstractCommand) => void) { + this._sendCommand = sendCommand setInterval(() => { - if (this.dataTransferQueue.length > 0) { - this.sendCommand(this.dataTransferQueue.shift()!) + if (this._dataTransferQueue.length > 0) { + this._sendCommand(this._dataTransferQueue.shift()!) } const now = Date.now() for (const transfer of this.transfers) { @@ -32,7 +41,7 @@ export class DataTransferManager { index: command.properties.index, locked: false }) - this.sendCommand(command) + this._sendCommand(command) } else { this._proceedNext() } @@ -52,20 +61,25 @@ export class DataTransferManager { } newTransfer (type: Enums.StoragePool, pool: number, description: { name: string, description: string }, data: Buffer) { - this.transfers.push(new DataTransfer( + const transfer = new DataTransfer( this.lastTransferIndex++, pool, type, description, data, - this._queueCommand - )) + (command: AbstractCommand) => this._dataTransferQueue.push(command) + ) + const ps = new Promise((resolve, reject) => { + transfer.finish = resolve + transfer.fail = reject + }) + this.transfers.push(transfer) if (this.locks[type] === false) { this._startNext() } - return this.lastTransferIndex + return ps } cancelTransfer (transferId: number) { @@ -92,24 +106,20 @@ export class DataTransferManager { size: tranfer.data.length, mode: tranfer.type === Enums.StoragePool.Sounds ? Enums.TransferMode.WriteAudio : Enums.TransferMode.Write }) - this.sendCommand(command) + this._sendCommand(command) tranfer.transferring = true tranfer.lastSent = Date.now() } } } - private _queueCommand (command: AbstractCommand) { - this.dataTransferQueue.push(command) - } - private _releaseLock (type: number) { const command = new Commands.LockStateCommand() command.updateProps({ index: type, locked: false }) - this.sendCommand(command) + this._sendCommand(command) } private _getLock (type: number) { @@ -118,7 +128,7 @@ export class DataTransferManager { index: type, locked: true }) - this.sendCommand(command) + this._sendCommand(command) } private _failTransfer (transferId: number) { @@ -191,7 +201,7 @@ export class DataTransfer { if (!this._sentDesc) { const command = new Commands.DataTransferFileDescriptionCommand() - command.updateProps({...this.description, hash: this._hash}) + command.updateProps({...this.description, fileHash: this._hash}) this._queueCommand(command) } @@ -199,7 +209,7 @@ export class DataTransfer { const command = new Commands.DataTransferDataCommand() command.updateProps({ transferId: this.index, - data: this.data.slice(this._sent, this._sent + chunkSize) + body: this.data.slice(this._sent, this._sent + chunkSize) }) this._queueCommand(command) this._sent += chunkSize diff --git a/src/lib/atemUtil.ts b/src/lib/atemUtil.ts index adda6ef5e..ad780bab8 100644 --- a/src/lib/atemUtil.ts +++ b/src/lib/atemUtil.ts @@ -1,5 +1,6 @@ import { IPCMessageType } from '../enums' import * as pRetry from 'p-retry' +import { Enums } from '..' export namespace Util { export function stringToBytes (str: string): Array { @@ -90,4 +91,55 @@ export namespace Util { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00 ]) + + /** + * @todo: BALTE - 2018-5-14: + * Create util functions that handle proper colour spaces. + */ + export function convertPNGToYUV422 (width: number, height: number, data: Buffer) { + const buffer = new Buffer(width * height * 4) + let i = 0 + while (i < width * height * 4) { + const r1 = data[i + 0] + const g1 = data[i + 1] + const b1 = data[i + 2] + const a1 = data[i + 3] * 3.7 + const r2 = data[i + 4] + const g2 = data[i + 5] + const b2 = data[i + 6] + const a2 = data[i + 7] * 3.7 + const y1 = (((66 * r1 + 129 * g1 + 25 * b1 + 128) >> 8) + 16) * 4 - 1 + const u1 = (((-38 * r1 - 74 * g1 + 112 * b1 + 128) >> 8) + 128) * 4 - 1 + const y2 = (((66 * r2 + 129 * g2 + 25 * b2 + 128) >> 8) + 16) * 4 - 1 + const 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 & 0xf + 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 + } + + export function getResolution (videoMode: Enums.VideoMode) { + const PAL = [720, 576] + const NTSC = [640, 480] + const HD = [1280, 720] + const FHD = [1920, 1080] + const UHD = [3840, 2160] + + const enumToResolution = [ + NTSC, PAL, NTSC, PAL, + HD, HD, + FHD, FHD, FHD, FHD, FHD, FHD, FHD, FHD, + UHD, UHD, UHD, UHD, + UHD, UHD + ] + + return enumToResolution[videoMode] + } } diff --git a/src/state/index.ts b/src/state/index.ts index 26a858a59..53f4c460e 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -7,6 +7,9 @@ import { MacroPlayerState } from './macroPlayer' export class AtemState { info = new DeviceInfo() + settings = { + videoMode: 0 + } video: AtemVideoState = new AtemVideoState() channels: Array<{ name: string