~martijnbraam/pyatem

f9386e44852abd1bfe5daabb938ad6919e9e2d9a — Martijn Braam 2 months ago 09750fa
pyatem: implement decoders for the camera data packets
2 files changed, 496 insertions(+), 1 deletions(-)

A pyatem/cameracontrol.py
M pyatem/protocol.py
A pyatem/cameracontrol.py => pyatem/cameracontrol.py +488 -0
@@ 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 = ["", ""]

M pyatem/protocol.py => pyatem/protocol.py +8 -1
@@ 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]