MLT XML 格式
前言
这是 MLT 项目的 XML 序列化/反序列化格式——因此,它与 MLT API 的内部结构紧密对应。
如果你想直接查看 DTD,请参见 mlt/src/modules/xml/mlt-xml.dtd,安装后它位于 $(prefix)/share/mlt/modules/xml/mlt-xml.dtd。目前,XML 解析器 不做合法性校验(non-validating)。
简介
一个 MLT XML 文档本质上是一列『生产器』(producers)——生产器是一种 MLT 对象,用于产生 mlt_frames(图像及关联的音频采样)。
生产器有 3 种类型:
- 基本生产器(Basic Producers)——通常是面向文件或设备的输入源;
- 播放列表(Playlists)——多个生产器的编排;
- 多轨道(Multitracks)——特效的封装容器。
在 MLT 模型中,生产器被创建并挂接到『消费器』(consumers)上—— 消费器是软件播放组件(例如 SDL),或硬件驱动的封装(例如 bluefish),甚至可以是 XML 序列化消费器本身(后者不接收帧——它只是 询问所连接生产器的配置)。
尽管 MLT XML 最初定义为已实例化 MLT 组件的序列化机制, 但本文档将着重介绍如何手工编写 MLT XML 文档。
规则
如本文档后续内容所示,编写 MLT XML 的基本原则是按照以下 方式组织文档:
- 为项目中的每个唯一媒体片段创建 producer 元素;
- 为每条轨道创建播放列表(playlist);
- 创建多轨道(multitrack)并指定滤镜(filters)和转场(transitions);
- 添加全局滤镜。
虽然 MLT XML 还有其他用法,但此处采用的方式是为了在复杂项目中 最大化效率。
基本生产器
最简单的 MLT XML 文档是:
<mlt>
<producer id="producer0">
<property name="resource">clip1.dv</property>
</producer>
</mlt>这里的 XML 包装当然是多余的——用 MLT 加载此文档 与直接加载该片段完全相同。
当然,你可以指定更多属性。例如,考虑一个 包含多条音轨的 MPEG 文件——你可以定义一个 MLT XML 文档来 确保加载第二条音轨:
<mlt>
<producer id="producer0">
<property name="resource">clip1.mpeg</property>
<property name="audio_track">1</property>
</producer>
</mlt>这依赖于该 mpeg 由 avformat 生产器处理,而不是 mcmpeg 生产器。详见 [Documentation#Plugins_Services (opens in a new tab)]。
一个更有用的例子是使用 pango 生产器作为文本生产器。
注意:
- 定义基本生产器时最好不要指定 in/out 入出点,因为这些可以在播放列表中指定。理由 在于 in/out 会限制片段可用的范围,如果你在其他位置 需要同一片段的不同区段,可能导致同一片段被重复 加载多次;
- MLT XML 文档本身可以作为 resource 指定,因此 XML 文档可以 自然地嵌套封装其他 XML 文档。
播放列表
播放列表为生产器提供了一种『集合』结构。它们可以在多轨道方案中 用于定义『轨道』,也可以用作单轨道顺序播放的简单播放列表。
例如下面这个例子定义了两个基本生产器和一个包含 3 个条目的 播放列表:
<mlt>
<producer id="producer0">
<property name="resource">clip1.dv</property>
</producer>
<producer id="producer1">
<property name="resource">clip2.dv</property>
</producer>
<playlist id="playlist0">
<entry producer="producer0" in="0" out="2999"/>
<entry producer="producer1" in="0" out="999"/>
<entry producer="producer0" in="3000" out="6999"/>
</playlist>
</mlt>这里可以看到播放列表如何定义基本生产器的 in/out 入出点。
注意:
- 所有 in/out 入出点都是相对于被追加到播放列表中的生产器的 绝对帧位置;
- MLT XML 文档目前是针对某一特定规格化(normalization)编写的;
- 文档中最后一个『producer』是播放的默认对象;
- 播放列表可以多次引用同一个生产器。in/out 区段 不必连续——重复和跳过都是允许的。
引入多轨道
到目前为止,我们已经定义了基本生产器和播放列表/轨道——tractor(牵引器) 是让我们编排轨道并指定滤镜和转场的元素。与播放列表类似,tractor 也是一个容器。
请注意,MLT 并不把滤镜或转场视为通常意义上的生产器—— 在 seek(定位)方面,滤镜和转场是被动的。 在内部,seek 是在生产器上执行的。这一点很重要—— MLT 不遵循传统的图(graph)导向模型。
可视化 MLT tractor 及其与消费器的交互会有助于理解:
+----------------------------------------------+
|tractor |
| +----------+ +-+ +-+ +-+ +-+ |
| |multitrack| |f| |f| |t| |t| |
| | +------+ | |i| |i| |r| |r| |
| | |track0|-|--->|l|- ->|l|- ->|a|--->|a|\ |
| | +------+ | |t| |t| |n| |n| \ |
| | | |e| |e| |s| |s| \ |
| | +------+ | |r| |r| |i| |i| \ | +--------+
| | |track1|-|- ->|0|--->|1|--->|t|--->|t|-----|--->|consumer|
| | +------+ | | | | | |i| |i| / | +--------+
| | | | | | | |o| |o| / | ^
| | +------+ | | | | | |n| |n| / | |
| | |track2|-|- ->| |- ->| |--->|0|- ->|1|/ | |
| | +------+ | | | | | | | | | | |
| +----------+ +-+ +-+ +-+ +-+ | |
+----------------------------------------------+ |
^ |
| |
+-----------+ |
|APPLICATION|--------------------------------------------+
+-----------+在内部,所有轨道的全部帧都会流经所有滤镜和 转场——它们被告知要处理哪些轨道以及轨道的哪些 区段。
请注意,应用程序是与生产器通信的——它可以改变 播放速度、位置,甚至改变哪个生产器连接到哪个 消费器。
消费器接收第一个非空白帧(blank frame,见下文)。它无法 决定帧的到达顺序(与 melt 一起使用的 sdl2 消费器 看起来可能是个例外——其实不是——它只是有一条回通应用程序的路径, 以便应用程序解释按键)。
Tractor(牵引器)
要创建多轨道 XML,我们可以使用两个播放列表并引入一个 tractor。为演示起见,我这里还加了一个滤镜:
<mlt>
<producer id="producer0">
<property name="resource">clip1.dv</property>
</producer>
<producer id="producer1">
<property name="resource">clip2.dv</property>
</producer>
<playlist id="playlist0">
<entry producer="producer0" in="0" out="2999"/>
<blank length="1000"/>
<entry producer="producer0" in="3000" out="6999"/>
</playlist>
<playlist id="playlist1">
<blank length="3000"/>
<entry producer="producer1" in="0" out="999"/>
</playlist>
<tractor id="tractor0">
<multitrack>
<track producer="playlist0"/>
<track producer="playlist1"/>
</multitrack>
<filter>
<property name="track">0</property>
<property name="mlt_service">greyscale</property>
</filter>
</tractor>
</mlt>这里可以看到空白帧(blank)被插入第一个播放列表,并且第二个 播放列表开头留有一段空白——这可以在传统的时间线控件中 可视化为:
+-------+ +-------------+
|a | |a |
+-------+---+-------------+
|b |
+---+在顶部轨道添加滤镜后,得到:
+-------+ +-------------+
|a | |a |
+-------+---+-------------+
|greyscale |
--------+---+-------------+
|b |
+---+注意,滤镜只应用于顶部轨道的可见部分。
需要将滤镜应用于输出而非特定轨道的需求,引出了上文 『规则』一节的最后一项。举个例子, 假设我们想给所有输出加水印,那么可以使用以下 方式:
<mlt>
<producer id="producer0">
<property name="resource">clip1.dv</property>
</producer>
<producer id="producer1">
<property name="resource">clip2.dv</property>
</producer>
<playlist id="playlist0">
<entry producer="producer0" in="0" out="2999"/>
<blank length="1000"/>
<entry producer="producer0" in="3000" out="6999"/>
</playlist>
<playlist id="playlist1">
<blank length="3000"/>
<entry producer="producer1" in="0" out="999"/>
</playlist>
<tractor id="tractor0">
<multitrack>
<track producer="playlist0"/>
<track producer="playlist1"/>
</multitrack>
<filter>
<property name="track">0</property>
<property name="mlt_service">greyscale</property>
</filter>
</tractor>
<tractor id="tractor1">
<multitrack>
<track producer="tractor0"/>
</multitrack>
<filter>
<property name="mlt_service">watermark</property>
<property name="resource">watermark1.png</property>
</filter>
</tractor>
</mlt>这里我们采用了另一个 tractor,它只定义了一条轨道(即我们先前 定义的 tractor),并在其上应用了一个水印滤镜。
这只是作为一个示例——水印功能其实最好在播放 阶段本身处理(即作为自动放置在所有生产器与消费器 之间的滤镜)。
轨道的作用就像 GIMP 这类图像处理程序中的『图层』。 最底部的轨道优先级最高,较高的图层是叠加层, 除非下层存在空隙,或者除非在指定区段应用了 合并各轨道的转场,否则它们不会显示。 就实际使用而言,对 A/B 视频剪辑来说这影响不大, 它会按预期工作;不过,作为一般规则,请将任何 CGI (用 pixbuf 做的图形叠加或用 pango 做的字幕)放在 高于视频轨道的轨道上。同时这意味着,任何位于视频轨道 之下的纯音频轨道会播放自身,而不是视频片段中的音频。 请记住,在应用转场或相应滤镜之前,混音或合成等 效果都不会发生。
<mlt>
<producer id="producer0">
<property name="resource">clip1.dv</property>
</producer>
<playlist id="playlist0">
<entry producer="producer0"/>
</playlist>
<producer id="producer1">
<property name="resource">clip2.mpeg</property>
</producer>
<playlist id="playlist1">
<blank length="50"/>
<entry producer="producer1"/>
</playlist>
<tractor id="tractor0" in="0" out="315">
<multitrack id="multitrack0">
<track producer="playlist0"/>
<track producer="playlist1"/>
</multitrack>
<transition id="transition0" in="50" out="74">
<property name="a_track">0</property>
<property name="b_track">1</property>
<property name="mlt_service">luma</property>
</transition>
<transition id="transition1" in="50" out="74">
<property name="a_track">0</property>
<property name="b_track">1</property>
<property name="mlt_service">mix</property>
<property name="start">0.0</property>
<property name="end">1.0</property>
</transition>
</tractor>
</mlt>『luma』转场是一种视频划像(wipe)处理器,使用灰度位图 来定义划像形状。当不指定位图时,luma 执行的是叠化 (dissolve)。『mix』转场做音频混合,它会在 start 和 end 属性之间对增益缩放因子进行插值—— 在本例中,从 0.0(完全取 A 轨,不含 B 轨)到 1.0(全部取 B 轨)。 由于底部轨道一开始用 <blank> 元素留出空隙,上方轨道 会在空白片段期间显示出来。参见演示和 plugins (opens in a new tab) 以了解内置转场的 能力范围。
灵活性
上述内容被视为 MLT XML 的『常规』(normal) 形式。例如,与 melt 一起使用时,xml 消费器生成的就是这种 输出。演示脚本中的『XML to File』消费器生成的也是这种 输出,初学者会发现它对学习使用 MLT XML 最有帮助。本节介绍 xml 生产器接受的其他替代形式。
首先,常规形式更像一种线性格式:生产器 和播放列表是在多轨道中使用它们之前先行定义的。生产器 也接受层次化格式:生产器作为 track 或播放列表 entry 的子元素,播放列表作为 track 的子元素:
<mlt>
<tractor>
<multitrack>
<track>
<playlist>
<entry>
<producer>
<property name="resource">clip1.dv</property>
</producer>
</entry>
</playlist>
</track>
</multitrack>
</tractor>
</mlt>显然,这个例子旨在演示层次结构,而非播放列表或多轨道的 高效用法!
其次,作为错误处理的一部分,如果你在可以推断的位置 省略了 <tractor>、<track> 和 <entry>,生产器会宽容处理。 这带来一种更简短的语法,冗长度更低,对人类阅读和理解 也许更不令人生畏。上面的例子可以简化为:
<mlt>
<multitrack>
<playlist>
<producer>
<property name="resource">clip1.dv</property>
</producer>
</playlist>
</multitrack>
</mlt>是的,滤镜和转场可以在闭合的 multitrack 标签(</multitrack>)之后添加到上面的例子中,因为它仍然 包含在 mlt 正文标签之内。
如果你在生产器上指定了 in 和 out,且它被包含在 <entry> 或 <playlist> 内,那么这些编辑点适用于播放列表 条目而不是生产器本身。这有助于媒体复用:
<playlist>
<producer id="clip1" in="25" out="78">
<property name="resource">clip1.dv</property>
</producer>
<entry producer="clip1" in="119" out="347"/>
</playlist>在上面的例子中,entry 元素的 producer 属性 是对前面生产器的引用。所有引用必须位于 定义之后。上面生产器上给出的编辑点不会影响 下面引用它的条目,因为生产器知道该片段是 播放列表条目并会对此情况进行优化。好处是无需 预先确定要包含的每个片段,也无需在多轨时间线上下文 之外指定它们。
这种编写方式对许多人来说更容易想象为非线性 编辑器的时间线。下面是一个更复杂的例子:
<mlt>
<multitrack>
<playlist>
<producer id="foo" in="100" out="149">
<property name="resource">clip2.mpeg</property>
</producer>
<blank length="25"/>
<entry producer="foo" in="10" out="59"/>
</playlist>
<playlist>
<blank length="25"/>
<producer id="bar" in="100" out="199">
<property name="resource">clip3.mpeg</property>
</producer>
<entry out="99" producer="bar"/>
</playlist>
</multitrack>
<filter mlt_service="greyscale" track="0"/>
<transition mlt_service="luma" in="25" out="49" a_track="0" b_track="1"/>
<transition mlt_service="luma" in="75" out="99" a_track="0" b_track="1">
<property name="reverse" value="1"/>
</transition>
</mlt>注意到最后一个例子中的不同之处了吗?属性也可以 用元素的 XML 属性来表达。不过,通过这种方式 只支持非服务专用的属性。例如, 『mlt_service』对任何生产器、滤镜或转场都可用,而 『resource』实际上是服务专用的。请注意最后一个 转场上最后一个属性的语法。生产器既接受用 『value』属性表达的属性值,也接受用元素文本表达。
我们已经看到了几种表达属性值的方式。对于 可以接受 XML 数据的属性,还有另外两种方式。例如, 带 librsvg 的 GDK pixbuf 生产器可以处理嵌入式 SVG,而 Pango 生产器可以处理嵌入式 Pango 标记。你可以用 CDATA 段 包裹嵌入的 XML:
<property name="resource"><![CDATA[ <svg>...</svg> ]]></property>请确保 CDATA 起始标签紧跟在 property 起始标签之后,并且段结束标签紧接在 property 闭合标签之前。
不过,生产器也可以接受内联嵌入的 XML:
<property name="resource">
<svg>
</svg>
</property>目前不做命名空间处理,因此只有当嵌入的 XML 中含有名为 『property』的元素时才会发生冲突,因为 生产器会一直收集嵌入的 XML 直到遇到闭合的 property 标签。
技巧与方法
如果你觉得上述层次化的简短格式直观易懂, 可以从一个简单的模板开始,按需填充和扩展:
<mlt>
<multitrack>
<playlist>
</playlist>
...add a playlist for each track...
</multitrack>
...add filters and transitions...
</mlt>为每条轨道使用一个播放列表,在项目开发过程中 迭代地添加新片段和空白区域会更加容易。你不必 使用 <track>,也不必等到后面再补加 <playlist>。
一个更高级、支持多轨道顺序编排的模板是:
<playlist>
<entry>
<multitrack>
<playlist>
</playlist>
...add a playlist for each track...
</multitrack>
...add filters and transitions...
</entry>
<entry>
<multitrack>
<playlist>
</playlist>
...add a playlist for each track...
</multitrack>
...add filters and transitions...
</entry>
</playlist>如果你最终为各种情形做了一套模板集合,那么 可以考虑使用 XML 实体(Entities),把应当参数化的内容 移入实体,让模板更有效。
如果你想要一个无声、黑色背景用于音频和视频淡入淡出, 那么把顶部轨道简单地设为 <producer mlt_service=”colour”/>。然后 使用 composite(合成)和 volume(音量)效果。参见 『Fade from/to black/silence』演示作为示例(demo/mlt_fade_black)。
如果你把 reverse=1 属性应用于像『luma』这样的转场, 请小心,因为它同时固有地交换了 A 轨和 B 轨的角色。 因此,如果效果不符合你的预期,你可能需要交换 a_track 和 b_track 的值。参见『Clock in and out』 示例(demo/mlt_clock_in_and_out)。