Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions PyATEMMax/ATEMCommandHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 38 additions & 2 deletions PyATEMMax/ATEMSetterMethods.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
11 changes: 1 addition & 10 deletions PyATEMMax/StateData/FadeToBlack.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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]):
Expand Down
40 changes: 39 additions & 1 deletion PyATEMMax/StateData/Transition.py
Original file line number Diff line number Diff line change
@@ -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.
"""

Expand Down Expand Up @@ -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():
Expand Down