【DaVinci Resolve API】長い処理を途中でキャンセルする処理の実装方法
まえがき
こんにちは。火注ゆかなです。
もうすぐ2023年も終わりますね。この一年間、スクリプト弄ったり音声処理関連を組んでは精度が出なくて絶望したりして、結局動画出さなかったなぁ……。
今回はDavinciResolveのスクリプト内で、長い処理をキャンセルする方法について説明します。
BMDフォーラムで実装方法について質問があったのですが、DavinciResolveのスクリプトでは割り込み処理がないので中々に苦労されていました。
これをUITimer使って実装したらどうなるか、というのが趣旨ですね。
半年以上前にUITimerについての記事を投稿したものの、どうやら情報は海を越えていなかったようです。全然知られてなかった。
本記事と同じ内容は既にWeSuckLessフォーラムにも投稿していますが、やはり日本語情報も残しておいた方が良いよねということでnoteにも投稿することにしました。
長い処理を途中でキャンセルする方法
実装コード(Lua)
先に動くコードを載せておきます。BMDフォーラムの質問主さんのスクリプトを元に修正したものです。
local ui = fusion .UIManager
local disp = bmd.UIDispatcher(ui)
local cancel, coro
-- 途中でキャンセルできるようにしたい関数
local function some_long_process (timer)
print (' beginning long process')
for i = 1, 10 do
if not timer:GetIsActive() then -- タイマーを確認し、停止してたら関数を中断
coroutine.yield()
end
print ('executing: ', i)
bmd.wait(1)
end
print ('ending long process')
return
end
-- タイマーの設定
local cancel_timer = ui:Timer {
ID = 'cancel_timer',
Interval = 1000, -- タイマー間隔は1秒
SingleShot = true
}
local function my_window()
local width,height = 200, 50
local win = disp:AddWindow({
ID = "my_window",
WindowTitle = "My Window",
WindowFlags = {Window = true, WindowStaysOnTopHint = true,},
Geometry = {100, 100, width, height},
Spacing = 10,
Margin = 20,
ui:VGroup{
ID = 'root',
Weight = 0,
ui:HGroup{
Weight = 0,
ui:Button{
ID = "start",
Text = "Start",
},
ui:Button{
ID = "cancel",
Text = "Cancel",
},
},
},
})
win:RecalcLayout()
function win.On.start.Clicked(ev)
if coro == nil then
cancel = false
coro = coroutine.create(some_long_process)
cancel_timer:Start()
coroutine.resume(coro, cancel_timer)
end
end
function win.On.my_window.Close(ev)
cancel = true
disp:ExitLoop()
end
function win.On.cancel.Clicked(ev)
cancel = true
end
return win
end
local my_window = my_window()
-- タイマー関数の設定
local timer_func = {}
timer_func[cancel_timer.ID] = function()
-- キャンセルボタンが押されたか、コルーチン化した関数が既に終了しているなら終了
-- そうでないなら関数処理とタイマーを再開
if (not cancel) and coroutine.status(coro) ~= "dead" then
my_window:Find('start').Enabled = false
cancel_timer:Start()
coroutine.resume(coro)
else
if cancel then
print(" long process cancelled")
end
coro = nil
my_window:Find('start').Enabled = true
end
end
-- イベントハンドラ:タイムアウト
function disp.On.Timeout(ev)
timer_func[ev.who]()
end
my_window:Show()
disp:RunLoop()
my_window:Hide()
ポイント①監視対象の関数からUITimerの停止状態を確認する
まず、一定間隔でボタン入力を検知させるためにUITimerを作成します。
そして、このUITimerが動いているか(一定時間経過したか)どうかはUITimer:GetIsActive()で確認できます。
動いていればtrue、停止していればfalseが返ります。
タイマーはSingleshot(一度きり設定)が有効なら指定時間経過後に自動停止するので、UITimer:GetIsActive()を使えば確実に判別可能。
ポイント②監視対象の関数を中断・再開する
DavinciResolveのスクリプトは割り込み処理はできません。
例えばボタンをクリックしても、現在実行中の関数処理が終わらないと、ボタンクリックに対応したイベント処理はされないのです。
つまり、ボタンクリックイベントを処理するためには関数を定期的に中断→再開する必要があります。
関数を途中で中断、再開するための仕組みとしてコルーチンがあります。
LuaでもPythonでも使えます。
上記の実装コードでは、下記の部分が該当しますね。
コルーチン化:coroutine.create(some_long_process)
コルーチンの実行・再開:coroutine.resume(coro)
コルーチンの中断:coroutine.yield( )
詳しいことは各自でお調べください。LuaとPythonでは書き方も変わりますし……。
さて、中断・再開する方法はわかりました。
あとはポイント①のUITimer:GetIsActive()で一定時間が経過したかどうかを判別して、タイマーが止まってたら処理を中断します。
ポイント③タイムアウトイベントでキャンセル状態を確認
監視対象の処理が終了したら、その間に発生したイベントの処理が順番に行われます。
タイマーが停止したことでタイムアウトイベントが発生するので、そこでキャンセル(ボタンがクリックされた)状態を確認します。
もしキャンセルされていればそのままコルーチンを破棄して、キャンセルされていなければタイマーとコルーチンを再開します。
ボタンをクリックしてキャンセルするときの処理を図にするとこんな感じでしょうか。
あとがき
説明は以上となります。
対象の関数をコルーチン化しなきゃいけないのがなんか嫌ですけど、あまり難しくないのが救いですね。
キャンセル可能な処理をいくつも作るつもりなら、なんか専用のラッパークラスでも作った方が良いかもしれません。
この記事が皆様のお役に立てば幸いです。
今年の冬はなんだか暖かいですが、最近は流石に冷えるようになってきました。体調にはお気を付けください。
サポートしていただけるとその分の価値を提供できてるんだなって励みになります。