pixel_typeとThrowError()について     2004. 7.11 (01版)

さて、皆さんはあるフィルタを使おうとしたところ、
ビデオソースの色空間がサポートされてなくてエラーが表示された経験は一度はあろうかと思います。
ここでは、その色空間のタイプチェックとエラーで終了させる方法を覚えましょう。

色空間は、構造体VideoInfoの中のpixel_typeにセットされます。
この構造体情報は、(PClip) _child から GetVideoInfo() によってavisynth本体から情報を取得できます。
GenericVideoFilterクラスを使えば、インスタンスの生成時に既に (VideoInfo) vi に取得済みなので、
そのままviにアクセスできます。
vi.pixel_type
がそれです。
これにどういう値が入っているのかちょっと見てみましょう。
--------- avisynth.h : GenericVideoFilterの定義(抜粋) -----
// instantiable null filter
class GenericVideoFilter : public IClip {
protected:
  PClip child;
  VideoInfo vi;
public:
  GenericVideoFilter(PClip _child) : child(_child) { vi = child->GetVideoInfo(); }
-------------------------------------------------------------
--------- avisynth.h : VideoInfoの定義(抜粋) --------------
  // Colorspace properties.
  enum {
    CS_BGR = 1<<28,  
    CS_YUV = 1<<29,
    CS_INTERLEAVED = 1<<30,
    CS_PLANAR = 1<<31
  };

  // Specific colorformats
  enum { CS_UNKNOWN = 0,
         CS_BGR24 = 1<<0 | CS_BGR | CS_INTERLEAVED,
         CS_BGR32 = 1<<1 | CS_BGR | CS_INTERLEAVED,
         CS_YUY2 = 1<<2 | CS_YUV | CS_INTERLEAVED,
         CS_YV12 = 1<<3 | CS_YUV | CS_PLANAR,  // y-v-u, planar
         CS_I420 = 1<<4 | CS_YUV | CS_PLANAR,  // y-u-v, planar
         CS_IYUV = 1<<4 | CS_YUV | CS_PLANAR  // same as above
         };
  int pixel_type;                // changed to int as of 2.5

  // useful functions of the above
  bool HasVideo() const { return (width!=0); }
  bool HasAudio() const { return (audio_samples_per_second!=0); }
  bool IsRGB() const { return !!(pixel_type&CS_BGR); }
  bool IsRGB24() const { return (pixel_type&CS_BGR24)==CS_BGR24; } // Clear out additional properties
  bool IsRGB32() const { return (pixel_type & CS_BGR32) == CS_BGR32 ; }
  bool IsYUV() const { return !!(pixel_type&CS_YUV ); }
  bool IsYUY2() const { return (pixel_type & CS_YUY2) == CS_YUY2; }  
  bool IsYV12() const { return ((pixel_type & CS_YV12) == CS_YV12)||((pixel_type & CS_I420) == CS_I420); }
  bool IsColorSpace(int c_space) const { return ((pixel_type & c_space) == c_space); }
  bool Is(int property) const { return ((pixel_type & property)==property ); }
  bool IsPlanar() const { return !!(pixel_type & CS_PLANAR); }
-------------------------------------------------------------
となっています。

次に、エラーを通知して処理を中止する方法ですが、
これは IScriptEnvironmentの中の
ThrowError()
関数を使用します。
--------- avisynth.h : IScriptEnvironmentの定義(抜粋) --------------
class IScriptEnvironment {
public:
  __declspec(noreturn) virtual void __stdcall ThrowError(const char* fmt, ...) = 0;
-------------------------------------------------------------

IScriptEnvironmentも引数として受け渡されてきていましたね。
したがって、
YUY2で無かったらエラーを表示して実行を中止するコードは以下のように書けます。
このチェックはコンストラクタの処理で行うのが適当でしょう。
Sample::Sample(PClip _child, IScriptEnvironment* env) : GenericVideoFilter(_child) {
    if (!vi.IsYUY2())
        env->ThrowError("Sample: requires YUY2 input.");
}
エラーメッセージには、誰がこのメッセージを表示したのか分かるようにフィルタ名を付けておきましょう。
なお、ThrowErrorのパラメタは、pritf()と同等です。

また、pixel_typeを変更する場合は、
vi.pixel_type = VideoInfo::CS_YUY2;
としますが、この説明は後日あらためて別の章で行います。

※pixel_typeの値は、avisynth 1.0x/2.0x系と2.5x系では異なります。

目次へジャンプ。
データアクセスの基礎(1) [YUY2編]     2004. 7.14 (02版)
2004. 7.11 (01版)

先ほどのスケルトンは何もしないものだったので、これを見ただけじゃビデオデータを
どうやって取り扱ったらよいのか判らなかった人もいると思います。
そこで、少しデータをアクセスしてみましょう。
なお、YUY2データは、常にQUADWORD(8の倍数, multiple of 8, mod 8)ライン境界になっています。

YUY2のビデオデータを入力して、色を消してモノクロのビデオデータにしてみようと思います。
YUY2のColor Formatを思い出してください。YUY2は以下のようになっていましたね。



モノクロにするには、uとvの値を全て128にすれば良いのでした。
では、書いてみましょう。

【処理1】
PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
    //*** in-place filtering ***
    PVideoFrame src = child->GetFrame(n, env);
    env->MakeWritable(&src);
    const int   pitch   = src->GetPitch();
    const int   rowsize = src->GetRowSize();
    const int   height  = src->GetHeight();
    const int   modulo  = pitch - src->GetRowSize();
    const BYTE *srcp    = src->GetReadPtr();
    BYTE       *dstp    = src->GetWritePtr();
    
    for (int j=0; j<height; j++) {
        for (int i=1; i<rowsize; i+=2) {
            dstp[j*pitch+i] = 0x80;     // u or v
        }
    }
    return src;
}

const int   pitch   = src->GetPitch();
       avisynthでは、YUY2の場合、データ境界は常にquadword単位(8の倍数、multiple of 8, mod 8)と
       なっているため、pitchも8の倍数となります。

BYTE* dstp = src->GetWritePtr();
       dstpはフレームデータの格納されている領域(1次元配列のデータテーブル)の先頭を指していますね。
       データにはポインタでアクセスしても良いし、配列のデータとしてアクセスすることもできます。

for (int j=0; j<height; j++) {
    for (int i=1; i<rowsize; i+=2) {
        dstp[j*pitch+i] = 0x80;     // u or v
    }
}
       2重のループで1画面の全データをなめています。
       外側のループは、変数 j で高さ、つまりライン数分回します。
       内側のループは、変数 i で1ライン分のデータを処理しています。
       i+=2 これは i=i+2と記述するのと同義です。
       (前者の記述はより最適化に向いてるわけですが、コンパイラの最適化により
        生成するコードに差は出ないので好きな方で書けばよいと思います。)
       iが1,3,5,7,...をとるようにしています。
       (上図を見れば、これでuとvのデータに交互にアクセスできていることが理解できるでしょう。)
       
