1. Controlling MXW through Python: Difference between revisions

From MXWendler Wiki
Jump to navigation Jump to search
Hwendler (talk | contribs)
No edit summary
No edit summary
 
(17 intermediate revisions by one other user not shown)
Line 13: Line 13:


The file '''mxw_plugin.ini''' defines the registration of the plugin. The following fields are valid:
The file '''mxw_plugin.ini''' defines the registration of the plugin. The following fields are valid:
{| class="wikitable" style="margin:auto"
{| class="wikitable" style="margin-left: 0px;"
|+ Caption text
|+ Fields
|-
|-
! Tag !! Use !! Values/Example
! Tag !! Use !! Values/Example
Line 32: Line 32:
| plugin_grid_bg_color || Default color in grid (may be changed via script) || 0.95 0.05 0.45 1.00 (RGBA with range 0..1)
| plugin_grid_bg_color || Default color in grid (may be changed via script) || 0.95 0.05 0.45 1.00 (RGBA with range 0..1)
|-
|-
| plugin_tooltip || Tool tip in grid and panel || "This plugin triggers the playlist when it finds a Face"
| plugin_tooltip || Tool tip in grid and panel || "This plugin triggers the playlist when it finds a face"
|}
|}


An example mxw_plugin.ini looks like this:
An example mxw_plugin.ini looks like this:
Line 48: Line 49:
‎</syntaxhighlight>
‎</syntaxhighlight>


The plugins are defined in a mxw_main.py file. This file is loaded, and  
 
The plugins are defined in a mxw_main.py file. This file is loaded, and the main UI calls the provided interface functions. The following interface functions are available for playlist plugins:
 
{| class="wikitable" style="margin-left: 0px;"
|+ Fields
|-
! Function Name !! Details
|-
| '''onCreate()''' || Called when the plugin item is created
|-
| '''onDelete()''' || Called when the plugin item is deleted. Note that this is only called on software shutdown, not called when the plugin is e.g. removed, since items are kept for eventual undo-operations
|-
| '''getColorBG()''' || Get background color, called each render cycle
|-
| '''onAction()''' || Called when cue is entered
|-
| '''onPostAction()''' || Called when the cue is left
|-
| '''onPreparePlayback()''' || Called when the playlist gets a preloading command (e.g. when the GoToStart button is pressed)
|-
| '''onActivateInUI()''' || Called when the item is selected in the ui
|-
| '''onNewFrameInPlayoutCue()''' || Called on each frame when in current active cue
|-
| '''onRenderPanel()''' || Called on each frame when actvated in UI to render the panel
|-
| '''onActiveCueChange()''' || Called when the playlist seeks
|-
| '''getDuration()''' || Get action duration
|-
| '''getText()''' || Get grid text, called each render cycle (may display some information)
|-
| '''onCleanup()''' || Called when the playlist aborts or seeks
|-
| '''getTimeSinceOnActionIssued()''' ||Get action duration, called each render cycle (may display a countdown)
|-
| '''onPause()''' || Called when the playlist goes paused
|}
 
Note that not all of the interface functions need to be implemented. If a function is not defined, it is probed but not called.
 
 
This is a minimal example of video writing and placing the recorded video into the first preload:


<syntaxhighlight lang="Python" line>
<syntaxhighlight lang="Python" line>
import tempfile
import tempfile
import pickle, codecs # load / store
import mxw, mxw_imgui # for mxw interaction, mxw ui interaction
import mxw, mxw_imgui # for mxw interaction, mxw ui interaction
import cv2 # image processing
import cv2         # image processing
import numpy as np # math
import numpy as np # math


# example per-instance-storage: create a dictionary
capture_device=""
# and use it with 'item_id' as key (this integer is set by host application before every function call)
videosize = (640,480)
class video_writer:
f = object()
    capture_device=""
out = object()
    instance_storage = {}
    videosize = (640,480)


