Skip to content

Instantly share code, notes, and snippets.

@asquelt
Created January 23, 2021 22:34
Show Gist options
  • Save asquelt/4f2c99b64b0b5f105c64a282fce9eba3 to your computer and use it in GitHub Desktop.
Save asquelt/4f2c99b64b0b5f105c64a282fce9eba3 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# https://github.com/raspberry-pi-camera/atem-tally/blob/master/atem.py
import socket
import sys
import struct
import ctypes
import time
import threading
import requests
from pprint import pprint
from zeroconf import ServiceBrowser, Zeroconf
version = "1.0.0"
if len(sys.argv) > 1:
if sys.argv[1] == "-v":
print(version)
exit()
def dumpHex(buffer):
s = ''
for c in buffer:
s += hex(c) + ' '
print(s)
def dumpAscii(buffer):
s = ''
for c in buffer:
if (ord(c) >= 0x20) and (ord(c) <= 0x7F):
s += c
else:
s += '.'
print(s)
# implements communication with atem switcher
class Atem:
# size of header data
SIZE_OF_HEADER = 0x0c
# packet types
CMD_NOCOMMAND = 0x00
CMD_ACKREQUEST = 0x01
CMD_HELLOPACKET = 0x02
CMD_RESEND = 0x04
CMD_UNDEFINED = 0x08
CMD_ACK = 0x10
# labels
LABELS_VIDEOMODES = ['525i59.94NTSC', '625i50PAL', '525i59.94NTSC16:9', '625i50PAL16:9',
'720p50', '720p59.94', '1080i50', '1080i59.94',
'1080p23.98', '1080p24', '1080p25', '1080p29.97', '1080p50', '1080p59.94',
'2160p23.98', '2160p24', '2160p25', '2160p29.97']
LABELS_PORTS_EXTERNAL = {0: 'SDI', 1: 'HDMI', 2: 'Component', 3: 'Composite', 4: 'SVideo'}
LABELS_PORTS_INTERNAL = {0: 'External', 1: 'Black', 2: 'Color Bars', 3: 'Color Generator', 4: 'Media Player Fill',
5: 'Media Player Key', 6: 'SuperSource', 128: 'ME Output', 129: 'Auxilary', 130: 'Mask'}
LABELS_MULTIVIEWER_LAYOUT = ['top', 'bottom', 'left', 'right']
LABELS_AUDIO_PLUG = ['Internal', 'SDI', 'HDMI', 'Component', 'Composite', 'SVideo', 'XLR', 'AES/EBU', 'RCA']
LABELS_VIDEOSRC = {0: 'Black', 1: 'Input 1', 2: 'Input 2', 3: 'Input 3', 4: 'Input 4', 5: 'Input 5', 6: 'Input 6',
7: 'Input 7', 8: 'Input 8', 9: 'Input 9', 10: 'Input 10', 11: 'Input 11', 12: 'Input 12',
13: 'Input 13', 14: 'Input 14', 15: 'Input 15', 16: 'Input 16', 17: 'Input 17', 18: 'Input 18',
19: 'Input 19', 20: 'Input 20', 1000: 'Color Bars', 2001: 'Color 1', 2002: 'Color 2',
3010: 'Media Player 1', 3011: 'Media Player 1 Key', 3020: 'Media Player 2',
3021: 'Media Player 2 Key', 4010: 'Key 1 Mask', 4020: 'Key 2 Mask', 4030: 'Key 3 Mask',
4040: 'Key 4 Mask', 5010: 'DSK 1 Mask', 5020: 'DSK 2 Mask', 6000: 'Super Source',
7001: 'Clean Feed 1', 7002: 'Clean Feed 2', 8001: 'Auxilary 1', 8002: 'Auxilary 2',
8003: 'Auxilary 3', 8004: 'Auxilary 4', 8005: 'Auxilary 5', 8006: 'Auxilary 6',
10010: 'ME 1 Prog', 10011: 'ME 1 Prev', 10020: 'ME 2 Prog', 10021: 'ME 2 Prev'}
LABELS_AUDIOSRC = {1: 'Input 1', 2: 'Input 2', 3: 'Input 3', 4: 'Input 4', 5: 'Input 5', 6: 'Input 6', 7: 'Input 7',
8: 'Input 8', 9: 'Input 9', 10: 'Input 10', 11: 'Input 11', 12: 'Input 12', 13: 'Input 13',
14: 'Input 14', 15: 'Input 15', 16: 'Input 16', 17: 'Input 17', 18: 'Input 18', 19: 'Input 19',
20: 'Input 20', 1001: 'XLR', 1101: 'AES/EBU', 1201: 'RCA', 2001: 'MP1', 2002: 'MP2'}
# cc
LABELS_CC_DOMAIN = {0: 'lens', 1: 'camera', 8: 'chip'}
LABELS_CC_LENS_FEATURE = {0: 'focus', 1: 'auto_focused', 3: 'iris', 9: 'zoom'}
LABELS_CC_CAM_FEATURE = {1: 'gain', 2: 'white_balance', 5: 'shutter'}
LABELS_CC_CHIP_FEATURE = {0: 'lift', 1: 'gamma', 2: 'gain', 3: 'aperture', 4: 'contrast', 5: 'luminance',
6: 'hue-saturation'}
# value options
VALUES_CC_GAIN = {512: '0db', 1024: '6db', 2048: '12db', 4096: '18db'}
VALUES_CC_WB = {3200: '3200K', 4500: '4500K', 5000: '5000K', 5600: '5600K', 6500: '6500K', 7500: '7500K'}
VALUES_CC_SHUTTER = {20000: '1/50', 16667: '1/60', 13333: '1/75', 11111: '1/90', 10000: '1/100', 8333: '1/120',
6667: '1/150', 5556: '1/180', 4000: '1/250', 2778: '1/360', 2000: '1/500', 1379: '1/725',
1000: '1/1000', 690: '1/1450', 500: '1/2000'}
VALUES_AUDIO_MIX = {0: 'off', 1: 'on', 2: 'AFV'}
# initializes the class
def __init__(self, address):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.setblocking(0)
self.socket.bind(('0.0.0.0', 9910))
self.address = (address, 9910)
self.packetCounter = 0
self.isInitialized = False
self.currentUid = 0x1337
self.system_config = {'inputs': {}, 'audio': {}}
self.status = {}
self.config = {'multiviewers': {}, 'mediapool': {}}
self.state = {
'program': {},
'preview': {},
'keyers': {},
'dskeyers': {},
'aux': {},
'mediaplayer': {},
'mediapool': {},
'audio': {},
'tally_by_index': {},
'tally': {}
}
self.cameracontrol = {}
self.state['booted'] = True
self.tallyHandler = None
self.pgmInputHandler = None
self.prvInputHandler = None
# hello packet
def connectToSwitcher(self):
datagram = self.createCommandHeader(self.CMD_HELLOPACKET, 8, self.currentUid, 0x0)
datagram += struct.pack('!I', 0x01000000)
datagram += struct.pack('!I', 0x00)
while True:
try:
# print('Connecting...')
self.sendDatagram(datagram)
break
except OSError as e:
# catch Network unreachable error
print('OSError', e.errno)
#if e.errno == 101:
time.sleep(5)
except:
# print('Connecting datagram failed')
time.sleep(5)
raise
# reads packets sent by the switcher
def handleSocketData(self):
# network is 100Mbit/s max, MTU is thus at most 1500
try:
datagram, address = self.socket.recvfrom(2048)
except socket.error:
return False
# print('received datagram')
header = self.parseCommandHeader(datagram)
if header:
self.currentUid = header['uid']
if header['bitmask'] & self.CMD_HELLOPACKET:
# print('not initialized, received HELLOPACKET, sending ACK packet')
self.isInitialized = False
ackDatagram = self.createCommandHeader(self.CMD_ACK, 0, header['uid'], 0x0)
self.sendDatagram(ackDatagram)
elif (header['bitmask'] & self.CMD_ACKREQUEST) and \
(self.isInitialized or len(datagram) == self.SIZE_OF_HEADER):
# print('initialized, received ACKREQUEST, sending ACK packet')
# print("Sending ACK for packageId %d" % header['packageId'])
ackDatagram = self.createCommandHeader(self.CMD_ACK, 0, header['uid'], header['packageId'])
self.sendDatagram(ackDatagram)
self.isInitialized = True
if len(datagram) > self.SIZE_OF_HEADER + 2 and not (header['bitmask'] & self.CMD_HELLOPACKET):
self.parsePayload(datagram)
return True
def waitForPacket(self):
# print(">>> waiting for packet")
i = 0
while not self.handleSocketData():
time.sleep(0.01)
i += 1
if i > 200:
print('2s no packet, reconnecting')
self.currentUid = 0x1337
self.connectToSwitcher()
return
# print(">>> packet obtained")
# generates packet header data
def createCommandHeader(self, bitmask, payloadSize, uid, ackId):
buffer = b''
packageId = 0
if not (bitmask & (self.CMD_HELLOPACKET | self.CMD_ACK)):
self.packetCounter += 1
packageId = self.packetCounter
val = bitmask << 11
val |= (payloadSize + self.SIZE_OF_HEADER)
buffer += struct.pack('!H', val)
buffer += struct.pack('!H', uid)
buffer += struct.pack('!H', ackId)
buffer += struct.pack('!I', 0)
buffer += struct.pack('!H', packageId)
return buffer
# parses the packet header
def parseCommandHeader(self, datagram):
header = {}
if len(datagram) >= self.SIZE_OF_HEADER:
header['bitmask'] = struct.unpack('B', datagram[0:1])[0] >> 3
header['size'] = struct.unpack('!H', datagram[0:2])[0] & 0x07FF
header['uid'] = struct.unpack('!H', datagram[2:4])[0]
header['ackId'] = struct.unpack('!H', datagram[4:6])[0]
header['packageId'] = struct.unpack('!H', datagram[10:12])[0]
# print(header)
return header
return False
def parsePayload(self, datagram):
# print('parsing payload')
# eat up header
datagram = datagram[self.SIZE_OF_HEADER:]
# handle data
while len(datagram) > 0:
size = struct.unpack('!H', datagram[0:2])[0]
packet = datagram[0:size]
datagram = datagram[size:]
# skip size and 2 unknown bytes
packet = packet[4:]
ptype = packet[:4]
payload = packet[4:]
# find the approporiate function in the class
method = 'recv' + ptype.decode("utf-8")
if hasattr(self, method):
func = getattr(self, method)
if callable(func):
# print('> calling ' + method)
func(payload)
else:
print('problem, member ' + method + ' not callable')
else:
# print('unknown type ' + ptype.decode("utf-8"))
# dumpAscii(payload)
pass
# sys.exit()
def sendCommand(self, command, payload):
print('sending command')
size = len(command) + len(payload) + 4
dg = self.createCommandHeader(self.CMD_ACKREQUEST, size, self.currentUid, 0)
dg += struct.pack('!H', size)
dg += "\x00\x00"
dg += command
dg += payload
self.sendDatagram(dg)
# sends a datagram to the switcher
def sendDatagram(self, datagram):
# print('sending packet')
# dumpHex(datagram)
try:
self.socket.sendto(datagram, self.address)
except:
print('socket.sendto failed')
def parseBitmask(self, num, labels):
states = {}
for i, label in enumerate(labels):
states[label] = bool(num & (1 << len(labels) - i - 1))
return states
def convert_cstring(self, bytes):
return ctypes.create_string_buffer(bytes).value.decode('utf-8')
# handling of subpackets
# ----------------------
def recv_ver(self, data):
major, minor = struct.unpack('!HH', data[0:4])
self.system_config['version'] = str(major) + '.' + str(minor)
def recv_pin(self, data):
self.system_config['name'] = data
def recvWarn(self, text):
print('Warning: ' + text)
def recv_top(self, data):
self.system_config['topology'] = {}
datalabels = ['mes', 'sources', 'color_generators', 'aux_busses', 'dsks', 'stingers', 'dves',
'supersources']
for i, label in enumerate(datalabels):
self.system_config['topology'][label] = data[i]
self.system_config['topology']['hasSD'] = (data[9] > 0)
def recv_MeC(self, data):
index = data[0]
self.system_config.setdefault('keyers', {})[index] = data[1]
def recv_mpl(self, data):
self.system_config['media_players'] = {}
self.system_config['media_players']['still'] = data[0]
self.system_config['media_players']['clip'] = data[1]
def recv_MvC(self, data):
self.system_config['multiviewers'] = data[0]
def recv_SSC(self, data):
self.system_config['super_source_boxes'] = data[0]
def recv_TlC(self, data):
self.system_config['tally_channels'] = data[4]
def recv_AMC(self, data):
self.system_config['audio_channels'] = data[0]
self.system_config['has_monitor'] = (data[1] > 0)
def recv_VMC(self, data):
size = 18
for i in range(size):
self.system_config.setdefault('video_modes', {})[i] = bool(data[0] & (1 << size - i - 1))
def recv_MAC(self, data):
self.system_config['macro_banks'] = data[0]
def recvPowr(self, data):
self.status['power'] = self.parseBitmask(data[0], ['main', 'backup'])
def recvDcOt(self, data):
self.config['down_converter'] = data[0]
def recvVidM(self, data):
self.config['video_mode'] = data[0]
def recvInPr(self, data):
index = struct.unpack('!H', data[0:2])[0]
self.system_config['inputs'][index] = {}
input_setting = self.system_config['inputs'][index]
input_setting['name_long'] = self.convert_cstring(data[2:22])
input_setting['name_short'] = self.convert_cstring(data[22:26])
input_setting['types_available'] = self.parseBitmask(data[27], self.LABELS_PORTS_EXTERNAL)
input_setting['port_type_external'] = data[29]
input_setting['port_type_internal'] = data[30]
input_setting['availability'] = self.parseBitmask(data[32], ['Auxilary', 'Multiviewer', 'SuperSourceArt',
'SuperSourceBox', 'KeySource'])
input_setting['me_availability'] = self.parseBitmask(data[33], ['ME1', 'ME2'])
def recvMvPr(self, data):
index = data[0]
self.config['multiviewers'].setdefault(index, {})['layout'] = data[1]
def recvMvIn(self, data):
index = data[0]
window = data[1]
self.config['multiviewers'].setdefault(index, {}).setdefault('windows', {})[window] = \
struct.unpack('!H', data[2:4])[0]
def recvPrgI(self, data):
meIndex = data[0]
self.state['program'][meIndex] = struct.unpack('!H', data[2:4])[0]
if self.pgmInputHandler is not None:
self.pgmInputHandler(self)
def recvPrvI(self, data):
meIndex = data[0]
self.state['preview'][meIndex] = struct.unpack('!H', data[2:4])[0]
if self.prvInputHandler is not None:
self.prvInputHandler(self)
def recvKeOn(self, data):
meIndex = data[0]
keyer = data[1]
self.state['keyers'].setdefault(meIndex, {})[keyer] = (data[2] != 0)
def recvDskB(self, data):
keyer = data[0]
keyer_setting = self.state['dskeyers'].setdefault(keyer, {})
keyer_setting['fill'] = struct.unpack('!H', data[2:4])[0]
keyer_setting['key'] = struct.unpack('!H', data[4:6])[0]
def recvDskS(self, data):
keyer = data[0]
dsk_setting = self.state['dskeyers'].setdefault(keyer, {})
dsk_setting['onAir'] = (data[1] != 0)
dsk_setting['inTransition'] = (data[2] != 0)
dsk_setting['autoTransitioning'] = (data[3] != 0)
dsk_setting['framesRemaining'] = data[4]
def recvAuxS(self, data):
auxIndex = data[0]
self.state['aux'][auxIndex] = struct.unpack('!H', data[2:4])[0]
def recvCCdo(self, data):
input_num = data[1]
domain = data[2]
feature = data[3]
feature_label = feature
try:
if domain == 0:
feature_label = self.LABELS_CC_LENS_FEATURE[feature]
elif domain == 1:
feature_label = self.LABELS_CC_CAM_FEATURE[feature]
elif domain == 8:
feature_label = self.LABELS_CC_CHIP_FEATURE[feature]
self.cameracontrol.setdefault(input_num, {}).setdefault('features', {}).setdefault(
self.LABELS_CC_DOMAIN[domain], {})[feature_label] = bool(data[4])
except KeyError:
print("Warning: CC Feature not recognized (no label)")
def recvCCdP(self, data):
input_num = data[1]
domain = data[2]
feature = data[3]
feature_label = feature
val = None
val_translated = None
if domain == 0: # lens
if feature == 0: # focus
val = val_translated = struct.unpack('!h', data[16:18])[0]
elif feature == 1: # auto focused
pass
elif feature == 3: # iris
val = val_translated = struct.unpack('!h', data[16:18])[0]
elif feature == 9: # zoom
val = val_translated = struct.unpack('!h', data[16:18])[0]
elif domain == 1: # camera
if feature == 1: # gain
val = struct.unpack('!h', data[16:18])[0]
val_translated = self.VALUES_CC_GAIN.get(val, 'unknown')
elif feature == 2: # white balance
val = struct.unpack('!h', data[16:18])[0]
val_translated = self.VALUES_CC_WB.get(val, val + 'K')
elif feature == 5: # shutter
val = struct.unpack('!h', data[18:20])[0]
val_translated = self.VALUES_CC_SHUTTER.get(val, 'off')
elif domain == 8: # chip
val_keys_color = ['R', 'G', 'B', 'Y']
if feature == 0: # lift
val = dict(zip(val_keys_color, struct.unpack('!hhhh', data[16:24])))
val_translated = {k: float(v) / 4096 for k, v in val.items()}
elif feature == 1: # gamma
val = dict(zip(val_keys_color, struct.unpack('!hhhh', data[16:24])))
val_translated = {k: float(v) / 8192 for k, v in val.items()}
elif feature == 2: # gain
val = dict(zip(val_keys_color, struct.unpack('!hhhh', data[16:24])))
val_translated = {k: float(v) * 16 / 32767 for k, v in val.items()}
elif feature == 3: # aperture
pass # no idea - todo
elif feature == 4: # contrast
val = struct.unpack('!h', data[18:20])[0]
val_translated = float(val) / 4096
elif feature == 5: # luminance
val = struct.unpack('!h', data[16:18])[0]
val_translated = float(val) / 2048
elif feature == 6: # hue-saturation
val_keys = ['hue', 'saturation']
val = dict(zip(val_keys, struct.unpack('!hh', data[16:20])))
val_translated = {}
val_translated['hue'] = float(val['hue']) * 360 / 2048 + 180
val_translated['saturation'] = float(val['saturation']) / 4096
try:
if domain == 0:
feature_label = self.LABELS_CC_LENS_FEATURE[feature]
elif domain == 1:
feature_label = self.LABELS_CC_CAM_FEATURE[feature]
elif domain == 8:
feature_label = self.LABELS_CC_CHIP_FEATURE[feature]
self.cameracontrol.setdefault(input_num, {}).setdefault('state_raw', {}).setdefault(
self.LABELS_CC_DOMAIN[domain], {})[feature_label] = val
self.cameracontrol.setdefault(input_num, {}).setdefault('state', {}).setdefault(
self.LABELS_CC_DOMAIN[domain], {})[feature_label] = val_translated
except KeyError:
# print("Warning: CC Feature not recognized (no label)")
pass
def recvRCPS(self, data):
player_num = data[0]
player = self.state['mediaplayer'].setdefault(player_num, {})
player['playing'] = bool(data[1])
player['loop'] = bool(data[2])
player['beginning'] = bool(data[3])
player['clip_frame'] = struct.unpack('!H', data[4:6])[0]
def recvMPCE(self, data):
player_num = data[0]
player = self.state['mediaplayer'].setdefault(player_num, {})
player['type'] = {1: 'still', 2: 'clip'}.get(data[1])
player['still_index'] = data[2]
player['clip_index'] = data[3]
def recvMPSp(self, data):
self.config['mediapool'].setdefault(0, {})['maxlength'] = struct.unpack('!H', data[0:2])[0]
self.config['mediapool'].setdefault(1, {})['maxlength'] = struct.unpack('!H', data[2:4])[0]
def recvMPCS(self, data):
bank = data[0]
clip_bank = self.state['mediapool'].setdefault('clips', {}).setdefault(bank, {})
clip_bank['used'] = bool(data[1])
clip_bank['filename'] = self.convert_cstring(data[2:18])
clip_bank['length'] = struct.unpack('!H', data[66:68])[0]
def recvMPAS(self, data):
bank = data[0]
audio_bank = self.state['mediapool'].setdefault('audio', {}).setdefault(bank, {})
audio_bank['used'] = bool(data[1])
audio_bank['filename'] = self.convert_cstring(data[18:34])
def recvMPfe(self, data):
if data[0] != 0:
return
bank = data[3]
still_bank = self.state['mediapool'].setdefault('stills', {}).setdefault(bank, {})
still_bank['used'] = bool(data[4])
still_bank['hash'] = data[5:21]#.decode("utf-8")
filename_length = data[23]
still_bank['filename'] = data[24:(24 + filename_length)].decode("utf-8")
def recvAMIP(self, data):
channel = struct.unpack('!H', data[0:2])[0]
channel_config = self.system_config['audio'].setdefault(channel, {})
channel_config['fromMediaPlayer'] = bool(data[6])
channel_config['plug'] = data[7]
channel_state = self.state['audio'].setdefault(channel, {})
channel_state['mix_option'] = data[8]
channel_state['volume'] = struct.unpack('!H', data[10:12])[0]
channel_state['balance'] = struct.unpack('!h', data[12:14])[0]
def recvAMMO(self, data):
self.state['audio']['master_volume'] = struct.unpack('!H', data[0:2])[0]
def recvAMmO(self, data):
monitor = self.state['audio'].setdefault('monitor', {})
monitor['enabled'] = bool(data[0])
monitor['volume'] = struct.unpack('!H', data[2:4])[0]
monitor['mute'] = bool(data[4])
monitor['solo'] = bool(data[5])
monitor['solo_input'] = struct.unpack('!H', data[6:8])[0]
monitor['dim'] = bool(data[8])
def recvAMTl(self, data):
src_count = struct.unpack('!H', data[0:2])[0]
for i in range(src_count):
num = 2 + i * 3
channel = struct.unpack('!H', data[num:num + 2])[0]
self.state['audio'].setdefault('tally', {})[channel] = bool(data[num + 2])
def recvTlIn(self, data):
src_count = struct.unpack('!H', data[0:2])[0]
for i in range(2, src_count + 2):
self.state['tally_by_index'][str(i - 1)] = self.parseBitmask(data[i], ['prv', 'pgm'])
self.tallyHandler(self)
def recvTlSr(self, data):
src_count = struct.unpack('!H', data[0:2])[0]
for i in range(2, min(src_count * 3 + 2, len(data)-2)):
source = struct.unpack('!H', data[i:i + 2])[0]
self.state['tally'][source] = self.parseBitmask(data[i + 2], ['prv', 'pgm'])
self.tallyHandler(self)
def recvTime(self, data):
self.state['last_state_change'] = struct.unpack('!BBBB', data[0:4])
def recvAMPP(self, data):
pass
def recvColV(self, data):
pass
def recvMPrp(self, data):
pass
def recvMvVM(self, data):
pass
def recvRXCC(self, data):
pass
def recvRXCP(self, data):
pass
def recvRXCE(self, data):
pass
def recvRXMS(self, data):
pass
def recvRXSS(self, data):
pass
def recvTrSS(self, data):
pass
def recvTrPr(self, data):
pass
def recvTrPs(self, data):
pass
def recvTMxP(self, data):
pass
def recvTDdP(self, data):
"""Transition Dip"""
pass
def recvTWpP(self, data):
"""Transition Wipe"""
pass
def recvLKST(self, data):
pass
## user functions
# used to register a function that should be called when a change is received from the atem
def handleAtemChange(self, func, method=''):
pass # todo: if given, filter by method parameter, then call func with atem method
# used to register a function that should be called when a change is set in an attribute
def handleStateChange(self, func, name=[]):
pass # todo: if given, filer by name path, then call func with name path and val
# used to get ranges and options lists for attributes
def getOption(self, name):
return # todo: return range/list of options for the given attribute name path
def dump(self):
pprint(self.system_config)
pprint(self.status)
pprint(self.state)
pprint(self.cameracontrol)
class MyListener:
def add_service(self, zeroconf, type, name):
info = zeroconf.get_service_info(type, name)
self.info = info
self.is_invalid = False
is_invalid = True
info = None
def fire_request_task(url,is_it_real):
try:
requests.get(url)
except:
True
def fire_and_forget(url):
threading.Thread(target=fire_request_task, args=(url,False)).start()
if __name__ == "__main__":
zeroconf = Zeroconf()
listener = MyListener()
browser = ServiceBrowser(zeroconf, "_blackmagic._tcp.local.", listener)
while listener.is_invalid:
time.sleep(0.5)
hexaddress = listener.info.addresses[0]
print("Atem found on {}.{}.{}.{}".format(hexaddress[0], hexaddress[1],hexaddress[2],hexaddress[3]))
a = Atem("{}.{}.{}.{}".format(hexaddress[0], hexaddress[1],hexaddress[2],hexaddress[3]))
prevtally = {}
def tallyWatch(atem):
async_list = []
global prevtally
if prevtally == atem.state['tally_by_index']:
#print(prevtally)
#print(atem.state['tally_by_index'])
#print("No change")
return
else:
prevtally = dict(atem.state['tally_by_index'])
#print(atem.state['tally_by_index'])
for camnum in range(1,5):
if camnum == 1:
camip = "192.168.111.242"
elif camnum == 2:
camip = "192.168.111.243"
elif camnum == 3:
camip = "192.168.111.244"
elif camnum == 4:
camip = "192.168.111.245"
else:
camip = "127.0.0.1"
try:
tally = atem.state['tally_by_index'][str(camnum)]
nonacturl = "http://%s/win&T=0"
#nonacturl = "http://%s/win&T=1&FP=0&A=15&R=0&G=0&B=255&FX=0&SX=1"
if tally['pgm']:
url = "http://%s/win&T=1&FP=0&A=50&R=255&G=0&B=0&FX=0&SX=100" % camip
camstate = 'LIVE'
elif tally['prv']:
url = "http://%s/win&T=1&FP=0&A=50&R=0&G=255&B=0&FX=0&SX=100" % camip
camstate = 'PREV'
else:
url = nonacturl % camip
camstate = 'NONE'
except:
url = nonacturl % camip
camstate = 'UNKN'
#print("%s %d %s" % (camstate,camnum,url))
fire_and_forget(url)
#print("---")
a.tallyHandler = tallyWatch
a.connectToSwitcher()
while True:
a.waitForPacket()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment