見出し画像

【DaVinci Resolve API】キースプラインをステップ状に設定する方法

まえがき

こんにちは、火注ゆかなです。
なんか急に涼しくなりましたね。
まだ30℃を超える日もあるみたいですが、全体的に30℃を下回る日が増えてきててようやく秋の兆しが見えてきてなによりです。
今年は秋が長いと良いな……。

さて、最近はFuseの作り方について色々調べていました。
とりあえずモディファイアを作ってみたものの、「値はキースプライン(BezierSpline)で設定した方が負荷が軽いのでは?」と思って、今度はノード作成してみたんです。

まあ他のモディファイアよりキースプラインの方が軽いかは別に調べていないのですが、この過程で「キースプラインを階段状に設定する」という処理が結構面倒なことがわかりました。

というわけで、記事のタイトルにもあるようにキースプラインをスクリプトから設定する方法について、わかったことを解説していきます。


キースプラインの設定について

そもそもキースプラインって何?

一般的にはキーフレームアニメーションと呼ばれている? ものです。たぶん。
GUIではコントロールの横にある「◇」をクリックすると現在のフレームに値を設定できます。(値が設定されているフレームでは◇が赤くなる)

手動で設定する場合、

  1. 指定したいタイミングに移動

  2. 設定したいコントロールの◇をポチっとクリック

  3. 任意の値に変更

を繰り返すだけで設定できます。
で、細かい調整はスプラインエディタで行います。


スクリプトから設定する方法

とはいえ、このキースプライン設定を自動化したい人もいるわけです。
毎回全く同じ動きなら一度作ったものを使いまわせば済みますが、毎回変更したい場合はそうもいきません。
そういう場合はスクリプトから設定することになります。

過去の記事でも触れていますが、コントロールへモディファイアの一種であるBezierSplineを割り当てることで設定できるようになります。

なので、スクリプトからキースプライン設定を行う場合、

  1. 任意のコントロールにBezierSplineを追加する

  2. 任意のタイミングに値を設定する

を行うことになります。

例えば、Text+テンプレートの文字表示サイズを変更したい場合はこんな感じになるでしょうか。
今回はLuaで記述します。

-- 現在表示中のタイムラインの映像トラック1の最初のクリップのコンポジション取得
project = resolve:GetProjectManager():GetCurrentProject()  -- プロジェクト取得
timeline = project:GetCurrentTimeline()                    -- カレントタイムライン取得
tl_item_list = timeline:GetItemListInTrack("video", 1)     -- 映像トラック1のクリップリスト取得
comp = tl_item_list[1]:GetFusionCompByIndex(1)             -- 最初のクリップのコンポジション取得

-- ノードの取得と、SizeコントロールへBezierSplineの追加
node = comp:FindTool("Template")  -- Text+ノードをノード名で検索して取得
if node.Size:GetConnectedOutput() == nil then  -- モディファイアが追加されていない場合
    comp.CurrentTime = 0    -- 再生フレームを0に設定(モディファイア追加時、現在フレームに値を設定してしまうため、意図しないタイミングに設定してしまうことを防ぐ)
    if not node:AddModifier("Size", "BezierSpline") then  -- モディファイアの追加
        print("モディファイアの追加に失敗しました")
        return
    end
end
spline = node.Size:GetConnectedOutput():GetTool()  -- 追加したBezierSplineを取得

-- キーフレーム毎に値を設定する
keyframe_list = {  -- キーフレーム毎の設定値を格納したテーブル
	[0] =  0.0,
	[5] =  0.4,
	[15] = 0.2,
	[25] = 0.5,
	[35] = 0.2,
	[45] = 0.3,
	[55] = 0.0,
}
spline:SetKeyFrames(keyframe_list)  -- キーフレームの一括設定(元々設定されていたキーフレームは削除される(範囲が被っている場合のみ?))
node.Size[100] = 0.5              -- BezierSplineを追加したコントロール[keyframe]への代入でも追加可能
-- spline:DeleteKeyFrames(0, 20)  -- 020フレームに設定した値の削除(今回は実施しない)

dump(node.Size:GetKeyFrames())    -- 設定したキーフレーム毎の値を取得、表示
設定されたキースプライン

こんな感じで設定ができます。



スクリプトからのフラグ設定について

キースプラインをステップ状にする

キースプラインはデフォルトだと直線状になります。
直線状だと「1→5」と設定した場合、その中間フレームでは「1→2→3→4→5」と徐々に変化していきます。
(実際には小数点単位で変化していく)
アニメーションするなら中間部分を補間してくれるのでありがたい機能ですが、「5のフレームになるまではずっと1のままが良い」というように補間してほしくない場合はむしろ邪魔になります。

