This document provides comprehensive examples of different types of Python nodes that can be created in Workflows, showing various patterns and use cases.
Overview
Python nodes in Workflows can be designed for many different purposes. This document shows examples of various node types to help you understand how to create nodes for different scenarios.
File Processing Nodes
File Copy Node
A node that copies files from input to output:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
def init(self):
self.color = QColor(120, 180, 180)
self.addConnector("input", Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.MODE_OPTIONAL)
self.addConnector("output", Cconnector.CONNECTOR_TYPE_OUTPUT, Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True, "input_%")
self.bypassSupported = True
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
return True
def process(self):
if self.bypassEnabled:
return True
inputs = self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
output = self.getFirstConnectorByName("output")
output.clearUniversalPaths()
for c in inputs:
for up in c.getUniversalPaths():
if self.isCancelled():
return False
output.addUniversalPath(up)
return True
File Rename Node
A node that renames files:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
def init(self):
self.color = QColor(255, 165, 0)
self.addConnector("input", Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.MODE_OPTIONAL)
self.addConnector("output", Cconnector.CONNECTOR_TYPE_OUTPUT, Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True, "input_%")
self.addProperty("newNamePattern")
self.bypassSupported = True
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
if not self.newNamePattern:
return self.critical("rename", "Name pattern is required")
return True
def process(self):
if self.bypassEnabled:
return True
inputs = self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
output = self.getFirstConnectorByName("output")
output.clearUniversalPaths()
for c in inputs:
for up in c.getUniversalPaths():
if self.isCancelled():
return False
new_up = up.clone()
output.addUniversalPath(new_up)
return True
Metadata Nodes
Metadata Reader Node
A node that reads metadata from files:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
def init(self):
self.color = QColor(128, 0, 128)
self.addConnector("input", Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.MODE_OPTIONAL)
self.addConnector("output", Cconnector.CONNECTOR_TYPE_OUTPUT, Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True, "input_%")
self.addProperty("metadataFields")
self.bypassSupported = True
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
return True
def process(self):
if self.bypassEnabled:
return True
inputs = self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
output = self.getFirstConnectorByName("output")
output.clearUniversalPaths()
for c in inputs:
for up in c.getUniversalPaths():
if self.isCancelled():
return False
metadata = up.getMetadata()
self.info("metadata", "File: {} - Metadata: {}".format(up.getFileName(), metadata))
output.addUniversalPath(up)
return True
Metadata Writer Node
A node that writes metadata to files:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
def init(self):
self.color = QColor(0, 128, 0)
self.addConnector("input", Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.MODE_OPTIONAL)
self.addConnector("output", Cconnector.CONNECTOR_TYPE_OUTPUT, Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True, "input_%")
self.addProperty("customMetadata")
self.bypassSupported = True
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
return True
def process(self):
if self.bypassEnabled:
return True
inputs = self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
output = self.getFirstConnectorByName("output")
output.clearUniversalPaths()
for c in inputs:
for up in c.getUniversalPaths():
if self.isCancelled():
return False
if self.customMetadata:
up.setMetadata(self.customMetadata)
output.addUniversalPath(up)
return True
External Application Nodes
FFmpeg Node
A complex node that integrates with FFmpeg:
from Mistika.Qt import QColor,QRegularExpression
from Mistika.classes import Cconnector,CbaseItem,CexternalAppNodeLink
from Mistika import sgoPaths
import os
import sys
from baseItemTools import totalNumberOfUPs
import Mistika
def ffmpegPath():
ext=".exe" if sys.platform == "win32" else ""
fldr = "/MacOS" if sys.platform == "darwin" else "/bin"
fp=sgoPaths.apps()+ fldr + "/ffmpeg" + ext
return fp
def init(self):
self.setClassName("FFmpeg")
self.color=QColor(92, 184, 92)
self.addConnector("input",Cconnector.CONNECTOR_TYPE_INPUT,Cconnector.MODE_OPTIONAL)
self.addConnector("output",Cconnector.CONNECTOR_TYPE_OUTPUT,Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True,"input_%")
self.addProperty("url")
self.addProperty("threads",1)
self.addProperty("dstExtension","mp4")
self.addProperty("cmdLineParams","-i [input] -c:v [param1] -b:v [param2] -c:a [param3] [param4] [param5] [param6] [param7] [param8] [output]")
self.addProperty("param1","h264_nvenc")
self.addProperty("param2","5M")
self.addProperty("param3","aac")
self.addProperty("param4","")
self.addProperty("param5","")
self.addProperty("param6","")
self.addProperty("param7","")
self.addProperty("param8","")
self.bypassSupported=True
self.setDropToProperty("url")
self.setDropSupportedTypes(1)
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
fp=ffmpegPath()
if not os.path.exists(fp):
return self.critical("ffmpeg:isReady","ffmpeg binary not found: ".format(fp))
return True
def process(self):
res=True
inputs=self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT,Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
out = self.getFirstConnectorByName("output")
out.clearUniversalPaths()
dstUrl=self.evaluate(self.url).strip()
self.setComplexity(totalNumberOfUPs(self)*100)
if self.bypassEnabled:
return True
self.progressUpdated(0)
binfp=ffmpegPath()
apps=[]
threads=int(self.threads)
queue=self.createExecutionQueue(threads)
cancelled=False
totalFrames=0
for c in inputs:
if cancelled:
break
for up in c.getUniversalPaths():
mfid=up.getMediaFileInfoData()
frames=mfid.getToken("Frames")
fp=up.getFilePath()
self.info("ffmpeg:process:up","Preparing {}".format(fp))
dst=self.composeDstFilePath(dstUrl,up,False,0)
ext=self.evaluate(self.dstExtension.strip())
if ext:
dst.setExtension(ext)
app=self.createExternalApp()
apps.append((app,up,dst))
if frames>0:
totalFrames+=frames
app.setMaxProgress(frames,totalFrames)
app.addHookRX("errorRX",QRegularExpression("^(Error .*)$"))
app.addHookRX("progressRX",QRegularExpression("^frame=\\s*(\\d+).*"))
app.setOutputUP(dst)
app.setPrintOutputs(app.EA_STD_BOTH)
defTokens=app.createDefaultDict(up)
args=app.createArgumentsList(self.evaluate(self.cmdLineParams.strip()),up,defTokens)
args=('-y',)+args
quoted=app.quoteList(args)
if self.isCancelled():
res=False
cancelled=True
queue.cancelAll()
break
self.info("ffmpeg:process:args","\"{}\" {}".format(binfp,' '.join(quoted)))
app.setParams(binfp,args)
queue.addJob(app)
while queue.wait(1000):
if self.isCancelled():
res=False
queue.cancelAll()
for app,up,dst in apps:
exitCode=app.exitCode()
fp=up.getFilePath()
if exitCode:
res=self.critical("ffmpeg:process:error","Unable to process {}. exitCode={}".format(fp,exitCode)) and res
self.addFailedUP(up,"Unable to process {}. exitCode={}".format(fp,exitCode))
else:
out.addUniversalPath(dst)
app.deleteLater()
queue.deleteLater()
self.progressUpdated(self.complexity())
return res
def onExternalAppHook(self,appId,output,name,rxMatch,line):
print("Signal",name,"received from std",output,"(Ignored)")
Network Nodes
Google Drive Node
A node that interacts with Google Drive:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
def init(self):
self.color = QColor(30, 144, 255)
self.addConnector("input", Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.MODE_OPTIONAL)
self.addConnector("output", Cconnector.CONNECTOR_TYPE_OUTPUT, Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True, "input_%")
self.addProperty("driveFolderId")
self.addProperty("accessToken")
self.bypassSupported = True
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
if not self.driveFolderId:
return self.critical("drive", "Drive folder ID is required")
if not self.accessToken:
return self.critical("drive", "Access token is required")
return True
def process(self):
if self.bypassEnabled:
return True
inputs = self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
output = self.getFirstConnectorByName("output")
output.clearUniversalPaths()
for c in inputs:
for up in c.getUniversalPaths():
if self.isCancelled():
return False
output.addUniversalPath(up)
return True
Data Processing Nodes
CSV Processor Node
A node that processes CSV data:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
import csv
def init(self):
self.color = QColor(255, 215, 0)
self.addConnector("input", Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.MODE_OPTIONAL)
self.addConnector("output", Cconnector.CONNECTOR_TYPE_OUTPUT, Cconnector.MODE_OPTIONAL)
self.setAcceptConnectors(True, "input_%")
self.addProperty("csvDelimiter", ",")
self.addProperty("processHeaders", True)
self.bypassSupported = True
return True
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
return True
def process(self):
if self.bypassEnabled:
return True
inputs = self.getConnectorsByType(Cconnector.CONNECTOR_TYPE_INPUT, Cconnector.CONNECTOR_SPECIALTYPE_NORMAL)
output = self.getFirstConnectorByName("output")
output.clearUniversalPaths()
for c in inputs:
for up in c.getUniversalPaths():
if self.isCancelled():
return False
with open(up.getFilePath(), 'r') as f:
reader = csv.reader(f, delimiter=self.csvDelimiter)
pass
output.addUniversalPath(up)
return True
Utility Nodes
Configuration Node
A node that manages configuration:
from Mistika.Qt import QColor
def init(self):
self.color = QColor(225, 225, 210)
self.addProperty("watchers")
self.addProperty("deliveries")
return True
def isReady(self):
res=True
if self.watchers=="":
res=self.critical("config:watchers","'Watchers' folder can not be empty") and res
if self.deliveries=="":
res=self.critical("config:deliveries","'Deliveries' folder can not be empty") and res
return res
def process(self):
return True
Logger Node
A node that logs information:
from Mistika.Qt import QColor
def init(self):
self.color = QColor(128, 128, 128)
self.addProperty("logLevel", "INFO")
self.addProperty("logMessage")
return True
def isReady(self):
return True
def process(self):
self.info("logger", self.logMessage)
return True
Node Structure Best Practices
Consistent Structure
All nodes should follow a consistent structure:
from Mistika.Qt import QColor
from Mistika.classes import Cconnector
def helper_function():
pass
def init(self):
return True
def isReady(self):
return True
def process(self):
return True
Error Handling
Proper error handling should be implemented:
def isReady(self):
if self.bypassSupported and self.bypassEnabled:
return True
if not self.requiredProperty:
return self.critical("validation", "Required property missing")
if not self.checkExternalDependency():
return self.critical("dependency", "External dependency not available")
return True
def process(self):
try:
pass
except Exception as e:
self.critical("process", "Error: {}".format(str(e)))
return False
return True
Testing Examples
Manual Testing
Nodes should be tested manually in workflows:
- Create workflow with the node
- Connect appropriate inputs/outputs
- Run workflow
- Check logs for errors
- Verify outputs are correct
Parameter Validation
def isReady(self):
if self.threads < 1:
return self.critical("threads", "Threads must be at least 1")
if not self.url or not self.url.strip():
return self.critical("url", "URL cannot be empty")
if self.filePath and not os.path.exists(self.filePath):
return self.critical("file", "File path does not exist")
return True
Summary
This document provides examples of various node types that can be created in Workflows:
- File processing nodes
- Metadata nodes
- External application integration nodes
- Network nodes
- Data processing nodes
- Utility nodes
Each example demonstrates different patterns and best practices for node development. The key is to understand the requirements and choose the appropriate patterns for your specific use case.