       j*pitch+i    pitchはライン間の距離でしたね。
                    2重のループとこの式で、1画面全てのuとvのデータの位置を指すことができます。
       0x80         16進数表記です。10進数で記述すれば、128です。
【一口ポイント - MakeWritable -】
       MakeWritableの前後では、ポインタの指すアドレスは変化しているかも知れない。

       const BYTE* srcp0   = src->GetReadPtr();
       BYTE*       dstp0   = src->GetWritePtr();
       env->MakeWritable(&src);
       const BYTE *srcp    = src->GetReadPtr();
       BYTE       *dstp    = src->GetWritePtr();

       {
           char msg[256];
           sprintf(msg,"\nsrcp=%08x - %08x\ndstp=%08x - %08x", srcp0, srcp, dstp0, dstp);
           OutputDebugString(msg);
       }
       でdebugviewで結果を見ると、
           [388] srcp=03d60020 - 04020020
           [388] dstp=00000000 - 04020020
           [388] 
           [388] srcp=03d60020 - 0c3d0020
           [388] dstp=00000000 - 0c3d0020
       となっています。
       つまり、env->GetReadPtr() の方は、指しているアドレスは変化することもありますが、
       env->MakeWritable(&src)の前でも後でも構いません。
       でも env->GetWritePtr() の方は必ず後で実行する必要があるということです。

       実際にMakeWritable()で何が行われているのか、もうちょっと見てみましょう。
       (改行位置を多少変更しています)

       --------  core\avisynth.cpp 807:   -------------------------------
       bool ScriptEnvironment::MakeWritable(PVideoFrame* pvf) {
         const PVideoFrame& vf = *pvf;
         // If the frame is already writable, do nothing.
         if (vf->IsWritable()) {
           return false;
         }
       
         // Otherwise, allocate a new frame (using NewVideoFrame) and
         // copy the data into it.  Then modify the passed PVideoFrame
         // to point to the new buffer.
           const int row_size = vf->GetRowSize(); 
           const int height = vf->GetHeight();
           PVideoFrame dst;
           if (vf->GetPitch(PLANAR_U)) {
             // we have no videoinfo, so we can only assume that it is Planar
             // Always V first on internal images
             dst = NewPlanarVideoFrame(row_size, height, FRAME_ALIGN,false);
           } else {
             dst = NewVideoFrame(row_size, height, FRAME_ALIGN);
           }
           BitBlt(dst->GetWritePtr(), dst->GetPitch(), vf->GetReadPtr(),
                    vf->GetPitch(), row_size, height);
           // Blit More planes (pitch, rowsize and height should be 0, if none is present)
           BitBlt(dst->GetWritePtr(PLANAR_V), dst->GetPitch(PLANAR_V), vf->GetReadPtr(PLANAR_V), 
                    vf->GetPitch(PLANAR_V), vf->GetRowSize(PLANAR_V), vf->GetHeight(PLANAR_V));
           BitBlt(dst->GetWritePtr(PLANAR_U), dst->GetPitch(PLANAR_U), vf->GetReadPtr(PLANAR_U),
                    vf->GetPitch(PLANAR_U), vf->GetRowSize(PLANAR_U), vf->GetHeight(PLANAR_U));
       
           *pvf = dst;
           return true;
       }
       -------------------------------------------------------------------

       --------  avisynth.h 305: -----------------------------------------
       class VideoFrame {
         const BYTE* GetReadPtr() const { return vfb->GetReadPtr() + offset; }
       
         BYTE* GetWritePtr() const {
           if (vfb->GetRefcount()>1) {
             _ASSERT(FALSE);
             //throw AvisynthError("Internal Error - refcount was more than one!");
           }
           return IsWritable() ? (vfb->GetWritePtr() + offset) : 0;
         }
       }
       -------------------------------------------------------------------

       つまり、Writableなら何もしませんが、
       Writableで無かったら、新たにVideoFrameを獲得してデータをコピーしています。

別の記述をしてみましょう。

【処理2】
    for (int j=0; j<height; j++) {
        for (int i=0; i<rowsize; i+=4) {
            dstp[i+1] = 0x80;     // u
            dstp[i+3] = 0x80;     // v
        }
        dstp += pitch;
    }

これは、vとuを個別にアクセスしています。
内側の i ループは、4バイト単位のステップ幅です。

同じことをポインタでアクセスするように変更してみましょう。

【処理3】
    for (int j=0; j<height; j++) {
        for (int i=0; i<rowsize>>2; i++) {
             dstp++;          // y0 skip!!
            *dstp++ = 0x80;   // u
             dstp++;          // y1 skip!!
            *dstp++ = 0x80;   // v
        }
        dstp += modulo;
    }

ここで注意することは、1ライン分のデータには余白がくっついているかも知れないので、
その分をスキップすることを忘れないようにすることです。

今度はDWORD単位でアクセスしてみましょう。

【処理4】
PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
    //*** in-place filtering ***
    PVideoFrame src = child->GetFrame(n, env);
    env->MakeWritable(&src);
    const int   pitch   = src->GetPitch();
    const int   rowsize = src->GetRowSize();
    const int   height  = src->GetHeight();
    const int   modulo  = pitch - src->GetRowSize();
    const BYTE *srcp    = src->GetReadPtr();
    DWORD      *dstp    = (DWORD*)src->GetWritePtr();
    
    for (int j=0; j<height; j++) {
        for (int i=0; i<rowsize>>2; i++) {
            *dstp++ = (*dstp & 0x00ff00ff) | 0x80008000;    // vvyyuuyy
        }
        dstp = (DWORD*)((BYTE*)dstp + modulo);
    }
    return src;
}

DWORD*      dstp    = (DWORD*)src->GetWritePtr();
        このcast変換はCスタイル(old style)です。
        c++的には、reinterpret_cast<DWORD*>()となります。

0x00ff00ff, 0x80008000 // vvyyuuyy
        ご存知だと思いますが、Intel x86 CPUはメモリアクセスはリトルエンディアンです。
        (下位アドレスが下位バイトから入ります。
         余談ですがモトローラCPUはビッグエンディアンです。)
        メモリ上で、下位アドレスから順に 00 01 02 03 とあれば、
        レジスタに読み込んだ場合には、03 02 01 00 となります。
        なので、メモリ上 (y0,u,y1,v)という並びは、DWORD単位のレジスタアクセスでは
        (v,y1,u,y0)となります。

dstp++;
dstp = (DWORD*)((BYTE*)dstp + modulo);
        dstpは、(DWORD*)なので、++はDWORD単位にインクリメントされます。
        moduloはBYTE単位での値なので、こういうcastが必要になってきます。
        dstp += (modulo>>2);
        でも構いません。

最後にQUADWORD単位にしてみましょう。

【処理5】
PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
    //*** in-place filtering ***
    PVideoFrame src = child->GetFrame(n, env);
    env->MakeWritable(&src);
    const int       pitch   = src->GetPitch();
    const int       rowsize = (src->GetRowSize() + 4) & -8;
    const int       height  = src->GetHeight();
    const int       modulo  = pitch - src->GetRowSize();
    const __int64  *srcp    = (__int64*)src->GetReadPtr();
    __int64        *dstp    = (__int64*)src->GetWritePtr();
    
    for (int j=0; j<height; j++) {
        for (int i=0; i<rowsize>>3; i++) {
            *dstp++ = (*dstp & 0x00ff00ff00ff00ffi64) | 0x8000800080008000i64;
        }
        dstp += (modulo>>3);
    }

    return src;
}