そんな時はステップ状に設定することで解決できます。
変更したいキーを選択すると、スプラインエディタの下部に表示されているアイコンがいくつか明るくなります。
この内のステップイン・ステップアウトを実行すると選択部分がステップ状になります。

複数のキーを選択するとステップイン・ステップアウトが設定可能に
ステップインを設定した場合(指定のキーになったら値が一気に変わる)
ステップアウトを設定した場合(指定のキーになったら次のキーの値に変わる)

補間してほしくない場合はステップイン・ステップアウトを設定すると良いわけですね。

「じゃあこれをスクリプトから設定すれば良いんだ!」
と安直に考えますよね?

できないんですよね、これが。


そもそもノード内の設定はどうなっているのか?

順に説明していきましょう。

まず、ノードの中身はJSON形式のテキストです。

キースプラインを設定したノードを選択して、テキストエディタへコピペして中身を確認してみます。

{
	Tools = ordered() {
		Template = TextPlus {
			CtrlWZoom = false,
			Inputs = {
				GlobalOut = Input { Value = 299, },
				Width = Input { Value = 1920, },
				Height = Input { Value = 1080, },
				UseFrameFormatSettings = Input { Value = 1, },
				StyledText = Input { Value = "Custom Title", },
				Font = Input { Value = "Open Sans", },
				Style = Input { Value = "Semibold", },
				Size = Input {
					SourceOp = "Templateサイズ",
					Source = "Value",
				},
				VerticalJustificationNew = Input { Value = 3, },
				HorizontalJustificationNew = Input { Value = 3, }
			},
			ViewInfo = OperatorInfo { Pos = { 220, 49.5 } },
		},
		["Templateサイズ"] = BezierSpline {
			SplineColor = { Red = 41, Green = 232, Blue = 149 },
			CtrlWZoom = false,
			NameSet = true,

			KeyFrames = {
				[0] = { 0, RH = { 1.66666666666667, 0.133333333333333 }, Flags = { Linear = true } },
				[5] = { 0.4, LH = { 3.33333333333333, 0.266666666666667 }, RH = { 8.33333333333333, 0.333333333333333 }, Flags = { Linear = true } },
				[15] = { 0.2, LH = { 11.6666666666667, 0.266666666666667 }, RH = { 18.3333333333333, 0.3 }, Flags = { Linear = true } },
				[25] = { 0.5, LH = { 21.6666666666667, 0.4 }, RH = { 28.3333333333333, 0.5 }, Flags = { Linear = true } },
				[35] = { 0.2, LH = { 35, 0.3 }, RH = { 38.3333333333333, 0.2 }, Flags = { StepIn = true } },
				[45] = { 0.3, LH = { 45, 0.266666666666667 }, RH = { 48.3333333333333, 0.2 }, Flags = { StepIn = true } },
				[55] = { 0, LH = { 51.6666666666667, 0.1 }, RH = { 70, 0.166666666666667 }, Flags = { Linear = true } },
				[100] = { 0.5, LH = { 85, 0.333333333333333 }, Flags = { Linear = true } }
			}
		}
	},
	ActiveTool = "Template"
}

「Templateサイズ」というBezierSplineの中、「KeyFrames」という項目がキースプラインの設定値です。
キー毎の値は「{ keyframe, LH ={X, Y}, RH={X, Y}, Frags = { flg = true } }」みたいなテーブルです。

ここで注目するべきは「Flags」という項目です。
Flagsは直線状のキーだと「Linear」、ステップ状のキーだと「StepIn」が有効になっていることがわかります。
Flagsでキー間の補間の仕方を指定しているわけですね。

このことから、スクリプトからキースプラインをステップ状に変更するということは「Flagsという項目をどうやって設定するのか」という話になります。

問題はここです。
BezierSplineクラスにはFlagsを設定するためのメソッドがありません。
(ひょっとしたら上手く設定する方法があるかもしれませんが、私は見つけられませんでした。)

例えFlagsを設定したキーフレーム設定値のテーブルをBezierSpline:SetKeyFrames()に渡したとしても、反映されるのはキーと値、それから「LH(Left Handle)テーブル」「RH(Right Handle)テーブル」だけです。

-- 下記のようにFlagsを設定したテーブルを引数に渡しても、ステップ状にならない

keyframe_list = {
	[0] =  {0.0, Flags={StepIn=true}},
	[10] = {0.2, Flags={StepIn=true}},
	[20] = {0.5, Flags={StepIn=true}},
	[30] = {0.2, Flags={StepIn=true}},
	[40] = {0.3, Flags={StepIn=true}},
	[50] = {0.0, Flags={StepIn=true}},
}
spline:SetKeyFrames(keyframe_list)
Flags = { StepIn = true } を追加したテーブルでもステップ状にならない


