ATEM: audio, camera settings and MIDI control watched and fixed against the restore file

atem_watch.js (run with Companion's node + its atem-connection) keeps one
connection to the switcher and compares, every second, every Fairlight
source's mix option and fader and every camera's settings with
atem-reference.xml (the 2026-10-07 14:59:55 restore file). ATEM shows
ATEM AUDIO WRONG / ATEM CAMERA WRONG; the hold and Sunday auto-fix send
back only what differs. ATEM SW MIDI ON is fixed by quitting ATEM Software
Control, switching midiEnabled off and reopening it. Live check: switcher
matches the file (0 differences, 1440 camera values).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 10:54:59 -04:00
co-authored by Claude Opus 5.5
parent b468b42889
commit 98070f31c4
7 changed files with 27211 additions and 5 deletions
+41
View File
@@ -0,0 +1,41 @@
// Read-only snapshot of the ATEM switcher's audio (Fairlight) and camera control (CCU) state,
// printed as one JSON line. Uses the atem-connection library inside Companion's ATEM module,
// run with Companion's own node. Nothing is sent that changes the switcher.
//
// node atem_state.js <switcher ip> <path to atem-connection> [seconds to collect CCU values]
const [ip, lib, secs] = process.argv.slice(2)
const { Atem } = require(lib)
const ccu = {}
const atem = new Atem({ disableMultithreaded: true })
atem.on('receivedCommands', (cmds) => {
for (const c of cmds) {
if (c.constructor && c.constructor.rawName === 'CCdP') {
const p = c.properties
const v = p.numberData && p.numberData.length ? p.numberData
: p.boolData && p.boolData.length ? p.boolData
: p.bigintData && p.bigintData.length ? p.bigintData.map(Number) : p.stringData
ccu[`${c.source}/${c.category}/${c.parameter}`] = v
}
}
})
const done = (out, code) => {
process.stdout.write(JSON.stringify(out) + '\n')
atem.disconnect().finally(() => process.exit(code))
setTimeout(() => process.exit(code), 2000)
}
setTimeout(() => done({ error: 'no connection to the switcher' }, 2), 15000)
atem.on('connected', () => {
setTimeout(() => {
const inputs = {}
const fl = (atem.state.fairlight && atem.state.fairlight.inputs) || {}
for (const [id, input] of Object.entries(fl)) {
if (!input) continue
for (const [sid, src] of Object.entries(input.sources || {})) {
if (!src || !src.properties) continue
inputs[`${id}/${sid}`] = { mixOption: src.properties.mixOption, faderGain: src.properties.faderGain }
}
}
done({ model: atem.state.info && atem.state.info.productIdentifier, fairlight: inputs, ccu }, 0)
}, (Number(secs) || 3) * 1000)
})
atem.connect(ip)