Page 1 of 1

Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Tue Oct 06, 2015 10:26 am
by Jason Bowdach
Hey Fusion community,

Has anyone coded a link between these two applications? or at least have a quick workflow to go between them easily?

If Rony Soussan happens to be in the forum, we discussed this possibility at a local BMD event in Burbank recently. Was just curious if this was possible, even if some aspect of it was automated.

Many thanks,


Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Tue Oct 06, 2015 3:28 pm
by Paul Ingvarsson
Just a note to say i'd be really interested in any developments on this too - i'm sure it's on the cards. The DS integration of editing and effects was incredible, it's frustrating to see these two products (Resolve / Fusion) not fused together even in the most basic way.

A simple copy clip from Resolve and paste into Fusion would be a great start...

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Tue Oct 06, 2015 5:05 pm
by Rony Soussan
Hi guys,

I wrote a simple class in python that you can use as starting point.
The code is not commented well (if any), but it's essentially can strip out the contents of the xml into a dictionary, with methods to create generation projects, and fusion comps etc..

My plan is to have a more complete setup with comments ready for studio8 release, but anyone is welcome to look at the wip.

The class
Code: Select all
import xml.etree.ElementTree as ET
from pprint import pprint as pp
import idEyeon
import os
import tkFileDialog
from Tkinter import *
import easygui

fusion = idEyeon.getFusion()
generation = idEyeon.getGeneration()

# pp (generation.GetPathMap())

proj = generation.ActiveProject
sub = proj.SubGet(0) 
track = sub.TrackGet(0)

_ROOT = './resources'
_FORMAT = '%s/format'%_ROOT
_ASSETS = '%s/asset'%_ROOT
_LIBRARY = './library'
_EVENT = '%s/event'%_LIBRARY
_PROJECT = '%s/project'%_EVENT
_SEQUENCE = '%s/sequence'%_PROJECT
_SPINE = '%s/spine'%_SEQUENCE
_CLIP = '%s/clip'%_SPINE
_VIDEO = '%s/video'%_CLIP

template = r"X:\OneDrive\VfxWork\BMD\Templates\FusionScript_TemplateComps\FXPXML_Template.comp"
# myXml = tkFileDialog.askopenfilename()
# myXml = r"x:\OneDrive\VfxWork\_Dev1\ScriptDev\LearningXML\CitizenChain (Resolve).fcpxml"

