見出し画像

インスペクタのコントロール操作とRenderScriptでひっかかったところ

どうも、火注ゆかなです。
先日も紹介しましたが、ぬろく様が立ち絵PSDファイルをDavinciResolve向けに自動変換してくれるツールを公開されました。
口パクはまだ対応されてませんが、これでDavinciResolveでソフトウェアトーク動画を作りやすくなりますね。


で、口パクのやり方についてぬろく様とちょっとTwitterでお話ししたり、それ関連で試行錯誤したことについてまとめておこうかと思った次第です。


インスペクタのコントロールをスクリプトで操作する

コントロールって何?

Editページでタイムラインのクリップを選択したとき、右側に表示されるのがインスペクタ。
インスペクタの中の入力項目一つ一つがコントロールです。
入力用UI=コントロールって思っていただければ。

コントロールは通常であれば手動で値を変えるものですが、他にも変える方法があります。
一つはExpression(数式)を設定すること。
もう一つはスクリプトから変更することです。

Expressionで変更する

こちらは本題ではないのでざっくり説明。
適当なコントロールの上で右クリックするとメニューが出ますが、その中の「エクスプレッション」という項目を選択。
(※DavinciResolve 17以前だとExpression)
すると、該当コントロールの下に数式入力欄が表示されます。

右クリック→「エクスプレッション」を押すと
数式入力欄が表示される

数式入力欄には他のコントロールの値を参照したり、数式を記入できます。
if文とか使えば条件に応じて値を切り替えることもできますね。

他のコントロールIDを記述すれば自動で値を取得する(例:Font)
文字サイズを条件に表示テキストを切り替えるなども可能

他にも再生時間の経過と共に文字を大きくしたり、色を変えたり、移動させたりということも可能です。
Expressionについては紹介しているところが多いので、詳しくは検索してください。

スクリプトで変更する

Expressionはコントロール同士の操作を連動・自動化が可能ですが、複雑な処理を記述するのには向いていません。
複雑な処理については変数も使えるスクリプトの出番です。

スクリプトを組める言語はLua、もしくはPythonです。
ライブラリの充実度や組みやすさはPythonでしょうが、一部使えない機能があるそうです。
逆にLuaはPythonのような機能制限はないものの、標準ライブラリが貧弱です。私は開発環境の設定が面倒だったのでLuaで組んでいますが、まあ色々と不満があります……。

これからスクリプトを組む人はPythonをオススメします。

本題に入りましょう。
スクリプトからコントロールを操作する場合ですが、「NodeID.ControllID[Frame]」で参照・設定が可能です。

これについてはText+クリップの文章を変更・参照する方法について記述した過去の記事でも既に書いていることではあるのですが、その時はちゃんと理解していなかったのです。

なので、Text+クリップの文章を変更する方法について改めて記述するとこんな感じ。

resolve = Resolve()		--Rosolveオブジェクト取得
projectManager = resolve:GetProjectManager()	--プロジェクトマネージャー取得
project = projectManager:GetCurrentProject()	--現在読み込まれているDaVinci Resolveプロジェクト取得 
timeline = project:GetCurrentTimeline()    --現在のタイムライン取得
 
