diff --git a/PyATEMMax/ATEMCommandHandlers.py b/PyATEMMax/ATEMCommandHandlers.py index 0bec8b5..7417c84 100644 --- a/PyATEMMax/ATEMCommandHandlers.py +++ b/PyATEMMax/ATEMCommandHandlers.py @@ -557,6 +557,39 @@ def _handleFtbS(self) -> None: self._d.fadeToBlack[mE].state.framesRemaining = self._inBuf.getU8(3) + def _handleTEST(self) -> None: + """Test method to see if new methods work""" + pass + + + def _handleFEna(self) -> None: + """Handle FEna (Fade Enable/Disable) state updates""" + try: + mE = self._getBufMixEffect(0) + + # Get packet bytes + byte0 = self._inBuf.getU8(0) + byte1 = self._inBuf.getU8(1) + + # Try to get byte2 safely + try: + byte2 = self._inBuf.getU8(2) + except: + byte2 = 0 + + # Parse based on your Wireshark analysis + if byte0 == 0 and byte1 == 1: + # Enable packet (00 01) + self._d.fadeToBlack[mE].disabled = False + else: + # Disable packet or other pattern + self._d.fadeToBlack[mE].disabled = True + + except Exception as e: + # Don't crash on parsing errors + pass + + def _handleColV(self) -> None: colorGenerator = self._getBufEnum(0, 8, self._p.colorGenerators) self._d.colorGenerator[colorGenerator].hue = self._inBuf.getFloat(2, False, 16, 10) diff --git a/PyATEMMax/ATEMSetterMethods.py b/PyATEMMax/ATEMSetterMethods.py index bfff47c..0695adc 100644 --- a/PyATEMMax/ATEMSetterMethods.py +++ b/PyATEMMax/ATEMSetterMethods.py @@ -244,17 +244,28 @@ def setTransitionNextTransition(self, mE: Union[ATEMConstant, str, int], nextTra mE: see ATEMMixEffects nextTransition: see ATEMTransitionStyles """ - + print(f"🔍 setTransitionNextTransition called with mE={mE}, nextTransition={nextTransition}") + mE_val = self.atem.mixEffects[mE].value - nextTransition_val = self.atem.transitionStyles[nextTransition].value + nextTransition_val = nextTransition + + print(f"🔍 mE_val={mE_val}, nextTransition_val={nextTransition_val}") indexMatch:bool = self.switcher._outBuf.getU8(1) == mE_val + print(f"🔍 indexMatch={indexMatch}") self.switcher._prepareCommandPacket("CTTp", 4, indexMatch) self.switcher._outBuf.setU8Flag(0, 1) # Bit 0: Transition Style ON self.switcher._outBuf.setU8(1, mE_val) self.switcher._outBuf.setU8(3, nextTransition_val) + + print(f"🔍 Command packet prepared: CTTp, length=4") + print(f"🔍 Byte 0 (flag): set to 1") + print(f"🔍 Byte 1 (mE): {mE_val}") + print(f"🔍 Byte 3 (nextTransition): {nextTransition_val}") + self.switcher._finishCommandPacket() + print(f"🔍 Command packet sent") def setTransitionPreviewEnabled(self, mE: Union[ATEMConstant, str, int], enabled: bool) -> None: @@ -2512,6 +2523,31 @@ def setFadeToBlackRate(self, mE: Union[ATEMConstant, str, int], rate: int) -> No self.switcher._outBuf.setU8(2, rate) self.switcher._finishCommandPacket() + + # Replace your setFadeToBlackDisabled method with this final version: + + def setFadeToBlackDisabled(self, mE: Union[ATEMConstant, str, int], disabled: bool) -> None: + mE_val = self.atem.mixEffects[mE].value + + print(f"Setting FTB disabled: ME{mE_val} = {disabled}") + + if disabled: + # DISABLE packet + indexMatch: bool = self.switcher._outBuf.getU8(1) == mE_val + self.switcher._prepareCommandPacket("FEna", 4, indexMatch) + self.switcher._outBuf.setU8Flag(0, 0) + self.switcher._outBuf.setU8(1, mE_val) + self.switcher._outBuf.setU8(2, 0) + print(f"Sent disable packet: [00 {mE_val:02x} 00]") + else: + # ENABLE packet + self.switcher._prepareCommandPacket("FEna", 4, False) + self.switcher._outBuf.setU8(0, 0) + self.switcher._outBuf.setU8(1, 1) + print(f"Sent enable packet: [00 01]") + + self.switcher._finishCommandPacket() + def setColorGeneratorHue(self, colorGenerator: Union[ATEMConstant, str, int], hue: float) -> None: """Set Color Generator Hue diff --git a/PyATEMMax/StateData/FadeToBlack.py b/PyATEMMax/StateData/FadeToBlack.py index dfbcf43..3dba854 100644 --- a/PyATEMMax/StateData/FadeToBlack.py +++ b/PyATEMMax/StateData/FadeToBlack.py @@ -1,18 +1,8 @@ -#!/usr/bin/env python3 -# coding: utf-8 -""" -PyATEMMax state data: FadeToBlack -Part of the PyATEMMax library. -""" - -# pylint: disable=missing-class-docstring - from PyATEMMax.ATEMProtocol import ATEMProtocol from PyATEMMax.ATEMValueDict import ATEMValueDict class FadeToBlack(): - class State(): def __init__(self): self.framesRemaining: int = 0 @@ -22,6 +12,7 @@ def __init__(self): def __init__(self): self.rate: int = 0 self.state: FadeToBlack.State = FadeToBlack.State() + self.disabled: bool = False class FadeToBlackList(ATEMValueDict[FadeToBlack]): diff --git a/PyATEMMax/StateData/Transition.py b/PyATEMMax/StateData/Transition.py index ebe4afe..bbd4ec2 100644 --- a/PyATEMMax/StateData/Transition.py +++ b/PyATEMMax/StateData/Transition.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # coding: utf-8 """ -PyATEMMax state data: Transition +PyATEMMax state data: Transition - Enhanced with Stinger totalRate Part of the PyATEMMax library. """ @@ -55,6 +55,44 @@ def __init__(self): # Transition.Stinger self.source: ATEMConstant = ATEMConstant() self.triggerPoint: int = 0 + @property + def totalRate(self) -> int: + """ + Get the total sting rate (mix rate + clip duration) + This matches what ATEM software control displays in the rate field + + Returns: + int: Total transition time in frames (mixRate + clipDuration) + """ + return self.preRoll + self.clipDuration + + @property + def stingRate(self) -> int: + """ + Alias for totalRate - total sting duration including mix and clip + This matches ATEM software behavior where the rate field shows total time + + Returns: + int: Total transition time in frames (mixRate + clipDuration) + """ + return self.totalRate + + @property + def totalRateSeconds(self) -> float: + """ + Get the total sting rate in seconds (assuming 25fps) + + Returns: + float: Total transition time in seconds + """ + return self.totalRate / 25.0 + + def __str__(self) -> str: + """String representation showing all sting parameters""" + return (f"Stinger(mixRate={self.mixRate}, clipDuration={self.clipDuration}, " + f"totalRate={self.totalRate}, source={self.source}, " + f"triggerPoint={self.triggerPoint})") + class Wipe(): class Position():