class parseFcpXML(object):
  _MAX_KEY = 0
  _maxCount = 0
  _tcMode = 0

  def __init__(self, filepath):
    self._filePath = filepath
    self._ok = False

      self._tree = ET.ElementTree(file=filepath)
      self._root = self._tree.getroot()
      self._ok = True
    except Exception, err:
      self.errors().append( 'Failed to parse file: %s'%filepath)
      self.errors().append( '%s'%err)
      return # verifies if there is an XML

  def getTcFormat(self):
    """evaluates if project is in drop rame mode or 23.9"""

    if self._root.find(_SEQUENCE).get('tcFormat') == "NDF":
      fps= 24
      fps = 23.978
    return fps

  def _frameCalc(self,rawDuration):
    """converts the Final CutX duration formula in frames and seconds"""

    fps = self.getTcFormat() # 24v call method to get projects fps format
    split = rawDuration.split("/") # spliting the format ##/#s into parts
    splitA = int(split[0]) # first number in the format
    splitB = int(split[1].replace("s","")) # striping the s off the format
    mulF = ((fps) / splitB)
    durInFF = (mulF * splitA) # results of above against first number for actuall frame duration
    durInTC = self.frame_to_tc(durInFF)
    return durInFF

  def getEvent(self):
        """ returns the name of timeline even from exported timeline."""

     return self._root.find(_EVENT).get('name')

  def getSeqDur(self):
    return self._root.find(_SEQUENCE).get('duration')

  def getProject(self):
     return self._root.find(_PROJECT).get('name')

  def getClipCount(self):
     count = 0
     for item in self._root.findall(_CLIP):
        count = count +1
   return count

  def frame_to_tc(self,frames):
     return '{0:02d}:{1:02d}:{2:02d}:{3:02d}'.format(frames / (3600*24),
                                                    frames / (60*24) % 60,
                                                    frames / 24 % 60,
                                                    frames % 24)
  def _getSource(self,id):
    """ gets a count of how many clips by ID count """

    for asset in self._root.findall(_ASSETS):
      if asset.get('id') == id:
        src = asset.get('src')
        src = src.replace("%20", " ") # replace with windows friendly space
        src = src.replace("file://localhost/", "")
        return src

  def getDict(self):
    """ build a dict from clip data in xml """

    clipDict = {}
    formatDict = {}
    listDict = []
    shotNumber = int(10)
    for clip in self._root.findall(_CLIP):

      clipRef = (clip.find('video')).get('ref') # establish relationship between the clip and it's source from the ref attribute.
      filePath = self._getSource(clipRef) # var for found sources
      clipDict["filePath"] = filePath
      name = clip.get('name')
      clipDict["Name"] = name
      duration = self._frameCalc (clip.get('duration'))
      clipDict["Duration"] = duration

      clip_offset_TC = (self.frame_to_tc(self._frameCalc (clip.get('offset'))))
      clip_Offset_FF = (self._frameCalc (clip.get('offset')))
      clipDict["(inpoint timeline) clip_offset_TC"] = clip_offset_TC # physical location on the timeline
      clipDict["(inpoint timeline) clip_Offset_FF"] = clip_Offset_FF - 86400

      video_offset_FF = self._frameCalc((clip.find('video')).get('offset'))
      video_offset_TC = self.frame_to_tc(video_offset_FF)
      clipDict["(seq starting FF) video_offset_FF"] = video_offset_FF
      clipDict["(seq starting TC) video_offset_TC"] = video_offset_TC

      clip_Start_FF = (self._frameCalc (clip.get('start')))
      clip_Start_TC = self.frame_to_tc(clip_Start_FF)
      clipDict ["(Clip_trim_in) clip_Start_FF"] = clip_Start_FF
      clipDict ["(Clip_trim_in) clip_Start_TC"] = clip_Start_TC

      fileTrim_OUT_FF = clip_Start_FF + duration
      fileTrim_OUT_TC = self.frame_to_tc(fileTrim_OUT_FF)
      clipDict ["fileTrim_OUT_FF"] = fileTrim_OUT_FF
      clipDict ["fileTrim_OUT_TC"] = fileTrim_OUT_TC

      # we have a list that contains a dict of every flound clip and it's attributes.

    return (listDict)

  def addClip(self,dicList):
    print ""
    for path in dicList:

      filePath = path["filePath"]
      print filePath
      clip = proj.DropSequence(filePath)
      clipM = track.ClipInsert(clip[1])
      clipV = clipM.VersionGet()

      loader = clipV.GetLoader()
      inPVar = path['(Clip_trim_in) clip_Start_FF'] - path['(seq starting FF) video_offset_FF']
      clipV.InPoint = inPVar
      out = path["(inpoint timeline) clip_Offset_FF"] + path["Duration"]

  def printData(self,data):

    pad = 35
    for i in data:
      for k,v in i.iteritems():
        offset = pad - len(k)
        print"-"*offset,("%s: %s") % (k,v)
      print "="*160

  def makeDir(self,path):

       os.makedirs( path )

  def saveProject(self,path):

    dirName = os.path.basename(path)
    print dirName
    projPath = os.path.join(path,"Generation")
    # fullPath = os.path.join(projPath,dirName+".genproj")
    print projPath
    # print fullPath

    fullPath = (projPath+"\\"+dirName+".genproj")
    print fullPath
    print proj

  # def myDialog(self,root):

  #       top = = Toplevel(parent)

  #       Label(top, text="Value").pack()

  #       self.e = Entry(top)
  #       self.e.pack(padx=5)

  #       b = Button(top, text="OK", command=self.ok)
  #       b.pack(pady=5)

  #       print "value is", self.e.get()