スクリプトからフラグ設定する方法

「これ詰んでない?」と少し諦めかけましたが、わざわざ記事にした以上は解決方法ももちろんあります。

ノードの設定値はテキストエディタへコピペできると言いましたが、逆にテキストエディタで編集した設定値をノードへコピペすることも可能です。
テキストエディタで「Flags = { StepIn = true }」と変更してから、ノードに「設定値をペースト」をするとキースプラインがステップ状になりました。

ノードの設定値をテキストエディタで編集(「Flags = { StepIn = true }」に変更)
「設定値をペースト」でステップ状になった

つまり、ノードの設定値のコピー→編集→ペーストをスクリプトで行えば良いわけです。こちらなら可能です。

設定値のコピーはOperator:SaveSettings()
設定値のペーストOperator:LoadSettings(settings)

でできます。
Operatorはノードとかモディファイアなどが当てはまると考えてください。

Operator:SaveSettings()は引数なしだと設定値をテーブルで取得できます。
(引数ありだと指定のファイルパスに設定値を保存します)
Operator:LoadSettings(settings)は設定値を格納したテーブルか、設定値を保存したファイルパスを指定することでテーブル設定を上書きできます。

それを踏まえた上でステップ状のキースプライン設定をするならこんな感じになります。

spline = node.Size:GetConnectedOutput():GetTool()  -- BezierSplineモディファイア取得
setting = spline:SaveSettings()    -- BezierSplineモディファイアの設定値テーブルを取得

keyframe_list = {
	[0] =  {0.0, Flags={StepIn=true}},
	[5] =  {0.4, Flags={StepIn=true}},
	[15] = {0.2, Flags={StepIn=true}},
	[25] = {0.5, Flags={StepIn=true}},
	[35] = {0.2, Flags={StepIn=true}},
	[45] = {0.3, Flags={StepIn=true}},
	[55] = {0.0, Flags={StepIn=true}},
	[100] = { 0.5, Flags = { StepIn = true } }
}
setting.Tools[spline.Name].KeyFrames = keyframe_list -- KeyFramesの項目を上書き

spline:LoadSettings(setting)    -- 編集後の設定値を反映

冒頭のキースプライン設定スクリプトを書き換えた場合はこうなります。

-- 現在表示中のタイムラインの映像トラック1の最初のクリップのコンポジション取得
project = resolve:GetProjectManager():GetCurrentProject()  -- プロジェクト取得
timeline = project:GetCurrentTimeline()                    -- カレントタイムライン取得
tl_item_list = timeline:GetItemListInTrack("video", 1)     -- 映像トラック1のクリップリスト取得
comp = tl_item_list[1]:GetFusionCompByIndex(1)             -- 最初のクリップのコンポジション取得

-- ノードの取得と、SizeコントロールへBezierSplineの追加
node = comp:FindTool("Template")  -- Text+ノードをノード名で検索して取得
if node.Size:GetConnectedOutput() == nil then  -- モディファイアが追加されていない場合
    comp.CurrentTime = 0    -- 再生フレームを0に設定(モディファイア追加時、現在フレームに値を設定してしまうため、意図しないタイミングに設定してしまうことを防ぐ)
    if not node:AddModifier("Size", "BezierSpline") then  -- モディファイアの追加
        print("モディファイアの追加に失敗しました")
        return
    end
end
spline = node.Size:GetConnectedOutput():GetTool()  -- 追加したBezierSplineを取得

-- キーフレーム毎に値を設定する
keyframe_list = {
	[0] =  {0.0, Flags={StepIn=true}},
	[5] =  {0.4, Flags={StepIn=true}},
	[15] = {0.2, Flags={StepIn=true}},
	[25] = {0.5, Flags={StepIn=true}},
	[35] = {0.2, Flags={StepIn=true}},
	[45] = {0.3, Flags={StepIn=true}},
	[55] = {0.0, Flags={StepIn=true}},
	[100] = { 0.5, Flags = { StepIn = true } }
}
setting = spline:SaveSettings()    -- BezierSplineモディファイアの設定値テーブルを取得
setting.Tools[spline.Name].KeyFrames = keyframe_list  -- KeyFramesを上書き
spline:LoadSettings(setting)       -- BezierSplineモディファイアの設定値テーブルを上書き

-- node.Size[100] = 0.5              -- BezierSplineを追加したコントロール[keyframe]への代入でも追加可能
-- spline:DeleteKeyFrames(0, 20)  -- 020フレームに設定した値の削除(今回は実施しない)