const int       rowsize = (src->GetRowSize() + 4) & -8;
       rowsizeを8の倍数にround-upしています。
       場合によっては、実際のrowsizeより大きくなりpadding dataにまで書き込むことに
       なりますが、問題はありません。
       またこの切り上げは、rowsize = (src->GetRowSize() + 7) & ~7;
       と書いたほうが一般的かも知れません。

どうでしょうか。
スケルトンでは「お約束」ばかりでC++の文法を知っていても何が何だかわからなかったかも
知れませんが、実際の処理でやっと馴染みのある式が出てきたことだと思います。
これで大体どうやってデータを取り扱うのかイメージが掴めたでしょうから、
色々と応用してみてください。

目次へジャンプ。
データアクセスの基礎(2) [YV12編]     2004. 7.14 (02版)
2004. 7.13 (01版)

次に、YV12のデータを処理してみましょう。
YV12/I420 Progressive
YV12/I420 Interlaced

YV12とI420は、U平面とV平面の順番が異なるだけで、avisynthが読み込み時に自動的に
データポインタを設定してくれるので、特に違いを意識する必要はありません。
avisynthから出力時には、YV12に統一されます。

なお、YV12のラインデータ境界は、Yは16の倍数(multiple of 16,mod 16)、
U及びVは8の倍数((multiple of 8,mod 8)となっています。

では、同様にモノクロにするフィルタを記述してみましょう。

PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
    //*** in-place filtering ***
    PVideoFrame src = child->GetFrame(n, env);
    env->MakeWritable(&src);
    const int   pitchY    = src->GetPitch(PLANAR_Y);
    const int   pitchUV   = src->GetPitch(PLANAR_U);
    const int   rowsizeY  = src->GetRowSize(PLANAR_Y);
    const int   rowsizeUV = src->GetRowSize(PLANAR_U);
    //const int   rowsizeY  = src->GetRowSize(PLANAR_Y_ALIGNED); //Aligned rowsize (mod16)
    //const int   rowsizeUV = src->GetRowSize(PLANAR_U_ALIGNED); //Aligned rowsize (mod8)
    const int   heightY   = src->GetHeight(PLANAR_Y);
    const int   heightUV  = src->GetHeight(PLANAR_U);
    const int   moduloY   = pitchY  - rowsizeY;
    const int   moduloUV  = pitchUV - rowsizeUV;
    const BYTE *srcpY    = src->GetReadPtr(PLANAR_Y);
    const BYTE *srcpU    = src->GetReadPtr(PLANAR_U);
    const BYTE *srcpV    = src->GetReadPtr(PLANAR_V);
    BYTE       *dstpY    = src->GetWritePtr(PLANAR_Y);
    BYTE       *dstpU    = src->GetWritePtr(PLANAR_U);
    BYTE       *dstpV    = src->GetWritePtr(PLANAR_V);
    
    int         i,j;
    
//----- No touch data in Y plane. -----
//    for (j=0; j<heightY; j++) {
//        for (i=0; i<rowsizeY; i++) {
//            dstpY[i] = dstpY[i];
//        }
//        dstpY += pitchY;
//    }

    for (j=0; j<heightUV; j++) {
        for (i=0; i<rowsizeUV; i++) {
            dstpU[i] = 0x80;
            dstpV[i] = 0x80;
        }
        dstpU += pitchUV;
        dstpV += pitchUV;
    }
    return src;
}

const int   pitchUV   = src->GetPitch(PLANAR_U);
const int   rowsizeUV = src->GetRowSize(PLANAR_U);
const int   heightUV  = src->GetHeight(PLANAR_U);
const int   moduloUV  = pitchUV - rowsizeUV;
       U,V平面では、これらは同じ値になるため、共通にしています。

//const int   rowsizeY  = src->GetRowSize(PLANAR_Y_ALIGNED); //Aligned rowsize (mod16)
//const int   rowsizeUV = src->GetRowSize(PLANAR_U_ALIGNED); //Aligned rowsize (mod8)
       PLANAR_Y_ALIGNED,PLANAR_U_ALIGNEDを使うと、mod16、mod8に切り上げた値が通知されます。
       これらは、8バイト単位、16バイト単位(主にMMX/SSE,SSE2のため)
       で処理する場合に便利です。

この例題のモノクロ化にするには、Yの値はいじらないので記述はありませんが、
もしYの値を操作したい場合は、コメントアウトしているループを参考にしてください。
この例では、ソースがプログレッシブでもインターレースでも区別は不要です。
これを区別しなければならない場合については、また別途解説します。

YUY2の場合と同様に、4バイト単位、8バイト単位等のアクセスも各々考えてみてください。

目次へジャンプ。
引数(arguments)いろいろ (1) --- AVSValue     2004. 7.27 (01版)

スケルトンでは、引数はclipのみだったので、
今回は、複数の引数をとる場合を取り上げましょう。

(1) 引数の型

avisynthで取れる引数のタイプには、
分類 length
クリップ(IClip*)PClip4 bytes
整数型(Interger)int4 bytes
実数型(Floating pointer)float4 bytes
論理型(Boolian)bool1 byte
文字列型(String)const char *4 bytes
及び、それらの配列があります。
これらは、AVSValueというクラス(実体は、8バイト)で定義されています。
各々の変数値をAVSValue値に変換するには、
AVSValue(val) です。
型チェックには、
Defined(), IsClip(), IsInt(), IsFloat(), IsBool(), IsString(), IsArray()
を、
逆に、AVSValue値から各々の型に変換するには、
AsClip(), AsInt(), AsFloat(), AsBool(), AsString(),
AsInt(default), AsFloat(default), AsBool(default), AsString(default)

を使います。
配列を獲得するには、インデックス数を[ ]で付けます。
配列サイズを求めるには、ArraySize()を使います。
配列のインデックス値は、AVSValue(AVSValue* val, int array_size)を使います。

【例】
    AVSValue array_A[10];
    AVSValue array_B[20];
    AVSValue array_list[] = { AVSValue(array_A, sizeof(array_A)/sizeof(array_A[0])),
                              AVSValue(array_B, sizeof(array_B)/sizeof(array_B[0])),
                            };
    AVSValue all = AVSValue(array_list, sizeof(array_list)/sizeof(array_list[0]));
    AVSValue vals;

    array_A[0] = AVSValue(10);
    array_A[1] = AVSValue(true);
    array_A[2] = AVSValue(11.1);
    array_A[3] = AVSValue("huuuuum!!");
    array_B[0] = array_A[0];
    array_B[1] = array_A[1];
    array_B[2] = array_A[2];
    array_B[3] = array_A[3];
    vals = all;
    sprintf(msg, "%d  %s  %.1f  %s",
                     vals[1][0].AsInt(),
                     vals[1][1].AsBool() ? "true" : "false",
                     vals[1][2].AsFloat(),
                     vals[1][3].AsString()
           );
    OutputDebugString(msg);
    sprintf(msg, "array_A.size=%d array_B.size=%d",
                     vals[0].ArraySize(), vals[1].ArraySize());
    OutputDebugString(msg);

