From f9386e44852abd1bfe5daabb938ad6919e9e2d9a Mon Sep 17 00:00:00 2001 From: Martijn Braam Date: Tue, 9 Nov 2021 16:52:51 +0100 Subject: [PATCH] pyatem: implement decoders for the camera data packets --- pyatem/cameracontrol.py | 488 ++++++++++++++++++++++++++++++++++++++++ pyatem/protocol.py | 9 +- 2 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 pyatem/cameracontrol.py diff --git a/pyatem/cameracontrol.py b/pyatem/cameracontrol.py new file mode 100644 index 0000000..f88e05b --- /dev/null +++ b/pyatem/cameracontrol.py @@ -0,0 +1,488 @@ +import math +import sys +import inspect + +_cache = {} + +VOID = 0 +BOOL = 0 +INT8 = 1 +INT16 = 2 +INT32 = 3 +INT64 = 4 +UTF8 = 5 +FIXED16 = 128 + + +class CameraControlData: + CATEGORY = -1 + PARAMETER = -1 + DATATYPE = -1 + DESCRIPTIONS = [""] + KEYS = [""] + + def __init__(self): + self.data = None + self.destination = None + + @classmethod + def from_data(cls, data): + """ + :type data: pyatem.field.CameraControlDataPacketField + """ + global _cache + if len(_cache) == 0: + current_module = sys.modules[__name__] + for name, dcls in inspect.getmembers(current_module): + if hasattr(dcls, 'CATEGORY') and dcls.CATEGORY > -1: + _cache[(dcls.CATEGORY, dcls.PARAMETER)] = dcls + + if (data.category, data.parameter) in _cache: + instance = _cache[(data.category, data.parameter)]() + instance.data = data.data + instance.destination = data.destination + instance.decode() + return instance + return None + + def decode(self): + pass + + def __repr__(self): + values = "" + if self.data is not None: + for i, value in enumerate(self.data): + values += f' {self.KEYS[i]}={value}{self.DESCRIPTIONS[i]}' + return f'<{self.__class__.__name__} dest={self.destination} id={self.CATEGORY}.{self.PARAMETER}{values}>' + + +class Focus(CameraControlData): + CATEGORY = 0 + PARAMETER = 0 + DATATYPE = FIXED16 + KEYS = ["distance"] + + +class ApertureFStop(CameraControlData): + CATEGORY = 0 + PARAMETER = 2 + DATATYPE = FIXED16 + KEYS = ["fnumber"] + + def decode(self): + self.data[0] = math.sqrt(2 ** self.data[0]) + + +class ApertureNormalized(CameraControlData): + CATEGORY = 0 + PARAMETER = 3 + DATATYPE = FIXED16 + KEYS = ["aperture"] + + +class ApertureOrdinal(CameraControlData): + CATEGORY = 0 + PARAMETER = 3 + DATATYPE = FIXED16 + KEYS = ["aperture"] + + +class OIS(CameraControlData): + CATEGORY = 0 + PARAMETER = 6 + DATATYPE = BOOL + KEYS = ["enabled"] + + +class AbsoluteZoom(CameraControlData): + CATEGORY = 0 + PARAMETER = 7 + DATATYPE = INT16 + KEYS = ["zoom"] + DESCRIPTIONS = ["mm"] + + +class AbsoluteZoomNormalized(CameraControlData): + CATEGORY = 0 + PARAMETER = 8 + DATATYPE = FIXED16 + KEYS = ["zoom"] + + +class ContinuousZoom(CameraControlData): + CATEGORY = 0 + PARAMETER = 9 + DATATYPE = FIXED16 + KEYS = ["rate"] + + +class VideoMode(CameraControlData): + CATEGORY = 1 + PARAMETER = 0 + DATATYPE = INT8 + KEYS = ["framerate", "mrate", "dimensions", "interlaced", "colorspace"] + DESCRIPTIONS = ["fps", "", "", "", ""] + + +class Gain(CameraControlData): + CATEGORY = 1 + PARAMETER = 1 + DATATYPE = INT8 + KEYS = ["ISO"] + + def decode(self): + self.data = [self.data[0] * 100] + + +class WhiteBalance(CameraControlData): + CATEGORY = 1 + PARAMETER = 2 + DATATYPE = INT16 + KEYS = ["temperature", "tint"] + DESCRIPTIONS = ["k", ""] + + +class Exposure(CameraControlData): + CATEGORY = 1 + PARAMETER = 5 + DATATYPE = INT32 + KEYS = ["time"] + DESCRIPTIONS = ["us"] + + +class DynamicRangeMode(CameraControlData): + CATEGORY = 1 + PARAMETER = 7 + DATATYPE = INT8 + KEYS = ["mode"] + + +class VideoSharpening(CameraControlData): + CATEGORY = 1 + PARAMETER = 8 + DATATYPE = INT8 + KEYS = ["level"] + + +class RecordingFormat(CameraControlData): + CATEGORY = 1 + PARAMETER = 9 + DATATYPE = INT16 + KEYS = ["file-fps", "sensor-fps", "width", "height", "flags"] + DESCRIPTIONS = ["", "", "", "", ""] + + +class AutoExposureMode(CameraControlData): + CATEGORY = 1 + PARAMETER = 10 + DATATYPE = INT8 + KEYS = ["mode"] + + +class ShutterAngle(CameraControlData): + CATEGORY = 1 + PARAMETER = 11 + DATATYPE = INT32 + KEYS = ["angle"] + DESCRIPTIONS = ["deg"] + + def decode(self): + self.data = [self.data[0] / 100] + + +class ShutterSpeed(CameraControlData): + CATEGORY = 1 + PARAMETER = 12 + DATATYPE = INT32 + KEYS = ["speed"] + + +class GainDB(CameraControlData): + CATEGORY = 1 + PARAMETER = 13 + DATATYPE = INT8 + KEYS = ["gain"] + DESCRIPTIONS = ["dB"] + + +class ISO(CameraControlData): + CATEGORY = 1 + PARAMETER = 14 + DATATYPE = INT32 + KEYS = ["iso"] + DESCRIPTIONS = ["ISO"] + + +class MicLevel(CameraControlData): + CATEGORY = 2 + PARAMETER = 0 + DATATYPE = FIXED16 + KEYS = ["level"] + + +class HeadphoneLevel(CameraControlData): + CATEGORY = 2 + PARAMETER = 1 + DATATYPE = FIXED16 + KEYS = ["level"] + + +class HeadphoneProgramMix(CameraControlData): + CATEGORY = 2 + PARAMETER = 2 + DATATYPE = FIXED16 + KEYS = ["mix"] + + +class SpeakerLevel(CameraControlData): + CATEGORY = 2 + PARAMETER = 3 + DATATYPE = FIXED16 + KEYS = ["level"] + + +class AudioInputType(CameraControlData): + CATEGORY = 2 + PARAMETER = 4 + DATATYPE = INT8 + KEYS = ["type"] + + +class AudioInputLevels(CameraControlData): + CATEGORY = 2 + PARAMETER = 5 + DATATYPE = FIXED16 + KEYS = ["left", "right"] + + +class PhantomPower(CameraControlData): + CATEGORY = 2 + PARAMETER = 6 + DATATYPE = BOOL + KEYS = ["enabled"] + + +class OutputOverlay(CameraControlData): + CATEGORY = 3 + PARAMETER = 0 + DATATYPE = INT16 + KEYS = "flag" + + +class OutputFrameGuideStyle(CameraControlData): + CATEGORY = 3 + PARAMETER = 1 + DATATYPE = INT8 + KEYS = "style" + + +class OutputFrameGuideOpacity(CameraControlData): + CATEGORY = 3 + PARAMETER = 2 + DATATYPE = FIXED16 + KEYS = "opacity" + + +class OutputOverlays(CameraControlData): + CATEGORY = 3 + PARAMETER = 3 + DATATYPE = INT8 + KEYS = ["style", "opacity", "safearea", "gridstyle"] + DESCRIPTIONS = ["", "%", "%", ""] + + +class DisplayBrightness(CameraControlData): + CATEGORY = 4 + PARAMETER = 0 + DATATYPE = FIXED16 + KEYS = ["brightness"] + + +class DisplayOverlay(CameraControlData): + CATEGORY = 4 + PARAMETER = 1 + DATATYPE = INT16 + KEYS = ["bitfield"] + + +class DisplayZebraLevel(CameraControlData): + CATEGORY = 4 + PARAMETER = 2 + DATATYPE = FIXED16 + KEYS = ["level"] + + +class DisplayPeakingLevel(CameraControlData): + CATEGORY = 4 + PARAMETER = 3 + DATATYPE = FIXED16 + KEYS = ["level"] + + +class DisplayColorBarsTime(CameraControlData): + CATEGORY = 4 + PARAMETER = 4 + DATATYPE = INT8 + KEYS = ["seconds"] + + +class DisplayFocusAssist(CameraControlData): + CATEGORY = 4 + PARAMETER = 5 + DATATYPE = INT8 + KEYS = ["method", "color"] + DESCRIPTIONS = ["", ""] + + +class TallyBrightness(CameraControlData): + CATEGORY = 5 + PARAMETER = 0 + DATATYPE = FIXED16 + KEYS = ["brightness"] + + +class TallyFrontBrightness(CameraControlData): + CATEGORY = 5 + PARAMETER = 1 + DATATYPE = FIXED16 + KEYS = ["brightness"] + + +class TallyRearBrightness(CameraControlData): + CATEGORY = 5 + PARAMETER = 2 + DATATYPE = FIXED16 + KEYS = ["brightness"] + + +class ReferenceSource(CameraControlData): + CATEGORY = 6 + PARAMETER = 0 + DATATYPE = INT8 + KEYS = ["source"] + + +class ReferenceOffset(CameraControlData): + CATEGORY = 6 + PARAMETER = 1 + DATATYPE = INT32 + KEYS = ["offset"] + DESCRIPTIONS = ["px"] + + +class RealtimeClock(CameraControlData): + CATEGORY = 7 + PARAMETER = 0 + DATATYPE = INT32 + KEYS = ["time", "date"] + DESCRIPTIONS = ["", " as BCD"] + + +class SystemLanguage(CameraControlData): + CATEGORY = 7 + PARAMETER = 1 + DATATYPE = UTF8 + KEYS = ["lang"] + + +class Timezone(CameraControlData): + CATEGORY = 7 + PARAMETER = 2 + DATATYPE = INT32 + KEYS = ["offset"] + DESCRIPTIONS = [" minutes"] + + +class Location(CameraControlData): + CATEGORY = 7 + PARAMETER = 3 + DATATYPE = INT64 + KEYS = ["latitude", "longitude"] + DESCRIPTIONS = ["", " as BCD"] + + +class LiftAdjust(CameraControlData): + CATEGORY = 8 + PARAMETER = 0 + DATATYPE = FIXED16 + KEYS = ["red", "green", "blue", "luma"] + DESCRIPTIONS = ["", "", "", ""] + + +class GammaAdjust(CameraControlData): + CATEGORY = 8 + PARAMETER = 1 + DATATYPE = FIXED16 + KEYS = ["red", "green", "blue", "luma"] + DESCRIPTIONS = ["", "", "", ""] + + +class GainAdjust(CameraControlData): + CATEGORY = 8 + PARAMETER = 2 + DATATYPE = FIXED16 + KEYS = ["red", "green", "blue", "luma"] + DESCRIPTIONS = ["", "", "", ""] + + +class OffsetAdjust(CameraControlData): + CATEGORY = 8 + PARAMETER = 3 + DATATYPE = FIXED16 + KEYS = ["red", "green", "blue", "luma"] + DESCRIPTIONS = ["", "", "", ""] + + +class ContrastAdjust(CameraControlData): + CATEGORY = 8 + PARAMETER = 4 + DATATYPE = FIXED16 + KEYS = ["pivot", "adjust"] + DESCRIPTIONS = ["", ""] + + +class LumaMix(CameraControlData): + CATEGORY = 8 + PARAMETER = 5 + DATATYPE = FIXED16 + KEYS = ["mix"] + + +class ColorAdjust(CameraControlData): + CATEGORY = 8 + PARAMETER = 6 + DATATYPE = FIXED16 + KEYS = ["hue", "saturation"] + DESCRIPTIONS = ["", ""] + + +class Codec(CameraControlData): + CATEGORY = 10 + PARAMETER = 0 + DATATYPE = INT8 + KEYS = ["codec", "variant"] + DESCRIPTIONS = ["", ""] + + +class TransportMode(CameraControlData): + CATEGORY = 10 + PARAMETER = 1 + DATATYPE = INT8 + KEYS = ["mode", "speed", "flags", "storage"] + DESCRIPTIONS = ["", "x", "", ""] + + +class PanTiltVelocity(CameraControlData): + CATEGORY = 11 + PARAMETER = 0 + DATATYPE = FIXED16 + KEYS = ["pan", "tilt"] + DESCRIPTIONS = ["", ""] + + +class PositionPreset(CameraControlData): + CATEGORY = 11 + PARAMETER = 1 + DATATYPE = INT8 + KEYS = ["command", "slot"] + DESCRIPTIONS = ["", ""] diff --git a/pyatem/protocol.py b/pyatem/protocol.py index 31e26b9..c6ee563 100644 --- a/pyatem/protocol.py +++ b/pyatem/protocol.py @@ -382,6 +382,7 @@ class AtemProtocol: if __name__ == '__main__': from pyatem.command import CutCommand import pyatem.mediaconvert + from pyatem.cameracontrol import CameraControlData logging.basicConfig(level=logging.INFO) @@ -393,6 +394,11 @@ if __name__ == '__main__': def changed(key, contents): if key == 'time': return + if isinstance(contents, fieldmodule.CameraControlDataPacketField): + parsed = CameraControlData.from_data(contents) + if parsed: + print(parsed) + return if isinstance(contents, fieldmodule.FieldBase): print(contents) else: @@ -403,7 +409,8 @@ if __name__ == '__main__': for mid in testmixer.mixerstate['macro-properties']: macro = testmixer.mixerstate['macro-properties'][mid] if macro.is_used: - testmixer.download(0xffff, macro.index) + # testmixer.download(0xffff, macro.index) + pass return for sid in testmixer.mixerstate['mediaplayer-file-info']: still = testmixer.mixerstate['mediaplayer-file-info'][sid] -- 2.34.2