Jump to: Board index » General » Fusion

Resolve 12 & Fusion - Easy Workflow \ Script

Learn about 3D compositing, animation, broadcast design and VFX workflows.
  • Author
  • Message
Offline
User avatar

Jason Bowdach

  • Posts: 42
  • Joined: Thu Jun 26, 2014 6:57 pm

Resolve 12 & Fusion - Easy Workflow \ Script

PostTue Oct 06, 2015 10:26 am

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,

Jason
Colorist \ Finishing Artist | www.JasonBowdach.com | www.PixelToolsPost.com
Offline

Paul Ingvarsson

  • Posts: 283
  • Joined: Tue Sep 10, 2013 3:57 pm
  • Location: London, UK

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostTue Oct 06, 2015 3:28 pm

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...
Paul Ingvarsson
StormHD
Strongbox DX-G (Dual e5-2690 v3 64GB)
RTX3090 | Decklink 4K Pro
Eizo CG3145
Resolve 17.3.1
Windows 10 Pro 19042.1202
Offline
User avatar

Rony Soussan

  • Posts: 727
  • Joined: Tue Nov 11, 2014 5:33 pm

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostTue Oct 06, 2015 5:05 pm

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

    try:
      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
    else:
      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

      listDict.append(dict(clipDict))
      # we have a list that contains a dict of every flound clip and it's attributes.

    return (listDict)

  def addClip(self,dicList):
    print ""
    pp(dicList)
    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"]
      clipM.SetOutPoint(out)

  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
    return

  def makeDir(self,path):

    try:
       os.makedirs( path )
    except:
       pass

  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
    self.makeDir(projPath)

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

  # def myDialog(self,root):

  #       top = self.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()

  #       self.top.destroy()



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)
      self.makeDir(fldName)
      print fldName
    return shotPath
  def getPath(self):
    # dirPath = tkFileDialog.askdirectory()
    dirPath = r"X:\Projects\TST"
    return dirPath

  def makeDir(self,path):

    try:
       os.makedirs( path )
    except:
       pass


  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])
    newComp.SetAttrs({'COMPN_GlobalEnd':ldLenght})
    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
    self.makeDir(saverPath)
    saverFull = os.path.join(saverPath,compName)
    sv.Clip = saverFull
    # print saverFull
    newComp.Save(fullPathComp)
    print "compname",compName
    print "fullPathComp" ,fullPathComp
    newComp.Close()
    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)

Offline

Paul Ingvarsson

  • Posts: 283
  • Joined: Tue Sep 10, 2013 3:57 pm
  • Location: London, UK

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostTue Oct 06, 2015 9:48 pm

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

Paul
Paul Ingvarsson
StormHD
Strongbox DX-G (Dual e5-2690 v3 64GB)
RTX3090 | Decklink 4K Pro
Eizo CG3145
Resolve 17.3.1
Windows 10 Pro 19042.1202
Offline
User avatar

Rony Soussan

  • Posts: 727
  • Joined: Tue Nov 11, 2014 5:33 pm

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostTue Oct 06, 2015 10:45 pm

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.
Offline
User avatar

Simon Dayan

  • Posts: 158
  • Joined: Tue Feb 04, 2014 10:39 am
  • Location: West Hollywood, Los Angeles,CA

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostMon Oct 12, 2015 5:47 pm

Hi Ronny,
nice code dude!

import idEyeon???

is there any SDK for fusion that we can download?
Offline
User avatar

Rony Soussan

  • Posts: 727
  • Joined: Tue Nov 11, 2014 5:33 pm

Re: Resolve 12 & Fusion - Easy Workflow \ Script

PostMon Oct 12, 2015 7:36 pm

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"

   try:
      import PeyeonScript as eyeon
   except Exception, err:
      print 'Failed to get eyeon PeyeonScript module: \n\t%s'%err
      exit(-1)

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

      
   if not fusion:
      os.system(path_fu)
      
      for i in range(MAX_TRIES):
         print ("waiting for fusion to load........")
         time.sleep(1)
         fusion = eyeon.scriptapp("Fusion")
         if fusion:
            break;

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


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

   try:
      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
      slates.update({date:note})
      # 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()

Return to Fusion

Who is online

Users browsing this forum: No registered users and 40 guests