【結果】
     10  true  11.10  huuuuum!!
     array_A.size=10 array_B.size=20


(2) 引数に対応するパラメタの型チェック

パラメタチェック文字列の指定方法は、
指定文字意味
cクリップ型(clip)
i整数型(int)
f実数型(float)
b論理型(bool)
s文字列型(string)
.任意型
*0個以上の配列型
+1個以上の配列型
[ ]引数の名前付け
の組み合わせで指定します。

例えば、引数として、clip, 整数型、実数型、文字列型、論理型を各1個ずつとる場合、
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
    env->AddFunction("Sample", "cifsb", Create_Sample, 0);
    return "`Sample' Sample plugin";
}
となります。
また、名前付き引数で定義したい場合、
例えば、スクリプトにパラメタとして、
LoadPlugin("f:\src\sample\release\sample.dll")
AVISource("foo.avi")
Sample(arg1=1, arg2=1.0, arg3="abc", arg4=true)
return last
とかいうふうに指定させたいとすると、
    env->AddFunction("Sample", "c[arg1]i[arg2]f[arg3]s[arg4]b", Create_Sample, 0);
と記述します。

この場合の引数の受け渡しは、
AVSValue __cdecl Create_Sample(AVSValue args, void* user_data, IScriptEnvironment* env) {
    return new Sample(  args[0].AsClip()
                      , args[1].AsInt()
                      , args[2].AsFloat()
                      , args[3].AsString()
                      , args[4].AsBool()
                      , env);
}

class Sample : public GenericVideoFilter {
public:
    Sample(PClip _child, int arg1, float arg2, const char *arg3, bool arg4, IScriptEnvironment* env);
と書けます。

また、ここで arg をまとめて、
AVSValue __cdecl Create_Sample(AVSValue args, void* user_data, IScriptEnvironment* env) {
    return new Sample(args, env);
}

class Sample : public GenericVideoFilter {
public:
    Sample(AVSValue args, IScriptEnvironment* env) : GenericVideoFilter(args[0].AsClip()) {
        // ----- initialization ------
    }
}
と書くこともできます。

初期値を与える場合は、
AVSValue __cdecl Create_Sample(AVSValue args, void* user_data, IScriptEnvironment* env) {
    return new Sample(  args[0].AsClip()
                      , args[1].AsInt(1)
                      , args[2].AsFloat(1.0)
                      , args[3].AsString("abc")
                      , args[4].AsBool(true)
                      , env);
}
という風にします。

ところで引数は果たして幾つまで取れるのでしょうか?
試したことはありませんが、60個までなら大丈夫だと思います。

最後に、AVSValueは実際には以下のように定義されています。
--------- avisynth.h : AVSValueの定義(抜粋) --------------
class AVSValue {
public:

  AVSValue() { type = 'v'; }
  AVSValue(IClip* c) { type = 'c'; clip = c; if (c) c->AddRef(); }
  AVSValue(const PClip& c) { type = 'c'; clip = c.GetPointerWithAddRef(); }
  AVSValue(bool b) { type = 'b'; boolean = b; }
  AVSValue(int i) { type = 'i'; integer = i; }
//  AVSValue(__int64 l) { type = 'l'; longlong = l; }
  AVSValue(float f) { type = 'f'; floating_pt = f; }
  AVSValue(double f) { type = 'f'; floating_pt = float(f); }
  AVSValue(const char* s) { type = 's'; string = s; }
  AVSValue(const AVSValue* a, int size) { type = 'a'; array = a; array_size = size; }
  AVSValue(const AVSValue& v) { Assign(&v, true); }

  ~AVSValue() { if (IsClip() && clip) clip->Release(); }
  AVSValue& operator=(const AVSValue& v) { Assign(&v, false); return *this; }

  // Note that we transparently allow 'int' to be treated as 'float'.
  // There are no int<->bool conversions, though.

  bool Defined() const { return type != 'v'; }
  bool IsClip() const { return type == 'c'; }
  bool IsBool() const { return type == 'b'; }
  bool IsInt() const { return type == 'i'; }
//  bool IsLong() const { return (type == 'l'|| type == 'i'); }
  bool IsFloat() const { return type == 'f' || type == 'i'; }
  bool IsString() const { return type == 's'; }
  bool IsArray() const { return type == 'a'; }

  PClip AsClip() const { _ASSERTE(IsClip()); return IsClip()?clip:0; }
  bool AsBool() const { _ASSERTE(IsBool()); return boolean; }
  int AsInt() const { _ASSERTE(IsInt()); return integer; }   
//  int AsLong() const { _ASSERTE(IsLong()); return longlong; } 
  const char* AsString() const { _ASSERTE(IsString()); return IsString()?string:0; }
  double AsFloat() const { _ASSERTE(IsFloat()); return IsInt()?integer:floating_pt; }

  bool AsBool(bool def) const { _ASSERTE(IsBool()||!Defined()); return IsBool() ? boolean : def; }
  int AsInt(int def) const { _ASSERTE(IsInt()||!Defined()); return IsInt() ? integer : def; }
  double AsFloat(double def) const { _ASSERTE(IsFloat()||!Defined()); return IsInt() ? integer : type=='f' ? floating_pt : def; }
  const char* AsString(const char* def) const { _ASSERTE(IsString()||!Defined()); return IsString() ? string : def; }

  int ArraySize() const { _ASSERTE(IsArray()); return IsArray()?array_size:1; }

  const AVSValue& operator[](int index) const {
    _ASSERTE(IsArray() && index>=0 && index<array_size);
    return (IsArray() && index>=0 && index<array_size) ? array[index] : *this;
  }

private:

  short type;  // 'a'rray, 'c'lip, 'b'ool, 'i'nt, 'f'loat, 's'tring, 'v'oid, or 'l'ong
  short array_size;
  union {
    IClip* clip;
    bool boolean;
    int integer;
    float floating_pt;
    const char* string;
    const AVSValue* array;
//    __int64 longlong;
  };

