Python使用复杂的层次结构解析XML - Nuke9.0v8

时间:2016-03-01 20:41:10

标签: python xml virtual-reality nuke adobe-premiere

我正在使用NukeX9.0v8,Adobe Premiere Pro CC 2015和核心内部python中断器。

# Result: 2.7.3 (default, Jul 24 2013, 15:50:23) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]

我是一名vfx艺术家,我试图围绕解析xml文件的最佳方法,以便:创建文件夹结构,批量创建.nk comp文件并插入特定部分中的数据当我制作我的.nk comps。我已经掌握了如何独立完成这些事情,但是将它们全部插入,并尝试在这样复杂的解析中找到教程,让我停下来。

我知道这个范围很大,但任何小小的建议都值得赞赏。

现在我有一个nuke comp,它有一个节点树,它接收相机输入并将它们拼接成360度视频的latlong图像,我将把它包装成一个Gizmo,用于每种不同类型的装备配置。这只是简化了创建的.nk文件,我可以公开我可以将数据输入的Gizmo部分。

每天我们都会从拍摄中获得大量的镜头,我们必须为每个镜头制作一个新的.nk补偿,并将其设置为立即渲染。我想要做的是让那些人创建一个首映项目并根据这个文件夹结构组织文件。该首映项目将导出为.xml文件。

