2011年6月14日火曜日

スクリプトをプラグインにしてみる

前回は簡単な関数をプラグインにしたので、今回は簡単なユーザースクリプト関数をプラグインにしてみることにしました。

まずこんなスクリプトを用意してみます。
#DAR_Padding.avs
function DAR_Padding(clip c, float "dar_x", float "dar_y", int "color", int "align") {
    dar_x = default(float(dar_x), float(c.width))
    dar_y = default(float(dar_y), float(c.height))
    color = default(color, $000000)
    align = default(align, 16)

    Assert(dar_x > 0 && dar_y > 0, "\"dar_x\" and \"dar_y\" need to be positive values.")
    Assert(align >= 1, "align needs to be 1 or heigher integer.")

    flag = c.width * dar_y - c.height * dar_x
    subsample_h = c.IsYUY2 || c.IsYV16 || c.IsYV12 ? 2 : 1
    #IsYV411は現時点(avisynth2.6.0a3)ではまだ作られてないので、YV411非対応
    subsample_v = c.IsYV12 ? 2 : 1

    dest_width  = flag < 0 ? int(ceil(c.height * dar_x / dar_y)) : c.width
    dest_width  = dest_width + (dest_width % subsample_h)

    dest_height = flag > 0 ? int(ceil(c.width * dar_y / dar_x)) : c.height
    dest_height = dest_height + (dest_height % subsample_v)

    pad_left = (dest_width - c.width) / 2
    pad_left = pad_left - (pad_left % subsample_h)
    pad_left = adjust_align(pad_left, align, subsample_h)

    pad_right = dest_width - (c.width + pad_left)

    pad_top = (dest_height - c.height) / 2
    pad_top = pad_top - (pad_top % subsample_v)
    pad_top = adjust_align(pad_top, align, subsample_v)

    pad_bottom = dest_height - (c.height + pad_top)

    return c.AddBorders(pad_left, pad_top, pad_right, pad_bottom, color)
}

function adjust_align(int pad, int align, int subsample) {
    return pad % align == 0 ? pad : adjust_align(pad - subsample, align, subsample)
}
はやい話が指定したDARにあわせて左右(または上下)に自動的にAddBordersでパディングするという関数です。
さて、これをC++で書いてみるとこうなりました。
//DAR_Padding.dll
#include <math.h>
#include "windows.h"
#include "avisynth26.h"

#pragma  warning(disable:4996)

class DAR_Padding : public GenericVideoFilter {

    PClip padded;

public:
    DAR_Padding(PClip _child, const float _dar_x, const float _dar_y,
                const int align, const int color, IScriptEnvironment* env);
    ~DAR_Padding() { }
    PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
};

DAR_Padding::
DAR_Padding(PClip _child, const float _dar_x, const float _dar_y, const int color,
            const int align, IScriptEnvironment* env) : GenericVideoFilter(_child)
{
    double dar_x = !_dar_x ? (double)vi.width : (double)_dar_x;
    double dar_y = !_dar_y ? (double)vi.height : (double)_dar_y;

    double flag = vi.width * dar_y - vi.height * dar_x;
    int dest_width = flag < 0 ? (int)ceil(vi.height * dar_x / dar_y) : vi.width;
    int dest_height = flag > 0 ? (int)ceil(dest_width * dar_y / dar_x) : vi.height;

    int subsample_h = vi.SubsampleH();
    int subsample_v = vi.SubsampleV();
    //SubsampleH(),SubsampleV()は、自分で作ってavisynth.hに追加した関数(YV411対応)。
    dest_width += dest_width % subsample_h;
    dest_height += dest_height % subsample_v;

    int pad_left = (dest_width - vi.width) >> 1;
    pad_left -= (pad_left % subsample_h);
    while(pad_left % align)
        pad_left -= subsample_h;

    int pad_right = dest_width - (vi.width + pad_left);

    int pad_top = (dest_height - vi.height) >> 1;
    pad_top -= (pad_top % subsample_v);
    while(pad_top % align)
        pad_left -= subsample_h;

    int pad_bottom = dest_height - (vi.height + pad_top);

    vi.width = dest_width;
    vi.height = dest_height;

    try {
        AVSValue padargs[6] = {child, pad_left, pad_top, pad_right, pad_bottom, color};
        padded = env->Invoke("AddBorders", AVSValue(padargs, 6)).AsClip();
    } catch (IScriptEnvironment::NotFound) {
        env->ThrowError("DAR_Padding: Couldn't Invoke AddBorders.");
    }
}

PVideoFrame __stdcall DAR_Padding::
GetFrame(int n, IScriptEnvironment* env)
{
    return padded->GetFrame(n, env);
}

AVSValue __cdecl Create_DAR_Padding(AVSValue args, void* user_data, IScriptEnvironment* env)
{
    const float dx = args[1].AsFloat(0.0);
    const float dy = args[2].AsFloat(0.0);
    const int cl = args[3].AsInt(0);
    const int al = args[4].AsInt(16);

    if (dx < 0 || dy < 0)
        env->ThrowError("DAR_Padding: \"dar_x\" and \"dar_y\" need to be positive values.");
    if (align < 1)
        env->ThrowError("DAR_Padding: \"align\" needs to be 1 or higher integer.");

    return new DAR_Padding(args[0].AsClip(), dx, dy, cl, al, env);
}

extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env)
{
    env->AddFunction("DAR_Padding", "c[dar_x]f[dar_y]f[color]i[align]i", Create_DAR_Padding, 0);
    return 0;
}
わかったこと
たしかにC/C++で書くための煩雑な手続き(クラスの宣言とか)は必要になりますが、if/for/whileといったものが使える分、プラグインのほうがむしろ書きやすいかもしれません。
それに、AviSynthの内部情報に直接アクセスできる点も有利です。
三項演算子や再帰を使ってあれこれ考えるのに飽きた人は挑戦してみるといいかも。

0 件のコメント:

コメントを投稿