Python binding de BlackMagic, utile pour traiter une IHM hors fusion ou passer par un Terminal.
- Fusion 7
import PeyeonScript as eyeon
fusion = eyeon.scriptapp("Fusion")
print fusion
- Fusion 8
import BlackmagicFusion as bmd
fusion = bmd.scriptapp("Fusion")
print(fusion)
- Fusion 18
import fusionscript
fusion = fusionscript.scriptapp("Fusion")
print(fusion)
On peut remarquer, par héritage, que seul le binding d'import change entre les versions. La plupart du code qui suit est issue de Fusion 7, qui lui même est issue du feu Digital Fusion (eyeon software). Toutes fonction sont présentes et la documentation de 2010 est encore bonne à prendre :)
Mais rassurez vous, il a aussi le manuel de scripting :
/Applications/Blackmagic Fusion 18/Developer/Fusion 8 Script Manual.pdf
win : C:\Program Files\Blackmagic Design\Fusion 9\FuScript.exe
linux : /opt/BlackmagicDesign/Fusion9/fuscript
darwin : /Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fuscript
fuscript
Usage: fuscript [opts] <script> [args]
-i - Enter interactive mode
-v - Print version information
-s - Run as script server
-S - Run as script server with no timeout
-p [appname] - Ping script apps
-P [appname] - Ping script apps (longer timeout)
-q - Be quiet
-b - Run with commandline debugger
-l <lang> - Explicitly set language, from:
lua
py2 or python2
py3 or python3
-x <string> - Execute string
# You can use FuScript to send a python command to the active Fusion session:
fuscript -l py2 -x 'fusion = fusionscript.scriptapp("Fusion");comp = fusion.CurrentComp;import sys; comp.Print("[Python Version] " + str(sys.version) + "\n")'
# You can use FuScript to send a remote python command:
fuscript -l py2 -x 'fusion = fusionscript.scriptapp("Fusion", "hostname");comp = fusion.CurrentComp;import sys; comp.Print("[Python Version] " + str(sys.version) + "\n")'
# You can use FuScript to run
fuscript -l py2 -x 'fusion = fusionscript.scriptapp("Fusion", "Pine");comp = fusion.CurrentComp;' <script.py>
S'il y a bien une chose à connaitre dans le process de fusion, c'est le
lock()/unlock()
Il permet de verrouiller la comp et ansi éviter des prompt sur de lourd/long traitements. ex: changement de PATH, évite les popup windows de replacements
comp.Lock()
... do stuff ...
comp.Unlock()
# Récupère tout les attrs de la comp (simple)
from pprint import pprint
pprint(comp.GetAttrs())
# Récupère tout les attrs des comp ouvertes (multiple)
from pprint import pprint
print("[Active Fusion Comps]")
compList = fu.GetCompList()
for key, cmp in sorted(compList.items()):
print("[" + str(cmp.GetAttrs()["COMPS_Name"]) + "]")
pprint(cmp.GetAttrs())
print("\n")
Ce processus de fonction comp.GetAttrs () [""] le nom de l'attribut individuel entre crochets.
print(comp.GetAttrs('SOME_ATTR'))
print(comp.SetAttrs('SOME_ATTR'))
renderStart = int(comp.GetAttrs()["COMPN_RenderStart"])
renderEnd = int(comp.GetAttrs()["COMPN_RenderEnd"])
stepBy = int(comp.GetAttrs()["COMPI_RenderStep"])
print("[Render Frame Range] " + str(renderStart) + "-" + str(renderEnd) + " [Step by Frames] " + str(stepBy) + "\n")
print(comp.GetPrefs())
comp.SetPrefs({})
Example
#rajout d'une ligne dans Fusion.prefs (#MASTER PROFIL)
#C:\Users\%USERNAME%\AppData\Roaming\Blackmagic Design\Fusion\Profiles\Default
#Pour évuentuel copy des pref ["Global"]
comp = self.fusion.GetCurrentComp()
comp.SetPrefs({
"Comp.FrameFormat.Name": "HDTV 1080",
"Comp.FrameFormat.Width": 1920,
"Comp.FrameFormat.Height": 1080,
"Comp.FrameFormat.AspectX": 1.0,
"Comp.FrameFormat.AspectY": 1.0,
"Comp.FrameFormat.GuideRatio": 1.77777777777778,
"Comp.FrameFormat.Rate": 25,
"Comp.FrameFormat.DepthInteractive": 2, #16 bits Float
"Comp.FrameFormat.DepthFull": 2, #16 bits Float
"Comp.FrameFormat.DepthPreview": 2 #16 bits Float
})
#paramétrer le format exr
if tool.GetAttrs('TOOLS_Name') == 'exr':
tool.OpenEXRFormat.Depth = 1 # 16-bit float
tool.OpenEXRFormat.ProcessRed = 1
tool.OpenEXRFormat.ProcessGreen = 1
tool.OpenEXRFormat.ProcessBlue = 1
tool.OpenEXRFormat.ProcessAlpha = 0
tool.OpenEXRFormat.RedEnable = 1
tool.OpenEXRFormat.GreenEnable = 1
tool.OpenEXRFormat.BlueEnable = 1
tool.OpenEXRFormat.AlphaEnable = 0
tool.OpenEXRFormat.ZEnable = 0
tool.OpenEXRFormat.CovEnable = 0
tool.OpenEXRFormat.ObjIDEnable = 0
tool.OpenEXRFormat.MatIDEnable = 0
tool.OpenEXRFormat.UEnable = 0
tool.OpenEXRFormat.VEnable = 0
tool.OpenEXRFormat.XNormEnable = 0
tool.OpenEXRFormat.YNormEnable = 0
tool.OpenEXRFormat.ZNormEnable = 0
tool.OpenEXRFormat.XVelEnable = 0
tool.OpenEXRFormat.YVelEnable = 0
tool.OpenEXRFormat.XRevVelEnable = 0
tool.OpenEXRFormat.YRevVelEnable = 0
tool.OpenEXRFormat.XPosEnable = 0
tool.OpenEXRFormat.YPosEnable = 0
tool.OpenEXRFormat.ZPosEnable = 0
tool.OpenEXRFormat.XDispEnable = 0
tool.OpenEXRFormat.YDispEnable = 0
object.GetInputList()
print(object.GetInputList())
#LOADER
loader = comp.Loader({'Clip' : 'C:\\Path_To_File.jpg'})
#MATERIAL
mat = comp.MtlBlinn({
'Diffuse.Color.Material' : 'C:\\Path_To_File.jpg',
'Specular.Intensity.Material' : 'C:\\Path_To_File.jpg'
})
#GEOMETRY
geo = comp.SurfaceFBXMesh({'ImportFile' : 'C:\\Path_To_File.fbx', 'ObjName' : 'Sphere', 'Size' : 0.5})
#TEXTURE
disp_tex = comp.Loader({'Clip' : 'C:\\Path_To_File.jpg'})
disp = comp.Displace3D({'Scale' : 7.5, 'Bias' : -0.5, 'Input' : disp_tex, 'SceneInput' : geo})
Exemple avec nom de fichier du type : la_tete_a_toto.comp
PATH_NOM_de_ma_comp = self.comp.GetAttrs("COMPS_FileName")
BASENAME_de_ma_comp = os.path.basename(PATH_NOM_de_ma_comp)
BASENAME_SPLIT_de_ma_comp = os.path.splitext(BASENAME_de_ma_comp)[0]
SPLIT_A = BASENAME_SPLIT_de_ma_comp.split("_")[0]
SPLIT_B = BASENAME_SPLIT_de_ma_comp.split("_")[1]
SPLIT_C = BASENAME_SPLIT_de_ma_comp.split("_")[2]
SPLIT_D = BASENAME_SPLIT_de_ma_comp.split("_")[3]
print SPLIT_A
print SPLIT_B
print SPLIT_C
print SPLIT_D
Console :
>>> la
>>> tete
>>> a
>>> toto
Il existe aussi la fonction
fusionscript.split()
oufusionscript.parseFilename()
vous pouvez retrouver toutes ces méthodes dans la scriptlib :
C:\Program Files\Blackmagic Design\Fusion 9\Scripts\bmd.scriptlib
Il s’agit d'activer/désactiver un node via ce Toggle :
tool.SetAttrs({'TOOLB_PassThrough' : True})
tool.SetAttrs({'TOOLB_PassThrough' : False})
mat = comp.MtlBlinn({})
mat.Diffuse.Color.Material = diff_tex
mat.Specular.Intensity.Material = spec_tex
geo = comp.SurfaceFBXMesh({})
geo.ImportFile = 'C:\\Path_To_File.fbx'
geo.ObjName = 'Sphere'
geo.Size = 0.5
geo.MaterialInput = mat
disp = comp.Displace3D({})
disp.Input = disp_tex
disp.SceneInput = geo
disp.Scale = 7.5
disp.Bias = -0.5
norm = comp.Loader({'Clip' : 'C:\\Path_To_File.jpg'})
norm_tex = comp.BumpMap({})
norm_tex.Input = norm
norm_tex.SourceImageType = 1
norm_tex = comp.BumpMap({'Input' : norm, 'SourceImageType' : 1})
fus_geo = 'C:\\Path_To_File.fbx'
geo_out = comp.ExporterFBX({})
geo_out.Input = disp_tex
geo_out.Filename = fus_geo
print(geo_out.Format.GetAttrs())
print(geo_out.Format.GetAttrs()['INPIDT_ComboControl_ID'])
geo_out.Format = 'FBX_binary'
geo_out.Format = '_3D_Studio_3DS'
geo_out.Format = 'Alias_OBJ'
L_view = comp.GetPreviewList()["Left"]
L2_view = comp.GetPreviewList()["Left.B"]
R_view = comp.GetPreviewList()["Right"]
R2_view = comp.GetPreviewList()["Right.B"]
L_view.ViewOn(image)
L_view.ViewOn(0)
(50% = .5, 100% = 1, Fit = 0)
L_view.View.SetScale(.5)
L_view.View.SetScale(1)
L_view.View.SetScale(0)
L_view.View.SetBuffer(2)
L_view.View.SetSplit(.5, .5, 45)
L_view.View.ResetView()
L_view.View.ShowingQuadView()
Revoie : True ou False
L_view.View.ShowQuadView(True)
L_view.View.ShowQuadView(False)
L_view.View.ShowingSubView()
Revoie : True ou False
L_view.View.ShowSubView(True)
L_view.View.ShowSubView(False)
L_view.View.SwapSubView()
Exemple de switch
comp.CurrentFrame.ViewOn(image, 1) # image sur la vue de gauche.
comp.CurrentFrame.ViewOn(image, 2) # image sur la vue de droite.
comp.CurrentFrame.ViewOn(1) #actif sur la vue.
comp.CurrentFrame.ViewOn(1, 0) # nettoie vue gauche.
comp.CurrentFrame.ViewOn() # nettoie toutes les vues
Exemple avec les modules du Viewer
comp.CurrentFrame.SwitchMainView('ToolView')
comp.CurrentFrame.SwitchMainView('SplineEditorView')
comp.CurrentFrame.SwitchMainView('TimelineView')
comp.CurrentFrame.SwitchMainView('TransportView')
comp.CurrentFrame.SwitchMainView('ModifierView')
comp.CurrentFrame.SwitchMainView('MaskView')
comp.CurrentFrame.SwitchMainView('TimeRulerView')
comp.CurrentFrame.SwitchMainView('ConsoleView')
comp.CurrentFrame.SwitchMainView('InfoView')
comp.CurrentFrame.SwitchMainView('FlowView')
comp.CurrentFrame.SwitchMainView('CurrentView')
comp.CurrentFrame.SwitchMainView('Composition')
comp.CurrentFrame.SwitchControlView('ControlView')
comp.CurrentFrame.SwitchControlView('ModifierView')
L_view.View.EnableLUT()
L_view.View.IsLUTEnabled()
Revoie : True ou False
Choix du node:
object = comp.Loader({})
# sélectionnez le flow
flow = comp.CurrentFrame.FlowView
# Choisir l'objet
flow.Select(object)
flow.Select(object, True)
# Rejetez le choix
flow.Select(object, False)
# Sélection du node actif
comp.SetActiveTool(object)
#S'il est actif ont récupère ses propriétés
print(comp.GetAttrs('COMPH_ActiveTool'))
print(comp.ActiveTool())
Déplacament du node
# Choix du flow
flow = comp.CurrentFrame.FlowView
# Sélection du node
object = comp.Loader({'Clip' : 'C:\\Temp\\image.png'})
# Assign sa position
flow.SetPos(object, 5, 5)
# Connaitre sa position
flow.GetPos(object) # il renvoie seulement X position
flow.GetPosTable(object) # il renvoie X et Y comme dictionnaire
# Créér groupe
group = comp.GroupOperator({})
# Assigner un Nom au groupe
group.SetAttrs({'TOOLS_Name': 'GroupName'})
# Ajouter node au groupe
object.SetAttrs({'TOOLH_GroupParent': group})
Calculer les images de la compo
comp.Render(True, 0, 1) # Render(wait_for_render, renderstart, renderend, step, proxy, hiQ, mblur)
# Start time
comp.SetAttrs({'COMPN_RenderStart': 0.0}) # int = render start time in frames
# End time
comp.SetAttrs({'COMPN_RenderEnd': 50}) # int = render end time in frames
# Global start time
comp.SetAttrs({'COMPN_GlobalStart': 10}) # int = global start time in frames
# Global end time
comp.SetAttrs({'COMPN_GlobalEnd': 10}) # int = global end time in frames
# Calculer en "silent Mode" (backgound rendering)
comp.SetAttrs({'COMPB_Rendering': True})
comp.SetAttrs({'COMPB_Rendering': False})
# Verbose du calcul
print(comp.GetAttrs('COMPN_RenderStartTime'))
print(comp.GetAttrs('COMPN_RenderEndTime'))
start_frame = self.comp.SetAttrs({'COMPN_GlobalStart' : newGlobalIn})
end_frame = self.comp.SetAttrs({'COMPN_GlobalEnd' : newGlobalOut})
rnd_start_frame = self.comp.SetAttrs({'COMPN_RenderStart': newGlobalIn})
rnd_end_frame = self.comp.SetAttrs({'COMPN_RenderEnd': newGlobalOut})
loaderName = "name of your loader"
toollist = comp.GetToolList().values()
for tool in toollist:
if tool.GetAttrs("TOOLS_RegID") == "Loader":
if tool.GetAttrs('TOOLS_Name') == loaderName:
newGlobalIn = int(tool.GlobalIn[0])
newGlobalOut = int(tool.GlobalOut[1])
else:
print (loaderName, "not found !")
start_frame = comp.SetAttrs({'COMPN_GlobalStart' : newGlobalIn})
end_frame = comp.SetAttrs({'COMPN_GlobalEnd' : newGlobalOut})
rnd_start_frame = comp.SetAttrs({'COMPN_RenderStart': newGlobalIn})
rnd_end_frame = comp.SetAttrs({'COMPN_RenderEnd': newGlobalOut})
toollist = comp.GetToolList().values()
tool = []
for tool in toollist:
if tool.GetAttrs("TOOLS_RegID") == "Loader":
loaderPath = tool.GetAttrs("TOOLST_Clip_Name")
loaderPathClean = loaderPath[1]
# Extract, split, via ".split(os.sep)" require import os
loaderPathSplit = loaderPathClean.split(os.sep)
# Listing files
LoaderPathDir = os.listdir(loaderPathSplit[0])
self.comp.Lock() # lock comp durring loop operation
#refresh loader
tool.SetAttrs({'TOOLB_PassThrough' : True})
tool.SetAttrs({'TOOLB_PassThrough' : False})
# Check file in directory, then apply frame missing,
LoaderPathDir = os.listdir(reLoadPath)
if len(LoaderPathDir) == 0:
# Write comment on loader
tool.Comments = "NO FOOTAGE"
# select missing frame : 0: Fail, 1:Hold previous, 2: Output Color, 3: Wait
tool.MissingFrames = 2
# Add tile color to the loader for warning users
tool.TileColor = {'R' : 255/255, 'G' : 85/255, 'B' : 0/255}
# If got one file to the directory put loop
if len(LoaderPathDir) == 1:
tool.Loop
# If TOOLS_Name is good then force frame in - frame out
if tool.GetAttrs('TOOLS_Name') == "we_suck_less":
# don't forget to force integer
newGlobalIn = int(tool.GlobalIn[0])
newGlobalOut = int(tool.GlobalOut[1])
self.comp.Unlock()
# Load a composite
compFilepath = fusion.MapPath("Temp:/example.comp")
cmp = fusion.LoadComp(compFilepath)
# The comp.Print() command will write the result into the Console tab of the active composite
cmp.Print("[Opened Comp] " + str(cmp.GetAttrs()["COMPS_FileName"]))
# Generate a date and time stamp
import datetime
currentDate = datetime.datetime.now().strftime("%Y-%m-%d %H.%M %p")
# Generate a filename with a date and time stamp on it that is going to be written to the %TEMP% / $TMPDIR folder on your system
compFilepath = fusion.MapPath("Temp:/" + str(currentDate) + " example.comp")
# Save the comp to disk
comp.Save(compFilepath)
# The comp.Print() command will write the result into the Console tab of the active composite
comp.Print("[Saved Comp] " + str(comp.GetAttrs()["COMPS_FileName"]))
from pprint import pprint
compList = fusion.GetCompList()
pprint(compList.items())
- Balancer les comp ouvertes dans un dict
# Scan the list of comp items
compList = fu.GetCompList()
for key,value in sorted(compList.items()):
print key, value
print("[Close All Comps]")
compList = fu.GetCompList()
for key, cmp in sorted(compList.items()):
print(str(cmp.GetAttrs()["COMPS_FileName"]))
# If the comp is unlocked, it will ask if the comp should be saved before closing.
cmp.Unlock()
# Close the active comp
cmp.Close()
# Check out the Fusion Attributes
from pprint import pprint
pprint(fu.GetAttrs())
Attributs des Class Fusion : Eyeon:Script/Reference/Applications/Fusion/Classes/Fusion - VFXPedia
# Stop Loader/Saver node file dialogs from showing
comp.Lock()
# Translate a relative PathMap location into a filepath:
macroFilePath = comp.MapPath('Macros:/example.setting')
print('[Macro File] ' + macroFilePath)
# Read the macro file into a variable
macroContents = bmd.readfile(macroFilePath)
print('[Macro Contents]\n' + str(macroContents))
# Add the macro to your foreground comp
comp.Paste(macroContents)
# Allow Loader/Saver node file dialogs to show up again
comp.Unlock()
Récupérer le texte du node Text1 :
textNode = comp.Text1()
print textNode.GetInput("StyledText")
print textNode.GetInput("Size")
Pour injecter une nouvelle valeur:
textNode({'StyledText' : 'bla bla bla'})
Récupérer les metadata du Loader1
metadata = comp.Loader1.Output.GetValue().Metadata
for field in metadata:
print field
print metadata['worldToNDC']
compdir = os.path.dirname(comp.GetAttrs()['COMPS_FileName'])
# methode decomposer
toollist = comp.GetToolList().values()
for tool in toollist:
if (tool.GetAttrs()['TOOLS_RegID']) == "Saver":
tool.SetAttrs({'TOOLB_PassThrough': True})
# recuperer tous les saver
all_savers = comp.GetToolList(False, "Saver").values()
selectedTools = comp.GetToolList(True)
loaders=[]
widths = []
heights = []
for tool in selectedTools.values():
if tool.GetAttrs()['TOOLS_RegID'] =='Loader':
loaders.append(tool)
widths.append(tool.GetAttrs('TOOLIT_Clip_Width')[1])
heights.append(tool.GetAttrs('TOOLIT_Clip_Height')[1])
import subprocess
subprocess.Popen(["C:/Program Files/eyeon/Fusion x.x/eyeonScript.exe", "x:/path/of/your/cripts/YourScript.eyeonscript"], shell=True, stdout=subprocess.PIPE)
#Récupérer le PATH de tous les Loaders de la comp
toollist = self.comp.GetToolList().values()
for tool in toollist:
if tool.GetAttrs("TOOLS_RegID") == "Loader":
...Do stuff...
for tool in toollist:
if tool.GetAttrs('TOOLS_Name') == 'nodeName':
#flow = self.comp.CurrentFrame.FlowView
#flow.Select(tool)
L_view = self.comp.GetPreviewList()["Left"]
R_view = self.comp.GetPreviewList()["Right"]
L_view.ViewOn(tool)
L_view.View.SetScale(0)
R_view.ViewOn(tool)
R_view.View.SetScale(0)