# -----------------------------------------------------------------------------------
def onCreate():
def onCreate():
v = video_writer()
global capture_device
dev = mxw.media().get_capture_device_names()
dev = mxw.media().get_capture_device_names()
v.capture_device = dev[1]
capture_device = dev[1]
instance_storage[item_id] = v
return
return
# save and load: you can serialize into a string
def onSave():
    serialized = codecs.encode(pickle.dumps(instance_storage[item_id]), "base64").decode()
    return serialized
def onLoad( serialized ):
    instance_storage[item_id] = pickle.loads(codecs.decode(serialized.encode(), "base64")) 
    return


def onAction():
def onAction():
v = instance_storage[item_id]
global capture_device, out, f
v.f = tempfile.NamedTemporaryFile(suffix='.avi')
f = tempfile.NamedTemporaryFile(suffix='.avi')
v.f.close()
f.close()
fourcc = cv2.VideoWriter_fourcc('M','P','4','V')
fourcc = cv2.VideoWriter_fourcc('M','P','4','V')
v.out = cv2.VideoWriter(v.f.name, fourcc, mxw.fps, videosize)
out = cv2.VideoWriter(f.name, fourcc, mxw.fps, videosize)
m = mxw.media(v.capture_device)
m = mxw.media(capture_device)
if(m.isvalid()):
if(m.isvalid()):
m.reference(True)
m.reference(True)
Line 93: Line 122:


def onPostAction():
def onPostAction():
v = instance_storage[item_id]
global capture_device, out, f
v.out.release()
out.release()
mxw.preload(1).set_media(v.f.name)
mxw.preload(1).set_media(f.name)
m = mxw.media(v.capture_device)
m = mxw.media(capture_device)
if(m.isvalid()):
if(m.isvalid()):
m.reference(False)
m.reference(False)
Line 102: Line 131:


def onNewFrameInPlayoutCue():
def onNewFrameInPlayoutCue():
v = instance_storage[item_id]
global capture_device, out, f
m = mxw.media(v.capture_device)
m = mxw.media(capture_device)
if(m.isvalid()):
if(m.isvalid()):
img = m.get_image_sample_cvmat(videosize[0],videosize[1])
img = m.get_image_sample_cvmat(videosize[0],videosize[1])
img = np.array(img, copy=False)
img = np.array(img, copy=False)
img = cv2.flip(img, 0)
img = cv2.flip(img, 0)
v.out.write(img)
out.write(img)
return
return


# render in panel for settings etc
# render in panel for settings etc
def onRenderPanel():
def onRenderPanel():
v = instance_storage[item_id]
mxw_imgui.text_unformatted("This plugin records a camera")
mxw_imgui.text_unformatted("This plugin records a camera")
if(hasattr(v,'f')):
mxw_imgui.text_unformatted(v.f.name)
if(hasattr(v,'out') and v.out.isOpened()):
mxw_imgui.text_unformatted("Recording")
else:
mxw_imgui.text_unformatted("Not recording")
dev = mxw.media().get_capture_device_names()
a = mxw_imgui.combo("Capture Device", dev.index(v.capture_device), dev)
if(a[0]):
print(str(a[1]))
v.capture_device = dev[a[1]]
return
return
‎</syntaxhighlight>
‎</syntaxhighlight>

Latest revision as of 09:31, 29 May 2024

Python is used in MXW trough a plugin interface. Specified program paths are searched for plugins which are registered, and then loaded through the user interface.

As of version 7.2., plugins are available in the playlist. There are two paths searched during program startup, the program folder and the user folder:

  • (Program folder)/plugins/playlist/python/
  • ~.StageDesigner/plugins/playlist/python/ (Unix)
  • ~.StageDesigner/plugins/playlist/python/ (Windows)

Each plugin resides in a folder. Inside this folder, two files have to be present,

  • mxw_plugin.ini
  • mxw_main.py

The file mxw_plugin.ini defines the registration of the plugin. The following fields are valid:

Fields
Tag Use Values/Example
plugin_version Plugin Version 1
plugin_script_language Plugin Script Language Python
plugin_action_level Plugin Activity Switch disabled = not visible
plugin_menu_parent Top level parent in playlist menu "AI", "IO" ..
plugin_menu_name Playlist menu entry "Face detection"
plugin_grid_name Default name in grid (may be completed via script) "Face detect"
plugin_grid_bg_color Default color in grid (may be changed via script) 0.95 0.05 0.45 1.00 (RGBA with range 0..1)
plugin_tooltip Tool tip in grid and panel "This plugin triggers the playlist when it finds a face"


An example mxw_plugin.ini looks like this:

[mxw_plugin]                            ; must be here
plugin_version = 1                      ; must be V1 (as of V7.2)
plugin_script_language = Python         ; must be Python (as of V7.2)
plugin_menu_parent = AI
plugin_menu_name = NeuronalNet(Dlib) Face Detect Plugin
plugin_grid_name = Face Detect Plugin
plugin_grid_bg_color = 0.950000 0.050000 0.450000 1.000000 ; set plugin color.
plugin_tooltip = This plugin triggers the playlist when it finds a Face using a neuronal net (Dlib)


The plugins are defined in a mxw_main.py file. This file is loaded, and the main UI calls the provided interface functions. The following interface functions are available for playlist plugins:

Fields
Function Name Details
onCreate() Called when the plugin item is created
onDelete() Called when the plugin item is deleted. Note that this is only called on software shutdown, not called when the plugin is e.g. removed, since items are kept for eventual undo-operations
getColorBG() Get background color, called each render cycle
onAction() Called when cue is entered
onPostAction() Called when the cue is left
onPreparePlayback() Called when the playlist gets a preloading command (e.g. when the GoToStart button is pressed)
onActivateInUI() Called when the item is selected in the ui
onNewFrameInPlayoutCue() Called on each frame when in current active cue
onRenderPanel() Called on each frame when actvated in UI to render the panel
onActiveCueChange() Called when the playlist seeks
getDuration() Get action duration
getText() Get grid text, called each render cycle (may display some information)
onCleanup() Called when the playlist aborts or seeks
getTimeSinceOnActionIssued() Get action duration, called each render cycle (may display a countdown)
onPause() Called when the playlist goes paused

Note that not all of the interface functions need to be implemented. If a function is not defined, it is probed but not called.


This is a minimal example of video writing and placing the recorded video into the first preload:

import tempfile
import mxw, mxw_imgui	# for mxw interaction, mxw ui interaction
import cv2 	        # image processing
import numpy as np	# math

capture_device=""
videosize = (640,480)
f = object()
out = object()

def onCreate():
	global capture_device
	dev = mxw.media().get_capture_device_names()
	capture_device = dev[1]
	return

def onAction():
	global capture_device, out, f
	f = tempfile.NamedTemporaryFile(suffix='.avi')
	f.close()
	fourcc = cv2.VideoWriter_fourcc('M','P','4','V')
	out = cv2.VideoWriter(f.name, fourcc, mxw.fps, videosize)
	m = mxw.media(capture_device)
	if(m.isvalid()):
		m.reference(True)
	return

def onPostAction():
	global capture_device, out, f
	out.release()
	mxw.preload(1).set_media(f.name)
	m = mxw.media(capture_device)
	if(m.isvalid()):
		m.reference(False)
	return

def onNewFrameInPlayoutCue():
	global capture_device, out, f
	m = mxw.media(capture_device)
	if(m.isvalid()):
		img = m.get_image_sample_cvmat(videosize[0],videosize[1])
		img = np.array(img, copy=False)
		img = cv2.flip(img, 0)
		out.write(img)
	return

# render in panel for settings etc
def onRenderPanel():
	mxw_imgui.text_unformatted("This plugin records a camera")
	return