Page 1 of 1


PostPosted: Sat Oct 03, 2015 8:54 pm
by Kirill Mukha
Do Blackmagic Design planning support FCP X .fcpxml 1.5 in Fusion and Fusion Studio?

Re: .fcpxml

PostPosted: Mon Oct 05, 2015 8:12 pm
by Rony Soussan
No, not directly. I wrote my own fcpxml parser for Generation (Fusion Suite)

consider it an example of how to parse, but it's only part of a complete pipeline solution
UPDATE: had only a method pasted before, here is the complete example 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: .fcpxml

PostPosted: Tue Oct 06, 2015 11:00 am
by Jeff Ha
just run it through resolve for an export xml. With apple constantly changing their own spec with each patch, it's a PITA to support fcpxml, which is typical Apple. I also heard Automatic Duck is back in the game but I think for Adobe products.

Re: .fcpxml

PostPosted: Thu Oct 08, 2015 5:17 pm
by Kirill Mukha
Thank you, Rony! I will try your script.

Thank you, Jeff! I also export xml from DaVinci Resolve.

AutomaticDuck not actual now, for After Effects and Nuke better choice is ClipExporter, imho.
May be later ClipExporter will support Fusion and may be DaVinci Resolve also will have "Send To Fusion" :D

Re: .fcpxml

PostPosted: Fri Oct 09, 2015 4:26 pm
by Milos Labski
first beta version of "XML to Fusion" script was published today on "We suck less" forum. Supports FCP XML up to version 5.

Re: .fcpxml

PostPosted: Fri Oct 09, 2015 6:28 pm
by Kirill Mukha
Very interesting, Milos!
Why you use scale? Or this transformation (scale/move/crop)?
How about retime? Colour Correction? Keying?
Do you can share script?

Re: .fcpxml

PostPosted: Sun Oct 11, 2015 8:48 am
by Milos Labski
Why you use scale?

Rescale nodes are created, if resolution of imported clip differs from fusion composition resolution - mainly for preview purposes. You have still the option to delete all rescale nodes with two clicks. :D
Ok, other option would be change of scale values on merge nodes - this is probably the better way to do it. Thanks.
How about retime? Colour Correction? Keying?

Time remapping effects are not processed. maybe in next version. Color correction - analysing of all proprietary color-correction tools from 3 applications and translate them to fusion tools would be probably a task too big for any software company on the market. For me too.
The main purpose of the script is the import of trimmed clips from sequences, created by editing applications such Premiere and Final cut - or even Resolve. I think, deeper integration between Fusion and Resolve can and should be done by BMD only.
Do you can share script?

It's a free script, so you can download it anytime from "We suck less". There are also some screenshots, showing how the script is working. I would appreciate any suggestion or improvement ideas.

Re: .fcpxml

PostPosted: Sun Oct 11, 2015 5:52 pm
by Kirill Mukha
Milos Labski wrote:It's a free script, so you can download it anytime

Thank you! How about github version? :) Other fusioner may help improve script.