首映结构的设计。

  • Day_01(拍摄当天)
  • - ^ - R001(镜头的卷数.R指相机类型)
  • - ^ - R001_C001(镜头名称)
  • --- ^ - Acamera剪辑(文件名的路径,视频点作为帧#)
  • --- ^ - Bcamera剪辑(文件名的路径,视频点作为帧#)
  • --- ^ - Ccamera剪辑(文件名的路径,视频点到帧#)

现在,在Nuke内部的脚本面板中,我可以输入当天xml的信息,以查找当天的内容。然后,假设查看卷的每个文件夹名称,并使用第一个字母(R为RED相机)并查看剪辑文件夹。然后它使用pathurl目录作为驱动器上的摄像机文件,并且还可以获取数据,如xml中存在的入点和出点。如果我更新针脚过程,我也有点输入模板版本。这将告诉nuke comp使用哪个Gizmo。

这是我在Nuke中的小组。

def sesquixmlparse():
'''
This imports the xml file from premiere. It looks for the bin that it is working for today and starts looking in what is inside the bins
It then sees the bins inside and uses them to create nuke scripts with these as inputs
It asks what template version to use for the rig. things change or maybe even get better
'''

# Lets build the Nuke Panel that tells us our inputs
p = nuke.Panel("Sesqui XML Parse for Dailies")
xml_file = 'Daily XML'
daynumber = 'Day_##'
nk_output_dir = 'Directory to build VFX folder structure'
dnx_render_dir = 'Directory for write nodes'
r_template_vr = 'VER1'
g_template_vr = 'VER1'
c_template_vr = 'VER1'

p.addFilenameSearch("Daily XML", xml_file)
p.addSingleLineInput("Bin to process", daynumber)
p.addFilenameSearch("Directory to build VFX folder structure", nk_output_dir)
p.addFilenameSearch("Directory to render from write nodes", dnx_render_dir)
p.addSingleLineInput("3 Red stmap version", r_template_vr)
p.addSingleLineInput("6 Gopro stmap verison", g_template_vr)
p.addSingleLineInput("5 Canon stmap verison", c_template_vr)
p.setWidth(600)
print "Panel created"
if not p.show():
    return

# Assign var from nuke panel user-entered data
xml_file = p.value("Daily XML")
daynumber = p.value("Bin to process")
nk_output_dir = p.value("Directory to build VFX folder structure")
dnx_render_dir = p.value("Directory to render from write nodes")
r_template_vr = p.value("3 Red stmap version")
g_template_vr = p.value("6 Gopro stmap verison")
c_template_vr = p.value("5 Canon stmap verison")
print "var's assigned from panel"

# Create paths for render directory if it does not exist
if not os.path.isdir(dnx_render_dir):
    os.mkdir(dnx_render_dir)
    print  dnx_render_dir + " directory created"
if not os.path.isdir(nk_output_dir):
    os.mkdir(nk_output_dir)
    print nk_output_dir + " directory created"

我对如何最好地阅读xml文件感到茫然。我在DOMelementtree上看到的所有教程都非常基础,并且处理直接代码来读取已知的XML标记并将数据分解为简单的str输出。

我需要输入变量,然后将解析约束到树的特定部分,然后进入未知的层次结构设置并查看内部的内容,然后决定如何处理它找到的内容。

以下是我的测试XML文件的示例。最终的计划是使用其他不同的卷筒类型来引用不同的相机类型,但现在我只使用3台红色摄像机。

这是一个非常大的文件,所以这里是一个pastebin:http://pastebin.com/vLaRA0X8

基本上我想限制脚本在我的变量<bin><name>'daynumber'</name>~~~~</bin>.中查看。在这种情况下查看Day_00 bin。如果根层次结构中还有其他内容,我想将其忽略为序列,未使用的剪辑和其他数据可能变得非常庞大。然后,我想在daynumber&amp;中创建nk_output_dir的目录。 dnx_render_dir这样拍摄日的所有内容都包含在该文件夹中。

XML文件的一个令人讨厌的部分是bin的名称是<bin>本身的子项,因此一旦找到bin名称,该bin的任何<children>将是同一级别树的<name>。我无法找到定位代码的示例代码,然后查看使用同一分支中的代码而不是它的子代。

现在它已找到当天的垃圾箱,我希望它开始寻找<children></children>中的所有垃圾箱。示例为<bin><name>R001</name>~~~</bin>并在nk_output_dir&amp ;;中创建的Day_00文件夹中创建目录。对于在结构的这一部分中找到的每个bin,dnx_render_dir。每次相机重新加载时都会滚动到R002,R003等。另外不同的相机类型如Gopros会创建G001,G002,G003。

然后我想在上面的二进制文件的<children>中查找并找到<bin><name>R001_C001</name>~~~</bin>内的所有邮箱,并在nk_output_dir\日期\~whatever bin this is contained~\~name of this bin~\中创建文件夹。用户创建的卷号和剪辑号。 (R001_C001,R001_C002等)这将是新的剪辑名称,将生成的.nk comp的名称以及写入节点上渲染的文件名。

此处的目标是在我为nk_output_dir选择的目录中重新创建bin文件夹结构。

dnx_render_dir用于插入我的nuke脚本的写节点,稍后将文件呈现到的位置。它是分开的,因为我将拥有一个不同的RAID驱动器,它会随着它们的填充而改变。渲染只需要放在daynumber\~rollnumber~的目录中,但不需要被限制在剪辑名的文件夹中。

这是我真正迷失的地方。现在,因为我必须考虑用户错误,所以我无法完全确定我需要走到树的深度。我知道我想要<pathurl>~</pathurl>我可以插入我制作的.nk(nuke)脚本。使用红色相机文件,它们可以直接在这里.R3D或文件夹结构,可以2-3个箱深。我知道我不能百分之百地依靠那些人来确定他们如何制造这个箱子。

我可以信任他们所做的就是确保它们按正确的字母顺序排列。如果你看一下xml,那么它们的顺序很重要。我也知道如果我正在寻找一个R ### roll bin我需要3 <pathurl></pathurl>如果我正在寻找G ###我需要6而C ###只需要5。

它们的顺序非常重要,因为它们可以重命名`~~~~中的名称标签来重命名设置错误的摄像机而不重命名源文件。 (它打破了其他程序所需的重要元数据)

在树的这一部分,我还想抓住<clip id=~><in>###</in>来抓取标记帧偏移量。如果摄像机不同步,可以设置起点。但当然这个标签不是<pathurl></pathurl>的孩子,实际上是3个父母!此标签也不会出现在每个剪辑上,所以我无法先查找它!

   <clip id="masterclip-40" explodedTracks="true" frameBlend="FALSE">
    <uuid>85f87acc-308f-401e-bf82-55e8ea41e55a</uuid>
    <masterclipid>masterclip-40</masterclipid>
    <ismasterclip>TRUE</ismasterclip>
    <duration>5355</duration>
    <rate>
        <timebase>30</timebase>
        <ntsc>TRUE</ntsc>
    </rate>
    <in>876</in>
    <name>B002_C002_0216AM_002.R3D</name>
    <media>
    <video>
        <track>
            <clipitem id="clipitem-118" frameBlend="FALSE">
                <masterclipid>masterclip-40</masterclipid>
                <name>B002_C002_0216AM_002.R3D</name>
                <rate>
                    <timebase>30</timebase>
                    <ntsc>TRUE</ntsc>
                </rate>
                <alphatype>none</alphatype>
                <pixelaspectratio>square</pixelaspectratio>
                <anamorphic>FALSE</anamorphic>
                <file id="file-40">
                    <name>B002_C002_0216AM_002.R3D</name>
                    <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/B002/B002_0216G4.RDM/B002_C002_0216AM.RDC/B002_C002_0216AM_002.R3D</pathurl>

所以,一旦我解析了所有这些我想要的信息。

  • daynumber中包含的XML的原始bin文件夹结构。获取箱子的名称并在nk_output_dir(Day_00 / R001 / R001_C001等等)中构建相同的文件夹结构
  • 我还想在daynumber文件夹中创建一个dnx_render_dir目录,并为每个参考相机胶卷的bin创建一个目录。
  • 根据剪辑名是否以R,G或C开头,我希望能够访问它以选择要制作的.nk类型。
  • 我想要每个bin中指向剪辑和插件的pathurl信息。如果该剪辑有任何<in>信息,我也想要这些信息。这样我就可以将它插入我的nuke Gizmo的读取节点信息中。

我想,一旦我弄清楚如何解析如此复杂的xml树,我就能够大惊小怪地摸索剩下的过程。

我真的很想找到解析像这样的复杂XML文件的例子。

1 个答案:

答案 0 :(得分:0)

每当遇到复杂的XML时,请考虑使用XSLT脚本将XML转换为更简单的结构。作为信息,XSLT是一种特殊用途的声明性语言(与SQL类型相同),旨在将XML转换为各种结构以满足最终用途需求。与其他通用语言一样,Python维护着一个XSLT处理器,特别是在其lxml模块中。

虽然此转换无法满足您的整体需求,但您可以为Nuke应用程序需求解析更简单的结构。目录和名称被简化并标记为daynumber,rollnumber,shotnames和带有pathurls的剪辑。

XSLT 脚本(另存为.xsl或.xslt,将在下面的.py脚本中引用)

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>

<xsl:key name="idkey" match="ctype" use="@id" />

  <xsl:template match="/">
    <root>
      <xsl:apply-templates select="*"/>
    </root>
  </xsl:template>

  <xsl:template match="xmeml/bin">
    <daynumber><xsl:value-of select="name"/></daynumber>
    <xsl:apply-templates select="children/bin"/>
  </xsl:template>

  <xsl:template match="xmeml/bin/children/bin">
    <roll>
      <rollnumber><xsl:value-of select="name"/></rollnumber>
      <rollnumberdir><xsl:value-of select="concat(ancestor::bin/name, 
                                                  '/', name)"/></rollnumberdir>
      <xsl:apply-templates select="children/bin"/>
    </roll>
  </xsl:template>

  <xsl:template match="xmeml/bin/children/bin/children/bin">
    <shot>
      <shotname><xsl:value-of select="name"/></shotname>
      <shotnamedir><xsl:value-of select="concat(/xmeml/bin/name, '/', 
                               /xmeml/bin/children/bin/name, '/', name)"/></shotnamedir>
      <xsl:apply-templates select="descendant::clip[position() &lt; 4]"/>
    </shot>
  </xsl:template>

  <xsl:template match="clip">
    <clip>
      <clipname><xsl:value-of select="descendant::name"/></clipname>
      <xsl:copy-of select="in"/>
      <pathurl><xsl:value-of select="descendant::pathurl"/></pathurl>
    </clip>
  </xsl:template>

</xsl:transform>

Python 脚本(转换,解析和导出更简单的结构)

#!/usr/bin/python
import lxml.etree as ET

# LOAD INPUT XML AND XSLT
dom = ET.parse('Input.xml'))
xslt = ET.parse('XSLTScript.xsl')

# TRANSFORM XML (SIMPLER NEWDOM CAN BE FURTHER PARSED: ITER(), FINDALL(), XPATH())
transform = ET.XSLT(xslt)
newdom = transform(dom)

# XPATH EXPRESSIONS (LIST OUTPUTS)
daynumber = newdom.xpath('//daynumber/text()')
# ['Day_00']
rolls = newdom.xpath('//rollnumber/text()')
# ['R001', 'R002']
shots = newdom.xpath('//shotname/text()')
# ['R001_C001', 'R002_C001', 'R002_C002']

# CONVERT TO STRING (IF NEEDED)
tree_out = ET.tostring(newdom, encoding='UTF-8', pretty_print=True,  xml_declaration=True)
print(tree_out.decode("utf-8"))

# OUTPUT TO FILE (IF NEEDED)
xmlfile = open('Output.xml'),'wb')
xmlfile.write(tree_out)
xmlfile.close()

TRANSFORMED XML (包含在.py脚本中的newdom对象中)

<?xml version='1.0' encoding='UTF-8'?>
<root>
  <daynumber>Day_00</daynumber>
  <roll>
    <rollnumber>R001</rollnumber>
    <rollnumberdir>Day_00/R001</rollnumberdir>
    <shot>
      <shotname>R001_C001</shotname>
      <shotnamedir>Day_00/R001/R001_C001</shotnamedir>
      <clip>
        <clipname>A002_C001_0216MW_001.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R001/A002/A002_0216FE.RDM/A002_C001_0216MW.RDC/A002_C001_0216MW_001.R3D</pathurl>
      </clip>
      <clip>
        <clipname>A002_C001_0216MW_002.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R001/A002/A002_0216FE.RDM/A002_C001_0216MW.RDC/A002_C001_0216MW_002.R3D</pathurl>
      </clip>
      <clip>
        <clipname>A002_C001_0216MW_003.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R001/A002/A002_0216FE.RDM/A002_C001_0216MW.RDC/A002_C001_0216MW_003.R3D</pathurl>
      </clip>
    </shot>
  </roll>
  <roll>
    <rollnumber>R002</rollnumber>
    <rollnumberdir>Day_00/R002</rollnumberdir>
    <shot>
      <shotname>R002_C001</shotname>
      <shotnamedir>Day_00/R001/R002_C001</shotnamedir>
      <clip>
        <clipname>A003_C001_0216XI_001.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/A003/A003_0216XO.RDM/A003_C001_0216XI.RDC/A003_C001_0216XI_001.R3D</pathurl>
      </clip>
      <clip>
        <clipname>B002_C001_02169H_002.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/B002/B002_0216G4.RDM/B002_C001_02169H.RDC/B002_C001_02169H_002.R3D</pathurl>
      </clip>
      <clip>
        <clipname>C002_C001_02168R_001.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/C002/C002_0216RL.RDM/C002_C001_02168R.RDC/C002_C001_02168R_001.R3D</pathurl>
      </clip>
    </shot>
    <shot>
      <shotname>R002_C002</shotname>
      <shotnamedir>Day_00/R001/R002_C002</shotnamedir>
      <clip>
        <clipname>C002_C002_0216M9_001.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/C002/C002_0216RL.RDM/C002_C002_0216M9.RDC/C002_C002_0216M9_001.R3D</pathurl>
      </clip>
      <clip>
        <clipname>C002_C002_0216M9_002.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/C002/C002_0216RL.RDM/C002_C002_0216M9.RDC/C002_C002_0216M9_002.R3D</pathurl>
      </clip>
      <clip>
        <clipname>C002_C002_0216M9_003.R3D</clipname>
        <pathurl>file://localhost/Volumes/REDLAB_3A/SESQUI/MASTER_FILES/DAY_00/RED/R002/C002/C002_0216RL.RDM/C002_C002_0216M9.RDC/C002_C002_0216M9_003.R3D</pathurl>
      </clip>
    </shot>
  </roll>
</root>