字幕動画編集をPythonスクリプトで簡略化 (DaVinci Resolve API)

# はじめに
最近はにじさんじライバーの切り抜き投稿にハマってるんですが、動画編集はもちろん、ネタ探しも必要で膨大な時間が無くなります。英語リスニングの訓練もできてないしコーディングの勉強も凝った動画編集の研究もできず、切り抜きを作ったら休日が吹き飛んでました。
せめて編集を簡略化したいと思ったのがきっかけです。最低限のことは実現できたので、紹介してみます。

# DaVinci Resolve とは
今私が使ってる動画編集ソフト。

+ 無料（ハイクオリティな動画を作成するなら32K 120fps対応の有料版になるが、無料版で十分）
+ テロップ編集が手間（縁取り・影等を全て手作業で加えていく必要がある）
+ 多機能で、カラー編集やモーショングラフィックス等の映像加工がこれ１本でできるようになる

って感じ。
なんでもできる無料ソフトと言えばAviutlで、私も10年前はコレ使ってたんですが、動画出力モジュールの調整が別途必要で導入に時間を食われるんですよね。インストールすれば作業環境が整うようにしたかったので、無料版のあるパッケージソフトから選びました。
fusion使えば何でもできるんで割と満足です。ただし作業量は多くて編集が大変。

さて、自動化で軽く調べてみたらどうやらDaVinci Resolve APIが用意されてるとのこと。これでマクロを組めないかと考えました。
LUAとPythonでコーディングできるのですが、将来的なことを考えると汎用性のあるPythonかなと思い、こちらを学習してスクリプトを組むことにしました。

# 作業環境
+ Windows 11 Home
+ DaVinci Resolve 18.0B
+ Python 3.10.6

大抵のPython入門サイトではanacondaを入れるように言われるのですが、それでは上手く動かなかったです。あくまでPython本体が必要でした。

# DaVinci Resolveの初期設定
Pythonを使用するためには環境変数を設定する必要があるのですが、上手く通らなくて早速躓きました。最終的にはマニュアルとちょっと違う入れ方をしています。

~~~
P_RESOLVE_SCRIPT_API=%PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting
PYTHONPATH=%PYTHONPATH%;%P_RESOLVE_SCRIPT_API%\Modules\
RESOLVE_SCRIPT_LIB=C:\Program Files\Blackmagic Design\DaVinci Resolve\fusionscript.dll
~~~

「RESOLVE_SCRIPT_API」がマニュアルに書いてある変数名なのですが、頭に「P_」を付けないと動きませんでした。恐らく読み込み順（アルファベット順？）が悪さをしてるんじゃないかと思います。

# スクリプト
~~~Python:auto.py
#!/usr/bin/env python
import DaVinciResolveScript as dvr_script
import time
import csv

#ファイル読み込み
csv_file = open("C:\\test\\test.csv", "r", encoding="utf_8", newline="")
flist = csv.reader(csv_file, delimiter=",", lineterminator="\r\n", skipinitialspace=True)
header = next(flist)
textcount = -1
textlist = []
for row in flist:
	if textcount == -1:
		p_size = float(row[0])
		p_v_anchor = float(row[1])
		p_h_anchor = float(row[2])
		p_center_x = float(row[3])
		p_center_y = float(row[4])
		p_red1 = float(row[5])
		p_green1 = float(row[6])
		p_blue1 = float(row[7])
		p_red2 = float(row[8])
		p_green2 = float(row[9])
		p_blue2 = float(row[10])
		p_thickness2 = float(row[11])
		p_red5 = float(row[12])
		p_green5 = float(row[13])
		p_blue5 = float(row[14])
		p_thickness5 = float(row[15])
	else:
		textlist.append(row[0])
	textcount += 1
csv_file.close()

#タイムライン編集準備（resolve,projectManager,project,timelineの順に取得）
resolve = dvr_script.scriptapp("Resolve")
projectManager = resolve.GetProjectManager()
project = projectManager.GetCurrentProject()
timeline = project.GetCurrentTimeline()
#タイムラインがなければ新たに生成
if not timeline:
	mediapool = project.GetMediaPool()
	timeline = mediapool.CreateEmptyTimeline("Timeline1")

for i in range(0, textcount):
	#Text+挿入(返却値は挿入したText+)
	newtext = timeline.InsertFusionTitleIntoTimeline("Text+")
	#Fusionに設定されたツール一覧取得(1番目にText+、2番目にMediaOut)
	toolList = newtext.GetFusionCompByIndex(1).GetToolList()
	
	#テキスト本体
	#VerticalTopCenterBottomは基準上-1、基準下1
	#HorizontalLeftCenterRightは左寄せ-1、右寄せ1
	toolList[1].StyledText = textlist[i]
	toolList[1].Font, toolList[1].Style = "LightNovelPopV2", "V2"
	toolList[1].Size = p_size
	toolList[1].Center = [p_center_x, p_center_y]
	toolList[1].VerticalTopCenterBottom = p_v_anchor
	toolList[1].HorizontalLeftCenterRight = p_h_anchor
	toolList[1].Red1 = p_red1
	toolList[1].Green1 = p_green1
	toolList[1].Blue1 = p_blue1
	time.sleep(0.01)
	
	#縁取り白
	toolList[1].Enabled2 = 1
	time.sleep(0.01)
	toolList[1].Thickness2 = p_thickness2
	toolList[1].Red2 = p_red2
	toolList[1].Green2 = p_green2
	toolList[1].Blue2 = p_blue2
	time.sleep(0.01)
	
	#縁取り文字色
	toolList[1].Enabled5 = 1
	time.sleep(0.01)
	toolList[1].ElementShape5 = 1
	toolList[1].Thickness5 = p_thickness5
	toolList[1].Red5 = p_red5
	toolList[1].Green5 = p_green5
	toolList[1].Blue5 = p_blue5
	time.sleep(0.01)

