OpenTimelineIO导入FCP XML无法定位剪辑,如何解决?
问题
我有多个Final Cut Pro和Adobe Premiere项目需要转换为Kdenlive格式,使用OpenTimelineIO编写转换代码后,Kdenlive中项目的剪辑布局与时间码正确,但所有剪辑均缺失。XML文件包含外部驱动器的媒体路径,却被OpenTimelineIO识别为MissingReference,替换%20为空格后问题仍存在。请问如何修改XML文件或通过猴子补丁代码让程序找到外部驱动器中的剪辑?
代码示例:
import opentimelineio as otio fp = "/path/to/file/ep4_fixed.fcpxml" output_fp = fp.replace(".fcpxml", ".kdenlive") assert fp != output_fp # Load the FCP XML file input_otio = otio.adapters.read_from_file(fp) # Extract the first Timeline from the SerializableCollection timeline = next((item for item in input_otio if isinstance(item, otio.schema.Timeline)), None) for item in input_otio: if isinstance(item, otio.schema.Timeline): print("Timeline found") print(item) else: print("Not a timeline: ", item) if timeline: # Save the timeline as a Kdenlive project otio.adapters.write_to_file(timeline, output_fp, adapter_name="kdenlive") else: print("No timeline found in the input file.")
XML资产示例:
<asset id="r117" name="Duck" uid="8E011DED69D190CFE0018A6D24E90A31" start="0s" duration="518344/44100s" hasAudio="1" audioSources="1" audioChannels="2" audioRate="44100"> <media-rep kind="original-media" sig="8E011DED69D190CFE0018A6D24E90A31" src="file:///Volumes/Video/BulgyPT.fcpbundle/ep1-6/Original%20Media/Duck.mp3"> <bookmark>Ym9va8AEA...EAAAAAAAAAA==</bookmark> </media-rep> <metadata> <md key="com.apple.proapps.mio.ingestDate" value="2020-07-05 18:19:58 +0100"/> </metadata> </asset>
对应OTIO输出:
otio.schema.Clip(name='Duck', media_reference=otio.schema.MissingReference(name='', available_range=None, available_image_bounds=None, metadata={}), source_range=otio.opentime.TimeRange(start_time=otio.opentime.RationalTime(value=0, rate=25), duration=otio.opentime.RationalTime(value=170, rate=25)), metadata={})
解决方案
方案一:修改XML文件路径格式
OTIO对file://开头的URL路径解析存在兼容性问题,直接改用本地文件系统路径即可:
- 将XML中
media-rep标签的src属性值从file:///Volumes/Video/...格式改为纯本地路径,比如/Volumes/Video/BulgyPT.fcpbundle/ep1-6/Original Media/Duck.mp3(注意把%20替换为真实空格) - 确保路径中没有其他URL编码字符,所有特殊字符直接用原始符号
方案二:代码层面修复路径解析
方法1:全局猴子补丁替换路径解析逻辑
在加载XML前,替换OTIO的媒体引用解析逻辑,自动修复MissingReference:
import opentimelineio as otio from urllib.parse import unquote # 猴子补丁:重写read_from_file,自动修复媒体路径 original_read = otio.adapters.read_from_file def patched_read(file_path, **kwargs): # 正常加载项目 project = original_read(file_path, **kwargs) # 递归修复所有剪辑的媒体引用 def fix_media(item): if isinstance(item, otio.schema.Clip) and isinstance(item.media_reference, otio.schema.MissingReference): # 这里可根据实际场景从XML或元数据提取真实路径,示例用给定路径 original_url = "file:///Volumes/Video/BulgyPT.fcpbundle/ep1-6/Original%20Media/Duck.mp3" local_path = unquote(original_url.replace("file://", "")) # 替换为有效外部引用 item.media_reference = otio.schema.ExternalReference( target_url=local_path, available_range=item.source_range ) # 处理嵌套轨道 if hasattr(item, "children"): for child in item.children: fix_media(child) # 处理整个项目集合或时间线 if isinstance(project, otio.schema.SerializableCollection): for elem in project: fix_media(elem) else: fix_media(project) return project # 替换原函数 otio.adapters.read_from_file = patched_read # 原有转换代码 fp = "/path/to/file/ep4_fixed.fcpxml" output_fp = fp.replace(".fcpxml", ".kdenlive") assert fp != output_fp input_otio = otio.adapters.read_from_file(fp) timeline = next((item for item in input_otio if isinstance(item, otio.schema.Timeline)), None) if timeline: otio.adapters.write_to_file(timeline, output_fp, adapter_name="kdenlive") else: print("输入文件中未找到时间线。")
方法2:解析XML建立路径映射,精准修复
这种方法更可靠,先从XML中提取所有资产的路径,再映射到OTIO的剪辑上:
import xml.etree.ElementTree as ET import opentimelineio as otio from urllib.parse import unquote # 第一步:解析XML,建立asset ID到本地路径的映射 xml_path = "/path/to/file/ep4_fixed.fcpxml" tree = ET.parse(xml_path) root = tree.getroot() asset_path_map = {} for asset in root.findall(".//asset"): media_rep = asset.find(".//media-rep[@kind='original-media']") if media_rep and "src" in media_rep.attrib: asset_id = asset.attrib["id"] # 解码URL路径为本地路径 local_path = unquote(media_rep.attrib["src"].replace("file://", "")) asset_path_map[asset_id] = local_path # 第二步:加载OTIO项目并修复媒体引用 input_otio = otio.adapters.read_from_file(xml_path) def fix_clips(item): if isinstance(item, otio.schema.Clip): # 从剪辑元数据中获取对应的asset ID(FCPXML导入后会保留该字段) asset_id = item.metadata.get("fcpxml", {}).get("asset_id") if asset_id in asset_path_map: # 替换MissingReference为有效外部引用 item.media_reference = otio.schema.ExternalReference( target_url=asset_path_map[asset_id], available_range=item.source_range ) # 递归处理子元素 if hasattr(item, "children"): for child in item.children: fix_clips(child) # 遍历所有项目元素修复 for elem in input_otio: fix_clips(elem) # 第三步:保存为Kdenlive项目 timeline = next((item for item in input_otio if isinstance(item, otio.schema.Timeline)), None) if timeline: output_fp = xml_path.replace(".fcpxml", ".kdenlive") otio.adapters.write_to_file(timeline, output_fp, adapter_name="kdenlive") else: print("输入文件中未找到时间线。")
内容的提问来源于stack exchange,提问作者ginjaemocoes
相关产品推荐
相关产品推荐