class mkComp(object):

  def __init__(self):
    # self._filePath = filepath
    self._ok = False

  def getClipData(self):

    for ClipV in proj.SelectedVersions().values():

      ClipM = ClipV.GetSlotClip()
      path = (ClipV.GetLoader().Filename)
      trimIn = int(ClipV.InPoint)
      length = int(ClipM.Length)
      trimOut = trimIn + length
      clipData =[path,trimIn,trimOut]

      return clipData

  def createFld(self,rootFld,shotNumber):

    prefix = "_VFX_"
    baseName = os.path.basename(rootFld)
    # var = os.path.join(rootFld,baseName+prefix)
    # shotPath = var+ str(shotNumber)

    for folder in folderList:
      # shotPath = var+"10"
      # print "DFLJKSDJFLOJ"
      var = os.path.join(rootFld,baseName+prefix)
      shotPath = var+ str(shotNumber)
      fldName = os.path.join(shotPath,folder)
      print fldName
    return shotPath
  def getPath(self):
    # dirPath = tkFileDialog.askdirectory()
    dirPath = r"X:\Projects\TST"
    return dirPath

  def makeDir(self,path):

       os.makedirs( path )

  def saveComp(self,assetRoot,filePath,trimIn,trimOut):

    shotName = os.path.basename(assetRoot)
    compPath = os.path.join(assetRoot,"comps")
    newComp = fusion.LoadComp( template , False )
    ld = newComp.LD
    sv = newComp.SV
    ld.Clip = filePath
    # ld.DPXFormat.BypassConversion = 1
    ld.ClipTimeStart = trimIn
    ld.ClipTimeEnd = trimOut -1
    ldLenght = (ld.ClipTimeEnd[0] - ld.ClipTimeStart[0])
    baseCompName = os.path.basename(assetRoot)
    compName = baseCompName + "_comp_v01.comp"
    fullPathComp = os.path.join(compPath,compName)
    saveFileName = baseCompName + "_comp_v01..jpg"
    print "saver file name ",saveFileName
    saverPath = os.path.join(assetRoot,"renders","v01")

    print "saverPath ",saverPath
    saverFull = os.path.join(saverPath,compName)
    sv.Clip = saverFull
    # print saverFull
    print "compname",compName
    print "fullPathComp" ,fullPathComp
    return fullPathComp

  def insertVer(self,compPath):

    for clipV in proj.SelectedVersions().values():

      print "HJD"

      # clip = proj.DropSequence(compPath)
      # clipM = track.ClipInsert(clip[1])
      # clipV = clipM.VersionGet()

# shot = mkComp()
# print shot
# pp(shot)

# clipData = shot.getClipPath()
# pp(clipData)
# # # #
# filePath, trimIn, trimOut = clipData
# # print filePath,trimIn,trimOut

# compPath = shot.getPath()
# # print "comppath",compPath

# shotNumber = integerbox(msg='enter shot number for clip', title='New Shot ', default='10')
# assetRoot = shot.createFld(compPath,shotNumber)
# # print "asset root",assetRoot

# newCompPath = shot.saveComp(assetRoot,filePath,trimIn,trimOut)

# # shot.insertVer(newCompPath)

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Tue Oct 06, 2015 9:48 pm
by Paul Ingvarsson
Hi Rony,

Before I bash myself against a wall getting this to work... Will I be able to run this script in Fusion 8 on mac?

I've installed python and can run your script but it's calling for idEyeon module (and I guess plenty more after that)

I must point out that although I am code savvy (PHP etc) Python is totally new to me


Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Tue Oct 06, 2015 10:45 pm
by Rony Soussan
You won't be able to use that script at all, it's there for other scripts to call on, so there is nothing much to run without the support scripts. I wanted to just put it out there for those who wanted to see the parsing code.

I am planning to bundle a complete working example when studio 8 is out.

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Mon Oct 12, 2015 5:47 pm
by Simon Dayan
Hi Ronny,
nice code dude!

import idEyeon???

is there any SDK for fusion that we can download?

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostPosted: Mon Oct 12, 2015 7:36 pm
by Rony Soussan
Thanks, I try (although I don't comment nearly as much as I should)

here is the ideyeon.

It's just a class for binding fusion/generation without having to do it in every script.

Code: Select all
import id_config
import pathParser_one
import re
import sys
import os
from time import strftime
from pprint import pprint as pp
import xml.etree.cElementTree as ET

def getFusion():

   MAX_TRIES = 50

   path_fu = r"S:\Software\eyeon\bin\Fusion\Launchers\Batch_Files\Fusion64.bat"

      import PeyeonScript as eyeon
   except Exception, err:
      print 'Failed to get eyeon PeyeonScript module: \n\t%s'%err

      print 'Fusion: %s'%fusion
   except Exception, err:
      fusion = eyeon.scriptapp("Fusion")

   if not fusion:
      for i in range(MAX_TRIES):
         print ("waiting for fusion to load........")
         fusion = eyeon.scriptapp("Fusion")
         if fusion:

   if not fusion:
      print 'Error: Failed to get a handle to fusion'
   return fusion

def getGeneration():
      import PeyeonScript as eyeon
   except Exception, err:
      print 'Failed to get eyeon PeyeonScript module: \n\t%s'%err

      print 'Generation: %s'%generation
   except Exception, err:
      generation = eyeon.scriptapp("Generation")
   return generation

def getNote(xml):

   doc = ET.parse( xml )
   root = doc.getroot()
   lines = root.findall("./Lines/Line")

   slates = {}
   for ln in lines:
      date = ln.attrib.get('Date')
      note = ln.text
      # pp (slates)
      # pp  (sorted(slates.values())[:1])
   slates = sorted(slates.values())[:1]
   print note

   if "r'_'" in note:
      print "Hrwerwerwerewr"
   return note

print getGeneration()
print getFusion()