  void Assign(const AVSValue* src, bool init) {
    if (src->IsClip() && src->clip)
      src->clip->AddRef();
    if (!init && IsClip() && clip)
      clip->Release();
    // make sure this copies the whole struct!
    ((__int32*)this)[0] = ((__int32*)src)[0];
    ((__int32*)this)[1] = ((__int32*)src)[1];
  }
};
------------------------------------------------------------

目次へジャンプ。
引数いろいろ(2) FilterRangeEx part(1) --- env->Invoke(), env->FunctionExists()     2004. 7.27 (01版)
では、実際に具体的な例をとりあげましょう。

課題として、「指定フレーム区間に指定フィルタを適用する」というものを作成してみましょう。
標準関数のApplyRangeやユーザー定義関数のFilterRange (nullinfo参照)と同じものですね。
このフィルタの定義は、
ApplyRange(clip clip, int start_frame, int end_frame, string filtername, args)
FilterRange(clip clip, int "start", int "end", string "filter")
ですが、このままだとあまり面白くないので、ここでは少し拡張してみましょう。
【拡張1】 ApplyRangeEx(clip clip, int s1, int e1, int s2, int e2, ... , string "filter", filter_args ... )
【拡張2】 FilterRangeEx(clip clip, int s1, int e1, int s2, int e2, ... , string "filter")
としましょう。
両者の違いは、フィルタの指定文字列に、パラメタ値と共に指定するか、別に指定するか、です。
【例】ApplyRangeEx(10, 19, "ColorYUV", 200)FilterRangeEx(10, 19, "ColorYUV(200)")

今回は、フォルダはSampleとは分けて作成することにしましょう。
   F:\Src
       |
       +--- include
       |       |
       |       +--- avisynth.h
       |
       +--- sample
       |       |
       |       +--- sample.cpp
       |       |
       |       +--- makefile
       |
       +--- FilterRangeEx
               |
               +--- FilterRangeEx.cpp
               |
               +--- makefile

さて、パラメタチェック用の文字列はどう書けばよいでしょうか?
パラメタが不定数なので、直接指定することはできませんね。
また、指定filterが取る引数の型もそれが幾つあるのかも不定ですね。
こういう場合には、任意型の配列を使えば解決します。
"ci*[filter]s.*"
このようにすれば良いわけです。
この指定により、argsには、
args[0]clip
args[1][0], args[1][1],...区間 int, int,...
args[2]filter名
args[3][0], args[3][1],...filterの持つ引数列
という風にチェックされて値が受け渡されてきます。

次に、どうやって他のフィルタを呼び出すのかですが、
Avisynthでは、この用途として、
IScriptEnvironmentクラスに、Invoke()メンバ関数が用意されています。
------------------ avisynth.h : 抜粋 -------------------------
class IScriptEnvironment {
public:
  class NotFound /*exception*/ {};  // thrown by Invoke and GetVar

  typedef AVSValue (__cdecl *ApplyFunc)(AVSValue args, void* user_data, IScriptEnvironment* env);

  virtual void __stdcall AddFunction(const char* name, const char* params, ApplyFunc apply, void* user_data) = 0;
  virtual bool __stdcall FunctionExists(const char* name) = 0;
  virtual AVSValue __stdcall Invoke(const char* name, const AVSValue args, const char** arg_names=0) = 0;
--------------------------------------------------------------

でも、これを見るとInvokeの指定形式は、【拡張1】のfilter_rargsを別指定するのには
そのままで使えそうですが、【拡張2】の形式から、このInvokeの形式に変換するはなんだか
大変そうですね。
まあ、この問題はちょっと於いて置いて、【拡張1】を先に考えてみましょう。

まず、Invoke関数ですが、これは以下のように使います。
AVSValue           args[]  = { child, 150 };
PClip              newclip = env->Invoke("ColorYUY2", AVSValue(args,2)).AsClip();
これは、ColorYUY2フィルタに、2つのパラメタ ( clip child, int 150 ) を渡しています。
なお、ColorYUY2のパラメタで最初のintはoff_yのことを意味しています。

ところで、Invoke関数には、第3パラメタを指定することもできます。
AVSValue           args[]      = { child, 50, 50, "tv->pc" };
static const char *arg_names[] = { 0, "cont_u", "cont_v", "levels" }
PClip              newclip     = env->Invoke("ColorYUY2", AVSValue(args,4), arg_names).AsClip();
これは、ColorYUY2(cont_u=50, cont_v=50, levels="tv->pc")という意味になります。
そうです、名前付き引数を指定することもできるというわけです。

次に、フィルタ名はここでは引数で貰うことにしてるので、実際にはそういう関数は存在しないかも
知れません。そこで指定された関数が登録されているのかどうかチェックすることにしましょう。
if (!env->FunctionExists("ColorYUY2"))
    env->TrowError("`ColorYUY2' filter not found.");

ここで、実際にこのInvoke関数を使う場合に問題になってくるのがクリップです。
どのクリップに対してInvokeしてもらうのかといえば、当然FilterRangeEx自体に受け渡されてきた
クリップになります。そこで引数リストの先頭にクリップを追加してみましょう。
for (int i=0; i<args.ArraySize(); i++) {
    new_args[i+1] = args[i];
}
new_args[0] = AVSValue(clip);

最後に、指定フィルタを適用する区間の指定についてですが、もう少し細かく仕様を規定しましょう。
(1) クリップの範囲外の場合は、範囲内に納める。
(2) 終了フレーム位置の指定は、開始フレーム位置より等しいか大きいものである必要がある。
(3) 複数の指定区間はそれぞれ独立とし、区間が前後しようが重複してようが構わないものとする。
(4) 区間指定が省略されれば、クリップ全体が指定されたものとする。
(5) 開始フレーム位置と終了フレーム位置は必ずペアになっていなければならない。

区間は、AVSValue _sectionsで受け渡されるものとすると、以下のような感じになります。
FilterRangeEx::FilterRangeEx(PClip _child, AVSValue _sections, const char *_filter, AVSValue _filter_args
                        , void *_user_data, IScriptEnvironment* env)
 : GenericVideoFilter(_child), filter(_filter), user_data(_user_data) {

    int     i;

    num_sections    = _sections.ArraySize();
    
    if((num_sections & 1) != 0)
        env->ThrowError("FilterRangeEx: 'start' and 'end' parameters are not a pair.\n");

    if(num_sections == 0) {
        num_sections = 2;
        sections[0] = 0;
        sections[1] = vi.num_frames - 1;
    } else {
        bool flag_err = false;
        for(i=0; i<num_sections; i++) {
            sections[i] = _sections[i].AsInt();
            if(sections[i] < 0)                 sections[i] = 0;
            if(sections[i] >= vi.num_frames)    sections[i] = vi.num_frames - 1;
            if((i&1)==1) {
                if(sections[i-1] > sections[i]) {
                    flag_err = true;
                    break;
                }
            }
        }
        if(flag_err)    env->ThrowError("FilterRangeEx: 'end' is less than 'start'.\n");
    }
}

目次へジャンプ。
引数いろいろ(3) FilterRangeEx part(2) --- Tryブロックと例外ハンドラ     2004. 7.31 (02版)
2004. 7.27 (01版)
さて、今回Invokeを使用するにあたって、フィルタ名と引数を指定してもらうことにした為、
フィルタ名が変だったり、引数がマッチしなかったりする可能性があります。
こういう場合には、Invoke関数のエラーハンドリングを行うことが望ましいですね。
以下のコードは、Invoke関数用としての2つの例外ハンドラを定義しています。

PClip   newclip = child;
try {
    newclip = env->Invoke(filter, args).AsClip();
}
catch(IScriptEnvironment::NotFound) {
    //--- error handler (1) ----
}
catch(AvisynthError ae) {
    //--- error handler (2) ----
    //throw    //re-create exception object
}

これでどのような動作になるのか、具体例を見てみましょう。
-----------------------------------------------------
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
    PClip       filtered = child;
    
