AviUtl のスクリプトを書き始めた話


# はじめに
AviUtl で編集していて，ちょうどよいものがなくて苦労していたが，スクリプトは比較的簡単に書けるようだったので，書き始めた．それに関してのメモ書き．

やりたいことができるスクリプトがどこにあるかわからないし，本家の機能だけでも大量にありすぎてよくわからない AviUtl + 拡張編集ですが，「AviUtlは初心者なエンジニア」にとっては，とっととスクリプトの書き方を覚えて，自分で書いてしまったほうが早い．

# 参考サイト
- [さつき氏のユーザー記事](https://dic.nicovideo.jp/u/2787743)
 - なんでこんなところでという，AviUtl と拡張編集そのものに関する詳しい説明記事．
- lua.txt
 - 拡張編集の中にある公式ドキュメント．まずはドキュメントを読もう．
- [AviUtlスクリプトWiki](https://aviutlscript.wiki.fc2.com/wiki/%E3%83%88%E3%83%83%E3%83%97%E3%83%9A%E3%83%BC%E3%82%B8)
 - 触り始めたときはここを読んでやっていたけど，微妙かも．

# ハマったところ
## 改行コード
日本語の文字コードが Windows-31J なのは文字化けですぐにわかったが，改行コードを LF にした場合，トラックバーが1つおきに非表示になるなど，解釈困難なバグとして現れ，ハマった．いまやメモ帳も UTF-8 や LF を扱える時代だが，AviUtl のプラグインスクリプトでは改行コードを CRLF にする必要がある．

この問題は，「とりあえず既存のスクリプトをコピペしてちょっと改造しよう」というときに，コピペしただけなのにスクリプトが動かないというバグとして現れたため，戸惑った．

## デバッグツール
AviUtl でうまく動かないとき，そのままだとエラーメッセージが表示されず，辛い．[金の髭さんが紹介している](http://goldenhige.cocolog-nifty.com/blog/2011/11/aviutllua-7713.html)ツールの内，[デバッグモニタツール](https://www.vector.co.jp/soft/winnt/prog/se278126.html)のほうを使ってみたが，Windows10 64bit でも動き，紹介記事と異なり，日本語も表示されている．Windows 側が対応するようになったのかな？

## 仮想バッファとキャッシュバッファ
フレームバッファは最終的に書き出す対象なので，フレームバッファに描画してしまうとそれ以降にエフェクト等をかけられなくなってしまう．これを回避するために用意されているのが仮想バッファで，仮想バッファに描画したあと，それをオブジェクトとして読み込んで次の処理に回す，ということができる．

より複雑な処理を考えていくと，仮想バッファに描画したものを一旦とっておいて，別の仮想バッファに描画し，最後にこれらを重ねて描画して一つのオブジェクトにする，というような操作がしたくなる．そのために使えるのがキャッシュバッファである．具体的には，次のようなコードになる．

```Lua:三日月テスト.obj
-- 仮想バッファを作る
local w, h = 200, 200
obj.setoption("drawtarget", "tempbuffer", w, h)

-- 円を描画してからずらした円でくり抜き，三日月を作り，cache:crescent に退避する
obj.setoption("blend", "alpha_add")
obj.load("figure", "円", 0xffff00, 100)
obj.draw()
obj.setoption("blend", "alpha_sub")
obj.load("figure", "円", 0xffffff, 80)
obj.draw(-20, 0)
obj.copybuffer("cache:crescent", "tmp")

-- 新しく仮想バッファを作り，青い四角を描画してから，その上に三日月を描画する
obj.setoption("drawtarget", "tempbuffer", w, h)
obj.setoption("blend", 0)
obj.load("figure", "四角形", 0x000066, 1)
local x0, y0, x1, y1 = -100, -100, 100, 100
obj.drawpoly(x0,y0,0, x1,y0,0, x1,y1,0, x0,y1,0)
obj.copybuffer("obj", "cache:crescent")
obj.draw()

-- できたものをオブジェクトにして制御を戻す
obj.copybuffer("obj", "tmp")
```

これをカスタムオブジェクトとしてロードすると，次のような画像が出力できる．フレームバッファに直接描画しておらず，オブジェクトなので，このあとにアニメーション効果等を掛けることができる．
![三日月テストの出力](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483881/558df206-3d2d-655d-2cb1-3179130b81a9.png)

ポイントは`obj.copybuffer()`である．[AviUtlスクリプトWikiのobj.copybuffer()の説明](https://aviutlscript.wiki.fc2.com/wiki/obj.copybuffer)にはほとんど何も書かれてなくてわからなかったのだが，キャッシュバッファからのロードには`obj.load()`は使えず，`obj.copybuffer()`を使う必要があるのだ．

ちなみに，後始末では，`obj.load("tempbuffer")`とするより，`obj.copybuffer("obj", "tmp")`としたほうがいいかもしれない．条件は不明だが，前者で`obj.ox`，`obj.oz`がうまく反映されないことがあったので．

## local
Lua は，変数がデフォルトでグローバル変数になる言語である．こんな言語使うの久しぶりだよ……．プログラミングを始めたばかりの頃に作った Perl のコードに`my`をつけていったら動かなくなった地獄を思い出した．

AviUtl のスクリプトとして使う場合のグローバル/ローカル変数のスコープがよくわからないのだが，できる限り`local`をつけてローカル変数にしておいたほうが安全．すでに同じ名前のグローバル変数がある場合も`shadowing`されるし，同じスコープでローカル変数が定義されていて新たに`local`をつけて再定義しても問題ないので，ブロックの外で使い回すつもりのない代入箇所の全てに`local`をつけても問題ないようだ．……`local`そのものが醜いという問題を除けば．

- `--dialog`で設定する変数にも`local`をつけられるようだ．
- アンカーポイント用の変数はグローバル変数にしないと読めないようだ．

# 開発と公開
## GitHub
スクリプトの開発は git で管理したい．秘匿してもしょうがないので(あれば)，GitHub で管理して公開してしまうのが早いだろう．このとき，スクリプトそのものは Lua 言語で書くが，拡張子は拡張編集向けに`.obj`とかを使わないといけなくて，そのままではGitHubで正しくシンタックスハイライトされない．ので`.gitattributes`とかで [GitHub のシンタックスハイライト判定を上書きする](https://qiita.com/elim/items/3d353fe1086e5378a407) とよい．コメント欄にある通り，後で`.gitattributs`を足した場合は，スクリプトを commit し直すと，シンタックスハイライトが正しくなる．

どんなスクリプトなのかみたいだけのときもアーカイブを落として展開するのとか面倒なので，他の人も GitHub とかで公開してくれるといいのにな……．ちなみに私は [こんな感じに公開](https://github.com/cycloawaodorin/ksa) している．完全に自分用なので，公開されてて嬉しいかは微妙だが．

## シンボリックリンク
開発中の動作チェックなどにも AviUtl が必要だが，開発ディレクトリに AviUtl のコピーを置くのも，script フォルダに(周辺ドキュメントなども含む)リポジトリのワーキングツリーを置くのも微妙．なので，script フォルダの下に，ワーキングツリー内のスクリプトをまとめたディレクトリへのシンボリックリンクを置いておくのが便利だと思う．

他者の公開スクリプト等でも，ドキュメントとスクリプトを分離するのも，script フォルダ内にドキュメントを置くのも煩わしくて悩んでいたのだが，同じように別の場所にアーカイブしておいて，script フォルダにはシンボリックリンクを置くのがいい．`.auo`や`.auf`のプラグインもこのように管理すればよかったのだな……．

ちなみに，Windows でシンボリックリンクを作るには`mklink`(PowerShellでは`cmd /c "mklink"`)コマンドを使う．

## モジュール
使うかどうかに関わらず，スクリプトごとに自作の関数定義を書き込んでいる公開スクリプト開発者もいるが，流石に lambda 的に使うのでない関数はモジュールに分離して管理したほうがいいと思う．拡張編集のスクリプトでは，`.obj`等のファイルのある場所も`require`のサーチパスに含まれているので`.obj`等と同じ場所に`xxx.lua`を置けば，`local xxx = require("xxx")`という形でモジュールをロードできる．

比較してないけど，`require`はキャッシュされるから，スクリプトをロードするたびに関数をコンパイルするより，この方が軽いんじゃないかな．

- 参考：[Luaのモジュール徹底解説（Lua 5.1〜5.3対応）](https://qiita.com/mod_poppo/items/ef3d8a6fe03f7f426426)