DMX caputre/playback software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

141 lines
4.8 KiB

# dmx.py
from PyQt5.QtCore import QObject, QThread, pyqtSignal
import socket
import struct
import numpy as np
import subprocess
import time
class DMXRecorder(QObject):
def __init__(self):
super().__init__()
self.thread = None
self.recording_thread = None
def start_recording(self):
self.thread = QThread()
self.recording_thread = RecordingThread()
self.recording_thread.moveToThread(self.thread)
self.thread.started.connect(self.recording_thread.run)
self.recording_thread.finished.connect(self.thread.quit)
self.recording_thread.finished.connect(self.recording_thread.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
def stop_recording(self):
if self.recording_thread:
self.recording_thread.stop()
if self.thread:
self.thread.quit()
self.thread.wait()
class RecordingThread(QObject):
finished = pyqtSignal()
def __init__(self):
super().__init__()
self.running = False
self.socket = None
self.ffmpeg_process = None
def run(self):
self.running = True
# Set up UDP socket to listen for Art-Net DMX data
UDP_IP = "0.0.0.0"
UDP_PORT = 6454
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.bind((UDP_IP, UDP_PORT))
# Set socket to non-blocking
self.socket.setblocking(False)
# Prepare for recording
expected_universes = set(range(4)) # Universes 0 to 3 (adjust as needed)
num_universes = len(expected_universes)
universe_size = 512
# Define frame dimensions
frame_width = 64 # Adjust as needed (must divide evenly into total data size)
total_pixels = num_universes * universe_size
frame_height = total_pixels // frame_width
# Frame rate and timing
frame_rate = 30 # frames per second
frame_interval = 1.0 / frame_rate
last_frame_time = time.time()
# Set up ffmpeg process
ffmpeg_cmd = [
'ffmpeg',
'-y',
'-f', 'rawvideo',
'-pix_fmt', 'gray',
'-s', f'{frame_width}x{frame_height}',
'-r', str(frame_rate),
'-i', '-', # Input from stdin
'-c:v', 'ffv1',
'dmx_video.mkv'
]
self.ffmpeg_process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE)
universes_data = {}
last_received = {}
try:
while self.running:
current_time = time.time()
# Receive data
try:
data, addr = self.socket.recvfrom(1024)
if data.startswith(b'Art-Net'):
op_code = struct.unpack('<H', data[8:10])[0]
# OpCode for ArtDMX is 0x5000
if op_code == 0x5000:
# Parse universe
universe = struct.unpack('<H', data[14:16])[0]
# Get length
length = struct.unpack('>H', data[16:18])[0]
dmx_data = data[18:18+length]
# Store the data for this universe
universes_data[universe] = dmx_data
last_received[universe] = current_time
except BlockingIOError:
# No data received
pass
except Exception as e:
print(f"Error receiving data: {e}")
# Check if it's time to write a frame
if current_time - last_frame_time >= frame_interval:
# Assemble frame data
frame_data = b''
for universe in sorted(expected_universes):
dmx_data = universes_data.get(universe, b'\x00'*512)
if len(dmx_data) < 512:
dmx_data += b'\x00' * (512 - len(dmx_data))
frame_data += dmx_data
# Convert frame_data to numpy array
frame_array = np.frombuffer(frame_data, dtype=np.uint8)
# Reshape to image dimensions
frame_array = frame_array.reshape((frame_height, frame_width))
# Write frame to ffmpeg stdin
self.ffmpeg_process.stdin.write(frame_array.tobytes())
last_frame_time = current_time
time.sleep(0.001) # Sleep briefly to avoid maxing out CPU
finally:
# Clean up
self.ffmpeg_process.stdin.close()
self.ffmpeg_process.wait()
self.socket.close()
self.finished.emit()
def stop(self):
self.running = False