    try {
        filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), names).AsClip();
    }
    
    catch(IScriptEnvironment::NotFound) {
        env->ThrowError("FilterRangeEx: fuction '%s' not found.\n", filter);
    }
    
    return filtered->GetFrame(n, env);
}
-----------------------------------------------------

このコードで、次のようなAVSスクリプトを実行してみましょう。
loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY3", 150)
return last

又は

loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY2", "abc")
return last

結果は下のようになります。

ところで、Invoke関数にarg_namesを追加すると、引数の型が不一致の際には、
exceptionが捕まえられずにすり抜けてしまいました。
-----------------------------------------------------
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
    PClip       filtered = child;
    
    try {
        static const char *arg_names[] = { 0, "off_y", "cont_u", "cont_v" };
        filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), arg_names).AsClip();
    }
    
    catch(IScriptEnvironment::NotFound) {
        env->ThrowError("FilterRangeEx: fuction '%s' not found.\n", filter);
    }
    
    return filtered->GetFrame(n, env);
}
-----------------------------------------------------
loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY2", "abc")
return last



この場合は、以下のようにするとキャッチできるようです。
-----------------------------------------------------
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
    PClip       filtered = child;
    
    try {
        static const char *arg_names[] = { 0, "off_y", "cont_u", "cont_v" };
        filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), arg_names).AsClip();
    }
    
    catch(IScriptEnvironment::NotFound) {
        env->ThrowError("FilterRangeEx: fuction '%s' not found.\n", filter);
    }
    catch(AvisynthError ae) {
        char msg[256];
        AVSValue subtitle_args[] = { child, "", 0, 32 };
        sprintf(msg, "FilterRangeEx: catch exception in '%s'.\n", filter);
        subtitle_args[1] = AVSValue(msg);
        filtered = env->Invoke("subtitle", AVSValue(subtitle_args,3)).AsClip();
        subtitle_args[0] = AVSValue(filtered);
        subtitle_args[1] = AVSValue(ae.msg);
        filtered = env->Invoke("subtitle", AVSValue(subtitle_args,4)).AsClip();
        //throw;
    }
    
    return filtered->GetFrame(n, env);
}
-----------------------------------------------------


ちなみに、前章で説明したFuctionExists()でチェックすると、結果は以下のようになります。
loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY3", 150)
return last

目次へジャンプ。
引数いろいろ(4) FilterRangeEx part(3) --- Eval,ScriptClip呼び出し、user_data     2004. 7.31 (03版)
2004. 7.27 (01版)
次に【拡張2】の方を考えましょう。
まず、関数登録ですが、これは簡単ですね。
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
    env->AddFunction("FilterRangeEx", "ci*[filter]s.*", Create_FilterRangeEx, 0);
    env->AddFunction("FilterRangeEx", "ci*[filter]s", Create_FilterRangeEx, 0);
    return "`FilterRangeEx' --- limited range section filtering";
}

でもこれだといったいどっちの形式で呼ばれたのか判断できません
もちろん、AddFunctionのコールバック関数を分ければ良いのですが、
今回は別の方法で判別できるようにしましょう。
AddFunctionの第4引数であるvoid *user_dataを使ってみます。
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
    env->AddFunction("FilterRangeEx", "ci*[filter]s.*", Create_FilterRangeEx, (void*)1);
    env->AddFunction("FilterRangeEx", "ci*[filter]s", Create_FilterRangeEx, (void*)2);
    return "`FilterRangeEx' --- limited range section filtering";
}

こうすれば、user_dataでどちらの形式なのか判別できます。
AddFunctionの登録順に気をつけてください。
これを逆順にすると、必ずuser_data=1の形式に該当してしまい、
env->AddFunction("FilterRangeEx", "ci*[filter]s", Create_FilterRangeEx, (void*)2);
を登録した意味が無くなってしまいます。
(何故なのかは、各自考えてみてください。)

【注】user_dataの使用方法として、本来意図した使用法ではないようですが、
  こういった使い方もできるという例です。

さて、【拡張2】の実現方法ですが、まず思いつくのが、自分でシコシコとスクリプトを
解読するというものですが、今はこれはやりたくはありません。
次に、Evalは使えないのか、ということが考えられますが、確かに使えます。
他にもScriptClipも使えそうです。
ただし、Evalは静的であり、逐次フレーム処理としてはちょっと問題がありそうです。
そこでまず、ScriptClipを使ってみましょう。
AVSValue filter_args[2];

filter_args[0] = AVSValue(child);
filter_args[1] = AVSValue("ColorYUY2(off_u=150)");
PClip filtered = env->Invoke("ScriptClip", AVSValue(filter_args, 2)).AsClip();

これで大丈夫そうです。
ただし、ScriptClipはエラーが発生した時にはクリップにメッセージを表示するようです。

次にEvalを使ってみましょう。
考え方は、フィルタを適用したクリップを別に作成しておいて、
指定範囲内か範囲外かでクリップを切り分けるというものです。
コンストラクタの中で、
evaled = env->Invoke("Eval", AVSValue(&filter_args[1], 1));
を実行

PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
    PClip       filtered = child;
    if(nが範囲内なら)  filtered = evaled.AsClip();
    return filtered->GetFrame(n, env);
}

最後のreturn evaled.AsClip()->GetFrame(n,env)でEvalで指定した内容が実行されます。
つまり、
AVISource("foo.avi")
a = trim(0, start-1)
b = trim(start, end).filter()
c = trim(end+1, 0)
return a+b+c

ということです。

最後に完成コードのサンプルを以下に示します。
なお、仕様を少し変更してあります。

(1) ApplyRangeEx(clip clip, string arg1_name, string arg2_name, ... \
                      int s1, int e1, int s2, int e2, ... , string "filter", filter_args ... )
(2) FilterRangeEx(clip clip, string arg1_name, string arg2_name, ... \
                      int s1, int e1, int s2, int e2, ... , string "filter", filter_args ... )
(3) ApplyRangeEx(clip clip, string arg1_name, string arg2_name, ... \
                      int s1, int e1, int s2, int e2, ... , string "filter")
(4) FilterRangeEx(clip clip, string arg1_name, string arg2_name, ... \
                      int s1, int e1, int s2, int e2, ... , string "filter")
(1)〜(3)は、指定フィルタを直接Invokeしますが、(4)はScriptClip(又はEval)をInvokeします。
#define USE_SCRIPTCRIPをコメントアウトすればEvalが使われます。
 この時、対象のクリップはlastとなりますので、その他のクリップの場合には、
 FilterRangeEx(10,19, 30,39, "clipA.ColorYUY2(cont_u=150, cont_v=150)")
 FilterRangeEx(10,19, 30,39, "ColorYUY2(clipA, cont_u=150, cont_v=150)")
 のように明示してください。