for k,v in pairs(tl:GetItemsInTrack("video", 1)) do
    if v:GetName() == "Text+" then    -- 動画ファイルなどが混在する可能性があるのでクリップ名で条件分岐
        toolList = v:GetFusionCompByIndex(1):GetToolList(false, "TextPlus")    -- Fusionに設定されたツール一覧取得(Text+ノードのみ抽出)

        -- キーフレームごとに表示内容を変えない場合、Node.ControllID に代入して設定可能
        toolList[1].StyledText = "キーフレーム指定なしの文章挿入テスト"    -- 表示するテキスト設定
        
        -- 設定したテキスト内容を表示したい場合
        print(toolList[1].StyledText[0])  --StyledTextのインデックスにキーフレームを指定する
                                          --挿入時にキーフレームを指定しない場合、キーフレーム0から開始という扱いになるらしい



        -- キーフレームを指定して内容を設定したい場合、Node.ControllID[Frame] に代入して設定可能
        comp = newtext:GetFusionCompByIndex(1)
        comp.CurrentTime = 0              -- アニメーションキーフレームを0にセット(今回必要なのかはわからない)
        spline = comp.BezierSpline()
        toolList[1].StyledText = spline   -- ベジェスプラインを設定
        toolList[1].StyledText[0] = "Frame0"       --キーフレーム0から表示するテキスト
        toolList[1].StyledText[10] = "Frame 10"    --キーフレーム10から表示するテキスト
        toolList[1].StyledText[50] = "Frame 50"    --キーフレーム50から表示するテキスト
        toolList[1].StyledText[100] = "Frame 100"  --キーフレーム100から表示するテキスト


        -- 設定済みキーフレームを指定してテキストを取得する(今回は0, 10, 50, 100フレームに設定済み)
        -- 未設定キーフレームを指定した場合、その直前の設定済みキーフレームを参照する。
        -- それもないなら直後の設定済みキーフレームを参照する。
        --  (10, 50フレームにテキストを設定済みで、40フレームを指定した場合、直前の10フレームのテキストを参照する)
        --  (10, 50フレームにテキストを設定済みで、5フレームを指定した場合、一番早い10フレームのテキストを参照する)
        print(toolList[1].StyledText[0])    
        print(toolList[1].StyledText[20])  -- 20フレームに設定がないので、直前の10フレームの設定値が参照される
        print(toolList[1].StyledText[50])
        print(toolList[1].StyledText[100])
        
        -- 設定済みキーフレームのテキストを順番に表示する
        textframe = toolList[1].StyledText:GetKeyFrames()     -- Text+に設定されたテキストのキーフレーム一覧取得
        for k2=1, #textframe do
	    print("[" .. k2 .. "]" .. toolList[1].StyledText[textframe[k2]])		-- Text+に設定されたキーフレームのテキストを表示
        end
    end
end

これでスクリプトでコントロールの値を変更・参照する方法は完璧?
いえ、これだけだととある状況でドツボにハマるかもしれません。私のように。
ということでRenderScriptについて解説します。


RenderScriptでコントロールを操作する

RenderScriptって何?

私もつい最近教えていただいたのですが、インスペクタの設定ページに「FrameRenderScript(フレームレンダースクリプト)」「StartRenderScript(開始レンダースクリプト)」「EndRenderScript(終了レンダースクリプト)」という入力欄があります。

ここにはLuaでスクリプトを記述できます。
FrameRenderScriptは1フレームごとに処理される内容を記述します。
StartRenderScriptはレンダリングが開始されたときに処理される内容を記述します。
基本的にはStartRenderScriptで変数の初期値などを準備しておいて、FrameRenderScriptでコントロールの値を変更することになるのかなと。

普通のスクリプトじゃなくてRenderScriptを使うメリットはあるのかと問われれば、あります。

普通のスクリプトだと
1.タイムラインから目的のクリップを検索して取得
2.クリップからFusionノード一覧を取得
3.処理したいノードを検索して取得
と探してようやくコントロールを操作できますが、上記の検索手順を飛ばせるのは楽です。

もちろん、異なるクリップ、異なるタイムラインを参照しての処理はできないなどの制約もあるようですが、一つのクリップで完結する処理ならこちらの方が良いでしょう。
普通のスクリプトだと処理対象のクリップが増えるごとに検索の手間が複雑化していきますし。

あとは、RenderScriptを記述するノード自身を参照するときに「self」が使える点ですね。
「self.Center.X」と書けば、ノードの名前がなんであってもノード自身を参照できます。一々ノードの名前を気にしなくて良いのは楽です。検索する手間はない方が良い。

スクリプトを組むほどじゃないけれど、Expressionに記述するには長すぎる……という処理にも良いようです。
そんな感じでメリットが色々あります。

RenderScriptでの注意点

で、普通のスクリプトと同じように、RenderScriptでも「NodeID.ControllID[frame]」で値の参照・設定ができます。
できますが、できない場合もあります。
これだけだと失敗するパターンがあるのです。

コントロールが文字列を扱っている場合、「NodeID.ControllID[frame].Value」と記述する必要があります。
「.Value」を余計につけなければいけないようです。

例えば、Text+ノードの表示テキストを取得するなら「self:StyledText[0].Value」と書きます。
「self:StyledText[0]」ではダメです。
でも値を代入(変更)するときは「.Value」は要りません。
他の文字列を扱うコントロールに対してそのまま代入するときも「.Value」が要りません。

ややこしいのでコード例をまとめてみました。

