前編では、DaVinci Resolveから書き出したOTIOファイルをFlame 2027で読み込み、Match Criteriaの挙動を確認しました。
Source Timecodeではリンクできましたが、Name / File NameではStrict ONからリンクすることができませんでした。
また、ConformリストのNameカラムには、ファイルネームTCと拡張子が付加された名前が表示されていました。
今回は、OTIOファイルの中身を確認しながら、その原因を調べていきます。
OTIOファイルの中身を確認
Rocky LinuxやmacOSでは、fileコマンドを使うことで、ファイルの種類を確認することができます。
[admin@Rocky Documents]$ file edit.otio
edit.otio: ASCII text
[admin@Rocky Documents]$
OTIOファイルはバイナリ形式ではなく、テキストファイルとして保存されていることが分かります。
実際に中身を確認してみましょう。
[admin@Rocky Documents]$ head edit.otio
{
"OTIO_SCHEMA": "Timeline.1",
"metadata": {
"Resolve_OTIO": {
"Resolve OTIO Meta Version": "1.0"
}
},
"name": "",
"global_start_time": {
"OTIO_SCHEMA": "RationalTime.1",
[admin@Rocky Documents]$
先頭を見ると、
"OTIO_SCHEMA": "Timeline.1"
「キー : 値」の形式で情報が記録されています。
JSONファイル形式でよく見られる構造です。OTIOファイルがJSONファイルとして読み込めるか確認してみましょう。
[admin@Rocky Documents]$ python3
Python 3.9.19 (main, Sep 11 2024, 00:00:00)
[GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> data = json.load(open("edit.otio"))
>>> print(type(data)) ----------> dataの型を表示する
<class 'dict'> ----------> dict = dictionary(辞書)
>>>
dictは、Pythonの辞書型(dictionary)のことです。
OTIOファイルがJSONファイル形式で記録されているため、Pythonから辞書型として読み込むことができました。
OTIOファイル内には、タイムラインやクリップなどの情報が記録されています。
まずは、どのような情報が含まれているのか確認してみます。
出力結果は長くなりますが、実際の検索結果をそのまま掲載しています。
[admin@Rocky Documents]$ grep -n OTIO_SCHEMA edit.otio
2: "OTIO_SCHEMA": "Timeline.1", 10: "OTIO_SCHEMA": "RationalTime.1", 15: "OTIO_SCHEMA": "Stack.1", 24: "OTIO_SCHEMA": "Track.1", 37: "OTIO_SCHEMA": "Clip.2", 43: "OTIO_SCHEMA": "TimeRange.1", 45: "OTIO_SCHEMA": "RationalTime.1", 50: "OTIO_SCHEMA": "RationalTime.1", 57: "OTIO_SCHEMA": "Effect.1", 72: "OTIO_SCHEMA": "Effect.1", 87: "OTIO_SCHEMA": "Effect.1", 149: "OTIO_SCHEMA": "Effect.1", 164: "OTIO_SCHEMA": "Effect.1", 179: "OTIO_SCHEMA": "Effect.1", 194: "OTIO_SCHEMA": "Effect.1", 209: "OTIO_SCHEMA": "Effect.1", 228: "OTIO_SCHEMA": "ImageSequenceReference.1", 232: "OTIO_SCHEMA": "TimeRange.1", 234: "OTIO_SCHEMA": "RationalTime.1", 239: "OTIO_SCHEMA": "RationalTime.1", 258: "OTIO_SCHEMA": "Clip.2", 264: "OTIO_SCHEMA": "TimeRange.1", 266: "OTIO_SCHEMA": "RationalTime.1", 271: "OTIO_SCHEMA": "RationalTime.1", 278: "OTIO_SCHEMA": "Effect.1", 293: "OTIO_SCHEMA": "Effect.1", 308: "OTIO_SCHEMA": "Effect.1", 370: "OTIO_SCHEMA": "Effect.1", 385: "OTIO_SCHEMA": "Effect.1", 400: "OTIO_SCHEMA": "Effect.1", 415: "OTIO_SCHEMA": "Effect.1", 430: "OTIO_SCHEMA": "Effect.1", 449: "OTIO_SCHEMA": "ImageSequenceReference.1", 453: "OTIO_SCHEMA": "TimeRange.1", 455: "OTIO_SCHEMA": "RationalTime.1", 460: "OTIO_SCHEMA": "RationalTime.1", 479: "OTIO_SCHEMA": "Clip.2", 485: "OTIO_SCHEMA": "TimeRange.1", 487: "OTIO_SCHEMA": "RationalTime.1", 492: "OTIO_SCHEMA": "RationalTime.1", 499: "OTIO_SCHEMA": "Effect.1", 514: "OTIO_SCHEMA": "Effect.1", 529: "OTIO_SCHEMA": "Effect.1", 591: "OTIO_SCHEMA": "Effect.1", 606: "OTIO_SCHEMA": "Effect.1", 621: "OTIO_SCHEMA": "Effect.1", 636: "OTIO_SCHEMA": "Effect.1", 651: "OTIO_SCHEMA": "Effect.1", 670: "OTIO_SCHEMA": "ImageSequenceReference.1", 674: "OTIO_SCHEMA": "TimeRange.1", 676: "OTIO_SCHEMA": "RationalTime.1", 681: "OTIO_SCHEMA": "RationalTime.1", 700: "OTIO_SCHEMA": "Clip.2", 706: "OTIO_SCHEMA": "TimeRange.1", 708: "OTIO_SCHEMA": "RationalTime.1", 713: "OTIO_SCHEMA": "RationalTime.1", 720: "OTIO_SCHEMA": "LinearTimeWarp.1", 727: "OTIO_SCHEMA": "Effect.1", 742: "OTIO_SCHEMA": "Effect.1", 757: "OTIO_SCHEMA": "Effect.1", 819: "OTIO_SCHEMA": "Effect.1", 834: "OTIO_SCHEMA": "Effect.1", 849: "OTIO_SCHEMA": "Effect.1", 864: "OTIO_SCHEMA": "Effect.1", 879: "OTIO_SCHEMA": "Effect.1", 898: "OTIO_SCHEMA": "ImageSequenceReference.1", 902: "OTIO_SCHEMA": "TimeRange.1", 904: "OTIO_SCHEMA": "RationalTime.1", 909: "OTIO_SCHEMA": "RationalTime.1", 931: "OTIO_SCHEMA": "Track.1", 946: "OTIO_SCHEMA": "Gap.1", 950: "OTIO_SCHEMA": "TimeRange.1", 952: "OTIO_SCHEMA": "RationalTime.1", 957: "OTIO_SCHEMA": "RationalTime.1", [admin@Rocky Documents]$
この中から今回注目するのは、Clip.2(37行目)とImageSequenceReference.1(228行目)です。
まずは、37行目付近を確認してみます。
[admin@Rocky Documents]$ nl -ba edit.otio | sed -n '30,60p'
30 "name": "Video 1",
31 "source_range": null,
32 "effects": [],
33 "markers": [],
34 "enabled": true,
35 "children": [
36 {
37 "OTIO_SCHEMA": "Clip.2",
38 "metadata": {
39 "Resolve_OTIO": {}
40 },
41 "name": "A002C001_2206227A[141603-141626].exr", ----------> シーケンスセグメント名
42 "source_range": {
43 "OTIO_SCHEMA": "TimeRange.1",
44 "duration": {
45 "OTIO_SCHEMA": "RationalTime.1",
46 "rate": 23.976023976023979,
47 "value": 24.0
48 },
49 "start_time": {
50 "OTIO_SCHEMA": "RationalTime.1",
51 "rate": 23.976023976023979,
52 "value": 141603.0
53 }
54 },
55 "effects": [
56 {
57 "OTIO_SCHEMA": "Effect.1",
58 "metadata": {
59 "Resolve_OTIO": {
60 "Display Type": 1,
[admin@Rocky Documents]$
41行目の”name"に、前編で確認したNameカラムと同じ値が記録されています。
A002C001_2206227A[141603-141626].exr
OTIOファイルを複製
[admin@Rocky Documents]$ tree .
.
├── edit.otio
└── edit_rename.otio ----------> 複製した.otio
0 directories, 2 files
[admin@Rocky Documents]$
今回は動作確認のため、41行目の”name”から、ファイルネームTCと拡張子を手動で削除します。
41 "name": "A002C001_2206227A", ----------> ファイルネームTC/拡張子を削除
42 "source_range": {
43 "OTIO_SCHEMA": "TimeRange.1",
44 "duration": {
45 "OTIO_SCHEMA": "RationalTime.1",
46 "rate": 23.976023976023979,
47 "value": 24.0
48 },
手動修正後、Flameで確認
手動で修正したedit_rename.otioをインポート


シーケンスセグメントを確認すると、ファイルネームTCと拡張子の表示なし


Match Criteria > Name / Strict ONからリンクすることができる


DaVinci Resolveから書き出したOTIOファイルの”name”に含まれるファイルネームTCと拡張子が、
Match Criteria > Name / Strict ON選択時にリンクできない原因の一つであることを確認することができました。
Pythonスクリプトで自動化
手動で修正できることは確認しましたが、実案件で毎回OTIOファイルを編集するのは現実的ではありません。
以前の記事では、DaVinci Resolveから書き出したEDL/XMLを解析し、.cccファイルを自動生成するPythonスクリプトを作成しました。
今回も考え方は同じです。
OTIOファイルはJSON形式のテキストファイルなので、シーケンスセグメント名として記録されている”name”からファイルネームTCと拡張子を削除するスクリプトを作成します。
resolve_otio_strip_clip_ext.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import json
import re
from pathlib import Path
EXT_RE = re.compile(r"\.(exr|dpx|tif|tiff|jpg|jpeg|png|mov|mxf|mp4)$", re.IGNORECASE)
FRAME_RANGE_RE = re.compile(r"\[[-0-9]+\s*-\s*[-0-9]+\]$")
def clean_name(name: str, strip_frame_range: bool = False) -> str:
"""
Resolve OTIO が作る Clip.name / media_reference.name から拡張子を外す。
strip_frame_range=True の場合は末尾の [1001-1024] も外す。
"""
name = EXT_RE.sub("", name)
if strip_frame_range:
name = FRAME_RANGE_RE.sub("", name)
name = name.rstrip("._- ")
return name
def process_otio(data: dict, strip_frame_range: bool = False) -> int:
changed = 0
def walk(obj):
nonlocal changed
if isinstance(obj, dict):
schema = obj.get("OTIO_SCHEMA", "")
# Flame の File Name リンクで見える可能性が高いクリップ名
if schema.startswith("Clip") and isinstance(obj.get("name"), str):
old = obj["name"]
new = clean_name(old, strip_frame_range)
if new != old:
obj["name"] = new
changed += 1
# Resolve の ImageSequenceReference 側の表示名
# 注意: name_suffix は実ファイル列の拡張子なので残す
media_refs = obj.get("media_references")
if isinstance(media_refs, dict):
for mr in media_refs.values():
if isinstance(mr, dict) and isinstance(mr.get("name"), str):
old = mr["name"]
new = clean_name(old, strip_frame_range)
if new != old:
mr["name"] = new
changed += 1
for value in obj.values():
walk(value)
elif isinstance(obj, list):
for value in obj:
walk(value)
walk(data)
return changed
def main():
parser = argparse.ArgumentParser(
description="Remove file extensions from Resolve OTIO clip names for Flame relinking."
)
parser.add_argument("input_otio", help="Input .otio file")
parser.add_argument("output_otio", help="Output .otio file")
parser.add_argument(
"--strip-frame-range",
action="store_true",
help="Also remove trailing frame range like [1001-1024]",
)
args = parser.parse_args()
input_path = Path(args.input_otio)
output_path = Path(args.output_otio)
data = json.loads(input_path.read_text(encoding="utf-8"))
changed = process_otio(data, strip_frame_range=args.strip_frame_range)
output_path.write_text(
json.dumps(data, indent=4, ensure_ascii=False),
encoding="utf-8",
)
print(f"Changed fields : {changed}")
print(f"Input : {input_path}")
print(f"Output : {output_path}")
if __name__ == "__main__":
main()ヘルプを確認
[admin@Rocky Documents]$ python3 resolve_otio_strip_clip_ext.py --help usage: resolve_otio_strip_clip_ext.py [-h] [--strip-frame-range] input_otio output_otio Remove file extensions from Resolve OTIO clip names for Flame relinking. positional arguments: input_otio Input .otio file output_otio Output .otio file optional arguments: -h, --help show this help message and exit --strip-frame-range Also remove trailing frame range like [1001-1024] ----------> ファイルネームTCを削除するオプション [admin@Rocky Documents]$
コマンド実行
[admin@Rocky Documents]$ python3 resolve_otio_strip_clip_ext.py --strip-frame-range edit.otio edit_strip.otio Changed fields : 8 Input : edit.otio Output : edit_strip.otio [admin@Rocky Documents]$
“name”検索
[admin@Rocky Documents]$ grep -n '"name":' edit_strip.otio | grep -E 'A00|B00' ----------> フィルネームTC/拡張子削除できているか確認
41: "name": "A002C001_2206227A", ----------> Clip.2
230: "name": "A002C001_2206227A", ----------> ImageSequenceReference
262: "name": "B004C0023",
451: "name": "B004C0023",
483: "name": "A003C013_220415_R07B",
672: "name": "A003C013_220415_R07B",
704: "name": "A005C0013",
900: "name": "A005C0013",
[admin@Rocky Documents]$ sed -n '41p;262p;483p;704p' edit_strip.otio ----------> 行番号がわかっているので、Clip.2だけ検索
"name": "A002C001_2206227A",
"name": "B004C0023",
"name": "A003C013_220415_R07B",
"name": "A005C0013",
[admin@Rocky Documents]$
Clip.2とImageSequenceReferenceの両方に”name”が存在するため、同じクリップ名が2回ずつ表示されています。
strip_clip_ext.py後、結果を確認
ファイルネームTCと拡張子を削除したedit_strip.ocioをインポート


NameカラムからフィルネームTCと拡張子が削除されていることを確認
Match Criteria > Name / Strict ONからリンクすることができる



File Nameカラムを確認
Name / Strict ON によるリンクは成功しました。
しかし、Conformリストを確認すると、File Nameカラムには依然として末尾に「d」が表示されています。

Nameカラムには
A002C001_2206227A
B004C0023
A003C013_220415_R07B
A005C0013
が表示されていますが、File Nameカラムには末尾に「d」が追加されています。
A002C001_2206227Ad
B004C0023.d
A003C013_220415_R07Bd
A005C0013.d
今回修正したのはClip.2オブジェクト内の”name”です。
そのため、File Nameカラムが参照している情報は別の場所に存在している可能性があります。
“name”検索時に、OTIOファイル内で同じクリップ名が2回ずつ出現していたのを覚えているでしょうか。
次は ImageSequenceReference.1を確認してみます。
ImageSequenceReferenceを確認
228行目付近を確認
[admin@Rocky Documents]$ nl -ba edit_strip.otio | sed -n '220,250p'
220 "name": "",
221 "effect_name": "Resolve Effect"
222 }
223 ],
224 "markers": [],
225 "enabled": true,
226 "media_references": {
227 "DEFAULT_MEDIA": {
228 "OTIO_SCHEMA": "ImageSequenceReference.1",
229 "metadata": {},
230 "name": "A002C001_2206227A",
231 "available_range": {
232 "OTIO_SCHEMA": "TimeRange.1",
233 "duration": {
234 "OTIO_SCHEMA": "RationalTime.1",
235 "rate": 23.976023976023978,
236 "value": 24.0
237 },
238 "start_time": {
239 "OTIO_SCHEMA": "RationalTime.1",
240 "rate": 23.976023976023978,
241 "value": 141603.0
242 }
243 },
244 "available_image_bounds": null,
245 "target_url_base": "/Volumes/StorageMedia/Cafu_Share/ACES2065-1/OTIO",
246 "name_prefix": "A002C001_2206227A",
247 "name_suffix": ".exr",
248 "start_frame": 141603,
249 "frame_step": 1,
250 "rate": 23.976023976023978,
[admin@Rocky Documents]$
Clip.2にも”name”があり、ImageSequenceReferenceにも”name”があります。
228: "OTIO_SCHEMA": "ImageSequenceReference.1"
230: "name": "A002C001_2206227A"
まずはImageSequenceReferenceの内容を確認してみます。
Clip.2ではリンク用のクリップ情報が記録されていましたが、
ImageSequenceReferenceには実際のファイルシーケンス情報が記録されています。
“name”以外にも、
"name_prefix"
"name_suffix"
"start_frame"
などが存在していることが確認できます。
[admin@Rocky Documents]$ grep -n 'name_prefix\|name_suffix\|start_frame' edit_strip.otio 246: "name_prefix": "A002C001_2206227A", 247: "name_suffix": ".exr", 248: "start_frame": 141603, 467: "name_prefix": "B004C0023.", 468: "name_suffix": ".exr", 469: "start_frame": 106298, 688: "name_prefix": "A003C013_220415_R07B", 689: "name_suffix": ".exr", 690: "start_frame": 261342, 916: "name_prefix": "A005C0013.", 917: "name_suffix": ".exr", 918: "start_frame": 374113, [admin@Rocky Documents]$
File Nameカラムに表示される末尾の「d」は、Clip.2の”name”を修正しても変化しませんでした。
ImageSequenceReference側には、”name_prefix”や”name_suffix”が存在しているため、
こちらの情報がFile Nameカラムに影響している可能性があります。
そこで、ImageSequenceReference側の情報も含めて処理できるように、resolve_otio_strip_clip_ext.pyをベースに機能を追加しました。
resolve_otio_clean_for_flame.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import json
import re
from pathlib import Path
EXT_RE = re.compile(r"\.(exr|dpx|tif|tiff|jpg|jpeg|png|mov|mxf|mp4)$", re.IGNORECASE)
FRAME_RANGE_RE = re.compile(r"\[[-0-9]+\s*-\s*[-0-9]+\]$")
def clean_name(name: str, strip_frame_range: bool = False) -> str:
name = EXT_RE.sub("", name)
if strip_frame_range:
name = FRAME_RANGE_RE.sub("", name)
name = name.rstrip("._- ")
return name
def round_near_integer(value, epsilon=1e-4):
"""Resolve OTIO may write values like 261341.99999999997. Flame can read this as 1 frame early.
Round only when the value is effectively an integer.
"""
if isinstance(value, (int, float)):
nearest = round(value)
if abs(value - nearest) tuple[int, int]:
name_changed = 0
time_changed = 0
def walk(obj):
nonlocal name_changed, time_changed
if isinstance(obj, dict):
schema = obj.get("OTIO_SCHEMA", "")
if schema.startswith("Clip") and isinstance(obj.get("name"), str):
old = obj["name"]
new = clean_name(old, strip_frame_range)
if new != old:
obj["name"] = new
name_changed += 1
media_refs = obj.get("media_references")
if isinstance(media_refs, dict):
for mr in media_refs.values():
if isinstance(mr, dict) and isinstance(mr.get("name"), str):
old = mr["name"]
new = clean_name(old, strip_frame_range)
if new != old:
mr["name"] = new
name_changed += 1
if fix_near_integer_time and schema.startswith("RationalTime") and isinstance(obj.get("value"), (int, float)):
old = obj["value"]
new = round_near_integer(old)
if new != old:
obj["value"] = new
time_changed += 1
for value in obj.values():
walk(value)
elif isinstance(obj, list):
for value in obj:
walk(value)
walk(data)
return name_changed, time_changed
def main():
parser = argparse.ArgumentParser(
description="Clean Resolve OTIO clip names and near-integer frame values for Flame relinking."
)
parser.add_argument("input_otio", help="Input .otio file")
parser.add_argument("output_otio", help="Output .otio file")
parser.add_argument("--strip-frame-range", action="store_true", help="Remove trailing frame range like [1001-1024]")
parser.add_argument("--fix-near-integer-time", action="store_true", help="Round values like 261341.99999999997 to 261342.0")
args = parser.parse_args()
input_path = Path(args.input_otio)
output_path = Path(args.output_otio)
data = json.loads(input_path.read_text(encoding="utf-8"))
name_changed, time_changed = process_otio(
data,
strip_frame_range=args.strip_frame_range,
fix_near_integer_time=args.fix_near_integer_time,
)
output_path.write_text(json.dumps(data, indent=4, ensure_ascii=False), encoding="utf-8")
print(f"Changed name fields : {name_changed}")
print(f"Rounded time values : {time_changed}")
print(f"Input : {input_path}")
print(f"Output : {output_path}")
if __name__ == "__main__":
main()ヘルプを確認
[admin@Rocky Documents]$ python3 resolve_otio_clean_for_flame.py --help
usage: resolve_otio_clean_for_flame.py [-h] [--strip-frame-range] [--fix-near-integer-time] [--flatten-for-strict-file-name]
input_otio output_otio
Clean Resolve OTIO names and near-integer frame values for Flame relinking.
positional arguments:
input_otio Input .otio file
output_otio Output .otio file
optional arguments:
-h, --help show this help message and exit
--strip-frame-range Remove trailing frame range like [1001-1024]
--fix-near-integer-time
Round values like 261341.99999999997 to 261342.0
--flatten-for-strict-file-name
Flatten ImageSequenceReference naming so Flame File Name Strict matching
does not see a trailing printf-style 'd' ----------> 末尾の「d」を除去する検証用オプション
[admin@Rocky Documents]$
コマンド実行
[admin@Rocky Documents]$ python3 resolve_otio_clean_for_flame.py --flatten-for-strict-file-name edit_strip.otio edit_flatten.otio Changed name fields : 10 Rounded time values : 0 Input : edit_strip.otio Output : edit_flatten.otio [admin@Rocky Documents]$
clean_for_flame.py後、File Nameリンクを確認
File Nameから「d」を削除したedit_flatten.otioをインポート


File Nameカラムから「d」が削除されていることを確認
Match Criteria > File Name / Strict ONからリンクすることがなぜかできない



結果
File Nameカラムから末尾の「d」は削除されましたが、Match Criteria > File Name / Strict ONによるリンク結果に変化はありませんでした。
現時点では、File Name / Strict ONでリンクできない原因は特定できていません。
まとめ
今回の検証では、DaVinci Resolveから書き出したOTIOファイルに含まれるClip.2およびImageSequenceReferenceの構造を確認し、Name / Strict ONからリンクできない原因の一部を特定することができました。
一方で、File Name / Strict ONについては未解決の部分が残っています。
原因を特定できていませんが、引き続きOTIOファイルの構造やFlameのリンクロジックを調査していきたいと思います。