つまり、
FilterRangeEx(10,19, 30,39, "ColorYUY2(cont_u=150, cont_v=150)")は正常ですが、
ApplyRangeEx(10,19, 30,39, "ColorYUY2(cont_u=150, cont_v=150)")はエラーとなります。
(3)の記述は、パラメタ無しでフィルタをストレートに使う場合に使用します。
※本当は(1)でまかなっていて処理にも違いは無いので、(3)は登録する必要は無いのですが。
他にも
FilterRangeEx("cont_u", "cont_v", 10,19, 30,39, "ColorYUY2", 150, 150)
という記述もできます。
※逆に区間指定のInt値は省略することはできなくなっちゃいました。
 それならば、"cs*i*[filter]s.*"は"cs*i+[filter]s.*"とした方が良いかもしれませんね。

アーカイブ FilterRangeEx.zip

#include <windows.h>
#include <stdio.h>
#include "avisynth.h"

#define MY_NAME1     "ApplyRangeEx"
#define MY_NAME2     "FilterRangeEx"

#define USE_SCRIPTCRIP

enum {
    ERROR_NOT_SUPPORT_COLORSPACE,
    ERROR_NOT_PAIR_PARAMETER,
    ERROR_START_END_INVALID,
    ERROR_NOTFOUND,
    ERROR_EXCEPTION,
};
char *ERRMSG[] = {
    "%s: requires YUY2 input.\n",
    "%s: 'start' and 'end' parameters are not a pair.\n",
    "%s: 'end' is less than 'start'.\n",
    "%s: fuction '%s' not found.\n",
    "%s: catch exception in '%s'.\n"
};

class FilterRangeEx : public GenericVideoFilter {
private:
    int             num_sections, num_filter_args;
    void           *user_data;
    int             sections[60];
    const char     *filter;
    AVSValue        filter_args[61];
    const char     *filter_arg_names[60];
    AVSValue        evaled;
    char            msg[256];   // for debug message

public:
    FilterRangeEx(AVSValue _args, void *_user_data, IScriptEnvironment* env);
    PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
private:
    const char *GetName() { return (((int)user_data&1)==1 ? MY_NAME1 : MY_NAME2); }
};

FilterRangeEx::FilterRangeEx(AVSValue _args, void *_user_data, IScriptEnvironment* env)
 : GenericVideoFilter(_args[0].AsClip()), filter(_args[3].AsString()), user_data(_user_data) {

    int     i;
    
    //if (!vi.IsYUY2())
    //    env->ThrowError(ERRMSG[ERROR_NOT_SUPPORT_COLORSPACE], GetName());

    num_sections         = _args[2].ArraySize();
    num_filter_args      = _args[4].ArraySize();
    
    if((num_sections & 1) != 0)
        env->ThrowError(ERRMSG[ERROR_NOT_PAIR_PARAMETER], GetName());

    if(num_sections == 0) {
        num_sections = 2;
        sections[0] = 0;
        sections[1] = vi.num_frames - 1;
    } else {
        bool flag_err = false;
        for(i=0; i%lt;num_sections; i++) {
            sections[i] = _args[2][i].AsInt();
            if(sections[i] < 0)                 sections[i] = 0;
            if(sections[i] >= vi.num_frames)    sections[i] = vi.num_frames - 1;
            if((i&1)==1) {
                if(sections[i-1] > sections[i]) {
                    flag_err = true;
                    break;
                }
            }
        }
        if(flag_err)    env->ThrowError(ERRMSG[ERROR_START_END_INVALID], GetName());
    }

    filter_args[0] = child;
    switch((int)user_data) {
        case 1:
        case 2:
            for(i=0; i<num_filter_args; i++) {
                filter_args[i+1] = _args[4][i];
            }
            num_filter_args++;
            break;
        default:
            filter_args[1] = filter;
            num_filter_args = 2;
            break;
    }

/*
    {
        char tmpbuf[256], *tokenptr;
        strncpy(tmpbuf, filter, 255);
        if((tokenptr = strtok(tmpbuf, " (\t")) != NULL) {
            if(!env->FunctionExists(tokenptr))
                env->ThrowError(ERRMSG[ERROR_NOTFOUND], GetName(), tokenptr);
        }
    }
*/

    for(i=0; i<sizeof(filter_arg_names)/sizeof(filter_arg_names[0]); i++) {
        filter_arg_names[i] = 0;
    }
    for(i=0; i<_args[1].ArraySize(); i++) {
        if(_args[1][i].AsString()[0]) {
            filter_arg_names[i+1] = _args[1][i].AsString();
        }
    }
    
#ifndef USE_SCRIPTCRIP
    if((int)user_data == 4) {
        evaled = env->Invoke("Eval", AVSValue(&filter_args[1], 1));
    }
#endif
}

PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
    PClip       filtered = child;
    bool        flag_range_in = false;
    int         i;

    for(i=0; i<num_sections; i+=2) {
        if( (sections[i]<=n) && (sections[i+1]>=n) ) {
            flag_range_in = true;
            break;
        }
    }
    
    try {
        if(flag_range_in) {
            switch((int)user_data) {
                case 4:
#ifdef USE_SCRIPTCRIP
                    filtered = env->Invoke("ScriptClip", AVSValue(filter_args, 2)).AsClip();
#else
                    if(evaled.IsClip()) filtered = evaled.AsClip();
#endif
                    break;
                default:
                    filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), filter_arg_names).AsClip();
                    break;
            }
        }
    }
    
    catch(IScriptEnvironment::NotFound) {
        env->ThrowError(ERRMSG[ERROR_NOTFOUND], GetName(), filter);
    }
    catch(AvisynthError ae) {
        AVSValue subtitle_args[] = { child, "", 0, 32 };
        sprintf(msg, ERRMSG[ERROR_EXCEPTION], GetName(), filter);
        subtitle_args[1] = AVSValue(msg);
        filtered = env->Invoke("subtitle", AVSValue(subtitle_args,3)).AsClip();
        subtitle_args[0] = AVSValue(filtered);
        subtitle_args[1] = AVSValue(ae.msg);
        filtered = env->Invoke("subtitle", AVSValue(subtitle_args,4)).AsClip();
        //throw;
    }
    
    return filtered->GetFrame(n, env);
}


AVSValue __cdecl Create_FilterRangeEx(AVSValue args, void* user_data, IScriptEnvironment* env) {
    return new FilterRangeEx( args, user_data, env );
}

extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
    env->AddFunction(MY_NAME1, "cs*i*[filter]s.*", Create_FilterRangeEx, (void*)1);
    env->AddFunction(MY_NAME2, "cs*i*[filter]s.*", Create_FilterRangeEx, (void*)2);
    env->AddFunction(MY_NAME1, "cs*i*[filter]s", Create_FilterRangeEx, (void*)3);
    env->AddFunction(MY_NAME2, "cs*i*[filter]s", Create_FilterRangeEx, (void*)4);
    return "`FilterRangeEx' --- limited range section filtering";
}

目次へジャンプ。
引数いろいろ(5) FilterRangeEx part(4) --- おまけ ユーザ関数バージョン     2004. 7.27 (01版)

以前、某所で投稿したJAVA Scriptを使ったものとにーやん氏作成の再帰バージョンです。(自分のためのメモ)