print("finish!")
~~~

# 軽い解説
ファイル読み込み部分はよくあるPythonコードですので割愛。
タイムライン編集準備では、最終的に字幕オブジェクトを取り出したくて色々やってると思ってください。
「timeline.InsertFusionTitleIntoTimeline」で縁取り字幕が作れるText+を配置しています。これは取り込んだ文字列の数だけ作動します。
「toolList」が取得できれば、こいつが持ってる変数に値を入れてやると字幕パラメータの設定ができます。

+ StyledText 字幕テキスト
+ Font, Style 字幕のフォント 英語名表記が必要（ググれば調べ方は出てきます）
+ Size 字幕の大きさ
+ VerticalTopCenterBottom 上寄せか下寄せか中央揃えか
+ HorizontalLeftCenterRight 左寄せか右寄せか中央揃えか
+ Red1, Green1, Blue1 字幕の色（RGB）
+ Thickness2 縁取りの幅
+ ElementShape5 文字タイプ(0が字幕本体、1が縁取り、2が背景、3が背景枠）
ElementShape2はデフォルトが1で縁取りなので、設定不要です。

# 動かしてみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2837795/be49277b-c2d5-c7b2-644b-830d63dc29aa.png)
まずはこんな感じのスプレッドシートを作ります。1行目はヘッダで無視、2行目に字幕の設定値を入れます。いつも私は2重縁取り加工をしてるので、そうなるように設定。
3行目からは実際に取り込みたい字幕文字列を書くようにします。これをCSVで出力。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2837795/256b347d-f94c-c0cf-9410-a24c8f7f8963.png)
こんな感じになります。これをopen関数で指定するフォルダに格納。

次にDaVinci Resolveを立ち上げ、画面上部のメニューバーから「ワークスペース＞コンソール」を選びます。
出てきたコンソールの上部「py3」を選択して、スクリプトをそのまま打ち込んでEnter。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2837795/07e48085-651f-3469-43c4-f115f382cf28.png)
結果がこんな感じです。あとは動画を配置して、字幕の位置・長さを調整するだけでひとまず切り抜きが完成します。

# 棚上げ事項
### 字幕の表示時間を変更できない
オブジェクトの長さを変える関数が見当たりません。5秒固定。GetDuration()（注：Duration
…クリップやエフェクトをかける時間の長さのこと）はあるのにSetDuration()がないんです。
最終調整は手作業だとは思っているんですが、それでも大まかには変更できるようにしたかったです。
### 一部文字が正確に取り込めない
プログラミングあるあるエスケープ文字問題。
Pythonでは改行するときには"\n"と記載しますが、どうもDaVinci Resolveの仕様なのか、取り込み時に"\n"→"\\\n"に変換されているようです。そして"\\\n"が出力されると改行ではなく「\n」とそのまま出力されます。なので、字幕を２行に表示させたいときは手作業が入ります。
これ私がPythonの知識に疎いだけのような気もしてて…解決策ありそうなんですけど…だれか知ってる人…
### ときどき処理がスキップする
一番の大問題。うそでしょ。マクロとして致命的では。
sleep入れてるのはこのためです。ちょっとましになったけどそれでも時折スキップされます。
DaVinci Resolveって若干重いので、 それがダメなのかと考え設定を一部変えてみたんですが、完全には無くなりませんでした。これのせいで自動挿入した字幕の設定の目検チェックが必要…。

# 参考にしたサイト

https://note.com/hitsugi_yukana/n/n1601e81df8d7

Luaスクリプトの記事でしたが、適宜Pythonに読み替えながら参考にさせていただきました。
本当にありがとうございます。

# おわりに
色々問題はあるんですが、それでもこれで字幕文字列の作成作業が分離できました。メモ帳に書いて後で取り込めばいいので電車の中でできます。仕事の昼休みにやっても、傍から見れば文章書いてるだけなので何の問題もないです。ひとまず満足です。
もっと洗練させて、編集の手間を無くしていきたいんですけど…まずSetDuration()実装してくださいお願いします Dear Blackmagic Design。あと処理スキップホントきついんで誰か改善策教えてください。


# Youtubeチャンネル

https://www.youtube.com/channel/UC6XvoSmyZjMbrYzopOlVQaA/featured

普段は切り抜き動画を作ってます。よければどうぞ。