dump(node.Size:GetKeyFrames())    -- 設定したキーフレーム毎の値を取得、表示


キースプラインの値はそのままにしてステップ状にしたいだけの場合、KeyFramesのFlagsテーブルだけ書き換えてあげれば良いでしょう。

spline = node.Size:GetConnectedOutput():GetTool()
setting = spline:SaveSettings()

keyframes = setting.Tools["Templateサイズ"].KeyFrames
for k, v in pairs(keyframes) do
	keyframes[k]["Flags"] = {StepIn = true}    -- Flagsだけ上書き
end
spline:LoadSettings(setting)



おまけ(モディファイアの削除)

今回はコントロールにモディファイアを追加しましたが、モディファイアの接続を切ったり削除もできます。

-- コントロールからモディファイアとの接続を切る場合
control = tool.Size
control:ConnectTo()    -- コントロール(Input)の接続先(Output)をなしに設定
-- tool.Size = nil        -- コントロールにnilを代入することでも同じことが可能
 

-- モディファイアの削除を行う場合
control = tool.Size
modifier = control:GetConnectedOutput():GetTool()
modifier:Delete()    -- 接続している全てのコントロールからモディファイアが参照できなくなる

ConnectTo()はコントロール(Input)にモディファイアなどの出力(Output)を繋ぐ関数です。引数なしだと元々接続しているOutputの接続を切ります。接続を切られたモディファイアは即座に削除されるわけではありません。
Dlete()はモディファイアを削除します。(Ctrl+Zで削除前に戻せますが)

この2つの違いですが、まず前提として1つのモディファイアを複数のコントロールに適用することができます。要は共有ですね。
GUIではコントロールを右クリックして「接続」→「モディファイアを適用しているノード選択」→「モディファイアを適用しているコントロール」を選択することで可能です。

他のコントロールに適用されたモディファイアを適用できる

あとスクリプトでやる場合は、コントロールのConnectTo()に、他のコントロールのGetConnectedOutput()の戻り値をそのまま渡してあげれば良いです。

control_1 = node.Size              -- モディファイア適用済みコントロール
control_2 = node.LineSpacingClone  -- モディファイアを共有したいコントロール
control_2:ConnectTo(control_1:GetConnectedOutput())

接続を切られたモディファイアは他に接続しているコントロールがない場合、アプリ再起動時などに削除されます。
逆に言えば、接続が残っている限りそのモディファイアは削除されません。

それから、モディファイアをDelete()で消した場合、該当モディファイアをと接続していたコントロール全てからモディファイアが消えます。
参照先がなくなったので当然といえば当然なのですが。

ただ、変数に格納していた場合はアプリを落とすまでは参照できるので、Delete()を実行したから即全部なくなるわけでもないようです。
削除というより、あくまでも接続を全て切ってるというのが正しい?

引数なしConnectTo()はコントロール側からモディファイアとの接続を切る
Delete()はモディファイア側から全コントロールとの接続を切る
という認識で良いんじゃないでしょうかね。



あとがき

というわけで、スクリプトからキースプラインをステップ状に設定する方法についての解説は以上となります。

マクロの構造を把握する必要はあるものの、SaveSettings()とLoadSettings(settings)を活用すれば既存メソッドでは触れられない設定も編集できるのは良いですね。
この手法は今後もちょこちょこ役に立ちそうです。

ちなみにこの方法見つけてからWe Suck Lessフォーラムで全く同じ話のスレッドを見つけました。3か月前に既に同じ方法で解決されてた。
んもーーーーーーーーーなんで解決後に見つかるんですか。私の検索能力が低いだけですね。はい。

ちなみに上記スレッドで解決策を回答されてるのは「はせがわ偽名」さんですね。スクリプトとかFuseプラグインを作って公開されている方みたいです。お強い。
3年くらいいじくりまわしてるのに碌なスクリプトを作れてない身からすると、まあ凄いなという他にありません。


閑話休題。

私の検索の仕方が下手なのはもうしょうがないとしても、もうちょっと探したい情報が簡単に見つかるようになれば良いんですけどね。
地道に情報発信して日本語圏のスクリプトやプラグインの開発者を増やすだけじゃなく、やっぱり日本語での情報交換フォーラムとかがないと駄目でしょうか。交流の場がないのは大きいですよね。

でもそういうのの運営なんて続けられる気がしないので、旗振り役は遠慮したい……。


愚痴っぽい内容はここまでにしましょう。
今回の記事も皆さんのお役に立てば幸いです。


サポートしていただけるとその分の価値を提供できてるんだなって励みになります。