【その1】
function F(clip clip, int "s1", int "e1", int "s2", int "e2"
\ , int "s3", int "e3", int "s4", int "e4", int "s5", int "e5") {
clip = IsInt(s1) ? clip.ApplyRange(s1,e1,"KenkunNR",256,2,24) : clip
clip = IsInt(s2) ? clip.ApplyRange(s2,e2,"KenkunNR",256,2,24) : clip
clip = IsInt(s3) ? clip.ApplyRange(s3,e3,"KenkunNR",256,2,24) : clip
clip = IsInt(s4) ? clip.ApplyRange(s4,e4,"KenkunNR",256,2,24) : clip
clip = IsInt(s5) ? clip.ApplyRange(s5,e5,"KenkunNR",256,2,24) : clip
return clip
}

【その2】
---- test.avs ---- 切り取り線-------------------------
LoadPlugin("warpsharp.dll")
AVISource("foo.avi")
ws = WScript("test.js", "JAVAScript")
ws.WSInvoke("FilterRangeEx", last, "ColorYUV(off_u=255)", 10, 19, 30, 39, 50, 59)
return last
---- test.avs ---- 切り取り線-------------------------

---- test.js ---- 切り取り線-------------------------
//************************************************************************************
//**   FilterRangeEx(clip clip, string filter, int s1, int e1, int s2, int s2, ...) **
//**                           ( must be 0 <= s1 <= e1 < s2 <= e2 < s3 <= e3 <... ) **
//************************************************************************************
function FilterRangeEx(clip, filter, avs_args)
{
    var start = new Array(), end = new Array();
    var parm_err = false;
    
    for (var i=2; i < FilterRangeEx.arguments.length; i+=2)
    {
        if (i+1 > FilterRangeEx.arguments.length
            || isNaN(FilterRangeEx.arguments[i])
            || isNaN(FilterRangeEx.arguments[i+1])
            || FilterRangeEx.arguments[i] > FilterRangeEx.arguments[i+1] )
        {
            parm_err = true; break;
        }
        else
        {
            start[start.length] = FilterRangeEx.arguments[i];
            end[end.length] = FilterRangeEx.arguments[i+1];
        }
    }
    for(i=1; i<start.length; i++)
    {
        if(start[i] <= end[i-1]) { parm_err=true; break; }
    }
    return parm_err ? AVS.MessageClip("parameter error") : FilterRange_exec(clip, filter, start, end);
}

function FilterRange_exec(clip, filter, start, end)
{
    AVS.last = clip;
    AVS.Eval("framecount = last.framecount()");
    var framecount = AVS.framecount;
    var range_first = (start[0]==0) ? "" : "Trim(0," + String(start[0]-1) + ") + ";
    for(var i=0; i<start.length; i++)
    {
        var range_in = "Trim(" + String(start[i]) + "," + String(end[i]) + ")." + filter;
        AVS.work = (i==0) ? AVS.Eval(range_first + range_in) : AVS.Eval("work + " + range_in);
        if(i+1<start.length)
        {
            if(end[i]+1 < start[i+1])
            {
                AVS.range_out = AVS.trim(clip, end[i]+1, start[i+1]-1);
                AVS.work = AVS.Eval("work + range_out");
            }
        }
    }
    if(end[end.length-1]+1 < framecount)
    {
        AVS.work = AVS.Eval("work + trim(" + String(end[end.length-1]+1) + ", last.framecount()-1)" );
    }
    return AVS.work;
}
---- test.js ---- 切り取り線-------------------------

【その3】にーやん作成の再帰呼び出しを使ったスクリプト 〜May 10, 2004の日記より抜粋

○説明

minamina氏作FilterRange関数を改造して、フィルタを適用できる範囲の数を増やした。
フィルタの適用範囲の数を調節することも可能。

メインのFilterRangePlus関数とサブのRecursiveA, RecursiveB, RecursiveCの3つの関数を
セットで使用します。 

○書式

FilterRangePlus(clip clip, string "filter", int "times",
\ int "s1", int "e1", int "s2", int "e2", int "s3", int "e3", ...)


○パラメータ

filter:使用したいフィルタ
times:フィルタを適用する範囲の数
s*:適用開始点(*は数字)
e*:適用終了点(*は数字)

※s*,e*は増やすことが可能(下記「応用」を参照)


○使用例

[重要]
適用範囲を複数指定する場合、フレーム番号が大きい範囲から順番に指定してください。
たとえば、3-6, 12-17, 21-28の3つの範囲を指定する時は、21-28, 12-17, 3-6の順になります。

×3,6, 12,17, 21,28
○21,28, 12,17, 3,6

FilterRangePlus("GreyScale()", 3, 21,28, 12,17, 3,6)


○やってはいけない

・フィルタ適用範囲の数(times)が間違っている。多すぎる場合はエラーが出るようにしています。
・フィルタ適用範囲の順番が小さい番号からになっている(例)6,9, 13,17
・フィルタ適用範囲がかぶっている(例)12,16, 8,14
・フィルタ適用範囲が連続している(例)5,9, 3,5→3,9と1つにまとめる


○応用(フィルタ適用範囲の数をふやす)

関数の()内に「,int "s*", int "e*"」を追加することで、フィルタ適用範囲の数を
増やすことができます。(*は数字)

(例)5箇所指定できるようにする 
function FilterRangePlus(〜 int "e3", int "s4", int "e4", int "s5", int "e5")


○メイン関数

function FilterRangePlus(clip clip, string "filter", int "times",
\ int "s1", int "e1", int "s2", int "e2", int "s3", int "e3")
{
Assert(IsInt(Eval("s" + String(times))), "timesが正しくない")
Assert(s1 > s2 && e2 < s1, "範囲指定が正しくない")
d = (Eval("s" + String(times)) < 2) ? clip.Trim(0, -1) :
\ clip.Trim(0, Eval("s" + String(times)) - 1)
Eval(RecursiveA("", times, filter))
Eval(RecursiveB("", times-1))
c0 = (e1 > clip.FrameCount()-2) ? BlankClip(clip,1) : clip.Trim(e1 + 1, 0)
c = (Eval("s" + String(times)) == 0) ? Eval(RecursiveC("", (times*2)-1) + "c0") :
\ d + Eval(RecursiveC("", (times*2)-1) + "c0")
c = (e1 > clip.FrameCount()-2) ? c.Trim(0, clip.FrameCount()-1) : c
return c
}


○サブ関数

function RecursiveA(string s, int times, string filter)
{
return (times == 0) ? s :
\ "c" + String(times*2-1) + " = clip.trim(s" + String(times) + ", e" +
\ String(times) + ")." + filter + " " + RecursiveA(s, times-1, filter)
}

function RecursiveB(string s, int times)
{
return (times == 0) ? s :
\ "c" + String(times*2) + " = clip.trim(e" + String(times+1) + "+1, s" +
\ String(times) + "-1)" + " " + RecursiveB(s, times-1)
}

function RecursiveC(string s, int times)
{
return (times == 0) ? s : "c" + String(times) + " + " + RecursiveC(s, times-1)
}

目次へジャンプ。

[PR]子育てママさんへ:3年毎に15万円うけとれる保険?