--例:
self.StyledText = "test text"       -- 〇 : コントロールへの文字列代入は正常に機能する
self.StyledText = self.Font        -- 〇 : コントロールへのコントロール設定も正常に機能する
print(self.Font.Value)              -- 〇 : コンソールにフォント名が表示される
print(string.len(self.Font.Value))  -- 〇 : ちゃんと文字列として認識され、コンソールにフォント名の文字列長が表示される
print(string.len(self.Font))        -- ✕ : 失敗するしアプリが落ちることもある
 
-- ちなみにself.StyledText[0]、self.Font[0]って変えても結果は同じ

普通のスクリプトなら「self:StyledText[0]」で参照も代入もいけるだけに、RenderScriptでは見事にドツボにハマりました。
こちらのフォーラムのやりとりでようやく解決できましたが、ここにたどり着くまでに6時間ぐらい浪費したんじゃないですかね……。

https://www.steakunderwater.com/wesuckless/viewtopic.php?t=1591


文字列を扱うコントロールで一体何が起こっているのか?

なんかデータ型が違うみたいです。
Luaのtype関数でデータ型を調べてみたらこんな感じでした。

-- コンソールから実行した結果
Lua> print(type(Template.StyledText))
userdata
Lua> print(type(Template.StyledText[0]))
string
Lua> print(type(Template.StyledText[0].Value))
nil

-- StartRenderScriptで実行された結果
StartRenderScript : print(type(Template.StyledText))
cdata
StartRenderScript : print(type(Template.StyledText[0]))
cdata
StartRenderScript : print(type(Template.StyledText[0].Value))
string

type(Template.StyledText[0])で調べると、コンソールからだと「string」型、StartRenderScriptだと「cdata」型。
「cdata型とはなんぞや?」と気になって調べたところ、どうやら「特殊文字を見たまま記述する」ためのタグとか型とかそんな感じのようです。

プログラミングをかじった人なら割と常識ですが、WordファイルやWebページなどでは表示されない文字が色々あります。
例えば太字を示す文章は<b>このタグに囲まれた部分は太字</b>みたいに記述されてます。でも<b>と</b>に囲まれた部分は表示されますが、<b>と</b>は表示されません。というか表示されたら困ります。<b></b>は文字列を太くするために記述したのであって、それ自体は見せたい文字ではないんですから。

他にもメジャーなものとしては改行を表す「"\n"」、タブ文字を示す「"\t"」とか。
プログラムに「"こんにちは\n良い天気ですね"」って記述したら
「"こんにちは
  良い天気ですね"」
って表示されます。

あとは「文書の中から数字だけ検索したい」というような時は正規表現パターンというものを使ったり。(%wだと英数字だけ、%dだと数字だけ対象にするetc)

そんな感じで色々な機能を持たせたタグなどが色々あるのですが、そうなると逆に特殊な文字やタグを表示したいとき、どう書けば良いのか? という問題が生じます。

解決方法としては、そういう特殊な文字の前にエスケープ記号と呼ばれるものを追加する方法。
(「"こんにちは\\n良い天気ですね"」と書くと、「こんにちは\n良い天気ですね」と表示される。「\」が次の記号を特殊文字ではなく普通の文字として扱うように機能している)

もう一つは特殊な文字やタグを無効化する型やタグで囲んで扱う方法で、cdata型はこちらみたいです。

ファイルパスなんかは「C:ProgramData\Blackmagic Design」みたいに、フォルダ区切りに「\」を使用していますが、これを普通の文字列として扱おうとすると「\B」を別のものとしてとらえかねませんし、それを回避するならエスケープ記号「\」を追加して「C:ProgramData\\Blackmagic Design」と書かなきゃいけないから要らない手間が増えます。

Luaで特殊文字扱いされるような文字が入力されたテキストに含まれていても、面倒を避けてそのまま表示されるようにcdata型で管理しているということなのだと思います。

いや、だとしても、もうちょっと表記方法の統一はどうにかなりませんでした?????



そんな感じでインスペクタのコントロールをスクリプトから制御する方法と、RenderScriptで文字列扱うコントロールから値取得するときは気を付けてねってお話でした。

RenderScriptの文字列コントロール問題は引っかかる人は引っかかりそうです。
そういう時にこの記事が早めに見つかって、こんなしょうもない問題で私みたいに何時間も浪費する人がちょっとでも減りますように……。

以上、少しでもお役に立てば幸いです。

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