VirtualDub用のシンプルなプラグインを作成しています

ビデオ処理が徐々にOpenCL / CUDAに移行しているという事実にもかかわらず、VirtualDubは単純なビデオ操作のための便利なツールのままです。 フレームのトリミング、フィルターの追加、またはブレンドは、ffmpegコンソールからよりもはるかに便利です。 さらに、長年にわたって、多くの操作を迅速かつ便利に実行できる大量のフィルターが開発されてきました。 SDKの単純さにもかかわらず、プラグインを作成する際には微妙な違いがあります。 この記事は、彼らと協力することに専念しています。









SDKは、著者のサイトの リンクから入手できます。 最新バージョンは1.1(VDPluginSDK-1.1.zip)です。 便利なフォルダにダウンロードして解凍します。 内部にはPluginSDK.chmヘルプファイルがありますが、このテキストの一部はこのテキストです。 開発はMicrosoft Visual Studio Community 2015で行われ、古いバージョンと新しいバージョンの両方を使用できます。 環境設定を確認するには、srcフォルダーのプロジェクトファイル、新しいStudioバージョンのSamples.sln、または古き良きVisual Studio 6のSamplesVC6.dswを使用できます。サンプルを収集すると、SampleVideoFilter.vdfファイルがout \ Releaseまたはout \ Debugフォルダーに表示されます。 これはテストフィルターです。 確認するには、VirtualDub \ pluginsフォルダーに配置し、フィルターメニューから追加します。 すべてが機能する場合、Visual Studioは正しくインストールされています。









例として、ゼロからフィルターを作成しましょう。 このチュートリアルは、初心者向けまたはWin32 APIを思い出すために設計されています。 スタジオでダイナミックライブラリDLLの空のプロジェクトを作成します。









VirtualDubのプラグインにはvdf拡張子があるため、プロジェクトのプロパティで拡張子を変更するたびに名前を変更しないように、プロパティ→一般→ターゲット拡張子を.vdfに変更します。 すべての構成を変更するため、[構成設定]タブですべての構成とすべてのプラットフォームのプラットフォームで切り替えることを忘れないでください。









アンパックされたSDKからプロジェクトにincludeフォルダーをコピーし、Atl-Shift-Aまたはメニューの[追加]→[既存のアイテム]を使用してファイルをプロジェクトに追加します。 動作させるには、インクルードフォルダーのヘッダーファイルとVDXFrameヘルパーファイルのセットが必要です。 インクルードフォルダーを、システムが検索するフォルダーのリストに追加することを忘れないでください。 これは、[プロパティ]→[VC ++ディレクトリ]→[ディレクトリを含める]から実行し、プロジェクトルートへのリンクを$(ProjectDir)\ includeの形式で追加します。









VDXFrameライブラリをプロジェクトに追加します。例では、個別のモジュールとして使用されていますが、ライセンスで許可されているため、ソースコードの形式で追加します。 プロジェクトディレクトリにsrcフォルダーを作成し、SDKからVideoFilter.cpp、VideoFilterEntry.cpp、VideoFilterDialog.cpp、およびstdafx.cppのファイルをコピーします。 次に、ヘッダーファイルをinclude \ stdafx.hから以前に作成したincludeフォルダーにコピーします。 Atl-Shift-Aを使用するか、メニューの[追加]→[既存のアイテム]から、コピーしたファイルをプロジェクトに追加することを忘れないでください。 これで、ヘルパーライブラリの統合が完了しました。



コードを書くことにします。 [追加]→[既存のアイテム]またはキーボードショートカットCtrl-Shift-Aを使用して、新しいmain.cppファイルをプロジェクトに追加します。 次の行をメインに追加します



#include <vd2/VDXFrame/VideoFilter.h> #include <BlackWhiteFilter.h> VDXFilterDefinition filterDef_blackWhite = VDXVideoFilterDefinition<BlackWhiteFilter>("Shadwork", "Black White filter", "Example for VirtualDub Plugin SDK: Applies a Black White filter to video."); VDX_DECLARE_VIDEOFILTERS_BEGIN() VDX_DECLARE_VIDEOFILTER(filterDef_blackWhite) VDX_DECLARE_VIDEOFILTERS_END() VDX_DECLARE_VFMODULE()
      
      





プラグインには、フィルタークラスのラッパーとして機能するVDXFilterDefinitionクラスの形式のパラメーターを持つVDX_DECLARE_VIDEOFILTERマクロで記述された任意の数のフィルターを含めることができます。 フィルタ自体は、作成者、タイトル、説明の3つのテキストフィールドで説明されます。 VirtualDubの作成者がCamelCaseを使用してクラスに名前を付けるBlackWhiteFilterという名前のフィルタークラスを作成しましょう。BlackWhiteFilter.hファイルにVDXVideoFilterから継承した新しいクラスを作成します。 変数g_VFVAPIVersionには、APIのバージョンが含まれます。 virtualで定義された関数はSDKの一部であり、ToBlackAndWhiteメソッドは画像変換を実装します。



 #include <vd2/VDXFrame/VideoFilter.h> #include <vd2/VDXFrame/VideoFilterEntry.h> #ifndef FILTER_VD_BLACK_WHITE #define FILTER_VD_BLACK_WHITE extern int g_VFVAPIVersion; class BlackWhiteFilter : public VDXVideoFilter { public: virtual uint32 GetParams(); virtual void Start(); virtual void Run(); protected: void ToBlackAndWhite(void *dst, ptrdiff_t dstpitch, const void *src, ptrdiff_t srcpitch, uint32 w, uint32 h); }; #endif
      
      





BlackWhiteFilter.cppファイルに実装を記述します。Start()メソッドが最初に実行されます。これは、たとえばAVX命令セットまたはCUDAサポートとの互換性を判断するための予備ステップを目的としています。 今のところ空のままにしておきます。 VDXFrameヘルパーは、このクラスのスコープ内で、faと呼ばれるVDXFilterActivationクラスのインスタンスへのポインターを提供します。このインスタンスには、フレームとバッファーに関する情報が含まれています。



GetParams()メソッドは、フィルターの互換性を判断するためにVirtualDubによって使用され、FILTERPARAM列挙からビットマスクを返す必要があります





RGB32イメージを白黒に変換するフィルターの場合、FILTERPARAM_SWAP_BUFFERSとFILTERPARAM_PURE_TRANSFORMが適しています。 RGB32以外のカラーエンコーディングをサポートし、SDKバージョンが12未満である場合、g_VFVAPIVersionのチェックを記述し、サポートされている場合、フィールドfa-> src.mpPixmapLayout-> formatで受信した画像のフォーマットをチェックします。 VirtualDubの以前のバージョンは、RGB32以外の色表現をサポートしていませんでした。 処理を簡素化するために、RGB32形式に準拠して記述しますが、一般的にVirtualDubはVDXPixmapFormatにリストされている形式の大きなリストをサポートします。



 uint32 BlackWhiteFilter::GetParams() { if (g_VFVAPIVersion >= 12) { switch (fa->src.mpPixmapLayout->format) { case nsVDXPixmap::kPixFormat_XRGB8888: break; default: return FILTERPARAM_NOT_SUPPORTED; } } fa->dst.offset = 0; return FILTERPARAM_SWAP_BUFFERS; }
      
      





フレーム処理はRun()メソッドによって実行されます。 フレームと入力および出力バッファに関するデータは、VDXFilterActivationクラスのインスタンスである変数faに保存されます。 VirtualDubはトリミングをサポートしているため、座標x1、y1、x2、y2を使用してユーザーが選択したウィンドウに関する情報を取得することにより、処理アルゴリズムを最適化できます。 フレームデータは、srcおよびdstオブジェクト、それぞれ入力および出力バッファーに格納されます。



 class VDXFilterActivation { public: const VDXFilterDefinition *filter; // void *filter_data; VDXFBitmap& dst; VDXFBitmap& src; VDXFBitmap *_reserved0; VDXFBitmap *const last; uint32 x1; uint32 y1; uint32 x2; uint32 y2; VDXFilterStateInfo *pfsi; IVDXFilterPreview *ifp; IVDXFilterPreview2 *ifp2; // (V11+) uint32 mSourceFrameCount; // (V14+) VDXFBitmap *const *mpSourceFrames; // (V14+) VDXFBitmap *const *mpOutputFrames; // (V14+) };
      
      





バージョン12未満のSDKをサポートするコードを記述し続けると、Run()メソッドの実装は次の形式になります。



 void BlackWhiteFilter::Run() { if (g_VFVAPIVersion >= 12) { const VDXPixmap& pxdst = *fa->dst.mpPixmap; const VDXPixmap& pxsrc = *fa->src.mpPixmap; switch (pxdst.format) { case nsVDXPixmap::kPixFormat_XRGB8888: ToBlackAndWhite(pxdst.data, pxdst.pitch, pxsrc.data, pxsrc.pitch, pxsrc.w, pxsrc.h); break; } } else { ToBlackAndWhite(fa->dst.data, fa->dst.pitch, fa->src.data, fa->src.pitch, fa->dst.w, fa->dst.h); } }
      
      





構造内の生データの場所は、プラグインがサポートするバージョンによって異なります。 したがって、6つのパラメーターがToBlackAndWhite関数に渡されます。



  1. void * dst0-フレーム出力バッファー
  2. ptrdiff_t dstpitch-出力バッファーの合計文字列長(バイト単位)
  3. const void * src0-入力フレームバッファー
  4. ptrdiff_t srcpitch-入力バッファーの合計行長
  5. uint32 w-ピクセル単位のフレーム幅
  6. uint32 h-ピクセル単位のフレームの高さ


コードを簡素化するために、トリミングパラメータを無視するため、設定のトリミングパラメータに関係なく、フレームは同じ速度で処理されます。 バッファ内のポイントはkPixFormat_XRGB8888形式で保存され、32ビットを占有します。 フレームの白黒への最も単純な変換を実装します。 最適化タスクがないため、浮動小数点演算の計算で数式を使用します



グレー= 0.299 * R + 0.587 * G + 0.114 * B



2つのサイクルを整理します。1つは線を通過し、もう1つはポイントを通過します。128のポイントの色を決定する境界レベルを取ります。



 void BlackWhiteFilter::ToBlackAndWhite(void *dst0, ptrdiff_t dstpitch, const void *src0, ptrdiff_t srcpitch, uint32 w, uint32 h) { char *dst = (char *)dst0; const char *src = (const char *)src0; for (uint32 y = 0; y<h; ++y) { // Get scanline uint32 *srcline = (uint32 *)src; uint32 *dstline = (uint32 *)dst; for (uint32 x = 0; x<w; ++x) { // Process pixels uint32 data = srcline[x]; float gray = 0.299f * (data & 0x000000ff) + 0.587f * ((data & 0x0000ff00) >> 8) + 0.114f *((data & 0x00ff0000) >> 16); dstline[x] = gray < 128 ? 0x00000000 : 0x00ffffff; } src += srcpitch; dst += dstpitch; } }
      
      





プラグインを収集し、Windows-VirtualDub-Plugin-BlackWhite.vdfファイルをプラグインVirtualDubフォルダーにコピーしてアクティブにします。 リストでは、VDXFilterDefinition-Black Whiteフィルタークラスで設定した名前で表示されます。 64ビットバージョン用にビルドされたプラグインは、VirtualDubの32ビットバージョンでは表示されないため、プロジェクトのアクティブな構成を確認することを忘れないでください。









設定のないプラグインはかなり鈍いため、設定するオプションとプレビューボタンを追加します。 これを行うには、Win32 APIのジャングルに飛び込む必要がありますが、このトピックについては十分な本が書かれているため、詳細には触れません。



設定ウィンドウを視覚的に表現するには、ダイアログボックスが必要です。 メニューCtrl-Shift-A→リソース→Resource FileでResource.rcという名前の新しいリソースファイルを作成します。 [リソースの追加]→[ダイアログ]でダイアログボックスを追加し、その名前をIDD_DIALOG_BLACKWHITE_SETTINGに変更します。 デフォルトでは、2つの[OK]ボタンと[キャンセル]ボタンが既にあります。 英語ロケールではリソースを作成する方が適切です。そうしないと、キャンセルボタンで読めないロシア語フォントで問題が発生する場合があります。 IDC_SLIDER_THRESHOLDという名前の[プレビュー]ボタンを画面に追加します。 後で戻らないように、設定の残りのコントロールを追加します。これは、しきい値IDC_SLIDER_THRESHOLDおよびチェックボックスIDC_CHECK_INVERTEDを変更するためのスライダーで、これにより画像を反転できます。 これは、たとえば次のように実行できます。









VDXVideoFilterDialogから継承したBlackWhiteFilterDialogダイアログクラスを作成しましょう。



 #include <windows.h> #include <commctrl.h> #include <resource.h> #include <vd2/VDXFrame/VideoFilterDialog.h> #include <vd2/VDXFrame/VideoFilter.h> #ifndef FILTER_VD_BLACK_WHITE_DIALOG #define FILTER_VD_BLACK_WHITE_DIALOG class BlackWhiteFilterDialog : public VDXVideoFilterDialog { public: BlackWhiteFilterDialog(IVDXFilterPreview *ifp); bool Show(HWND parent); virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam); protected: IVDXFilterPreview *const mifp; bool OnInit(); bool OnCommand(int cmd); void OnDestroy(); }; #endif
      
      





プレビューウィンドウを制御するIVDXFilterPreviewクラスへのリンクがコンストラクターに渡され、ローカルリンクをmifp変数に格納します。



 BlackWhiteFilterDialog::BlackWhiteFilterDialog(IVDXFilterPreview *ifp):mifp(ifp){ }
      
      





Show(HWND parent)メソッドは、親コンストラクターへの呼び出しでオーバーロードされ、設定ダイアログIDD_DIALOG_BLACKWHITE_SETTINGのリソース識別子をパラメーターとして使用します



 bool BlackWhiteFilterDialog::Show(HWND parent) { return 0 != VDXVideoFilterDialog::Show(NULL, MAKEINTRESOURCE(IDD_DIALOG_BLACKWHITE_SETTING), parent); };
      
      





DlgProcは、ダイアログボックスからのメッセージを処理するために使用され、OnInit()、OnDestroy()メソッドのダイアログライフサイクルの処理、およびOnCommandのコントロールからのイベントの処理を実装します。



 INT_PTR BlackWhiteFilterDialog::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: return !OnInit(); case WM_DESTROY: OnDestroy(); break; case WM_COMMAND: if (OnCommand(LOWORD(wParam))) return TRUE; break; case WM_HSCROLL: if (mifp) mifp->RedoFrame(); return TRUE; } return FALSE; }
      
      





まず、[OK]ボタンと[キャンセル]ボタンでダイアログを閉じます。 さらに、Toggle((VDXHWND)mhdlg)メソッドを使用してプレビューウィンドウの表示を制御するプレビューハンドラーが必要です。



 bool BlackWhiteFilterDialog::OnCommand(int cmd) { switch (cmd) { case IDOK: EndDialog(mhdlg, true); return true; case IDCANCEL: EndDialog(mhdlg, false); return true; case IDC_PREVIEW: if (mifp) mifp->Toggle((VDXHWND)mhdlg); return true; } return false; }
      
      





ダイアログを操作するためのクラスが作成されました。これを呼び出す必要があります。そのため、BlackWhiteFilterクラスのConfigureメソッド(VDXHWND hwnd)をオーバーロードして実装します。



 bool BlackWhiteFilter::Configure(VDXHWND hwnd) { BlackWhiteFilterDialog dlg(fa->ifp); return dlg.Show((HWND)hwnd); }
      
      





プロジェクトをアセンブルし、プラグインファイルをVirtualDubフォルダーにコピーし、リストに新しいフィルターを追加して、ダイアログと使用可能なプレビューボタンを確認します。









構成ウィンドウはありますが、フィルターにはまだ設定がありません。実装に進みます。 2つの変数、しきい値の値としてのmTresholdと反転フラグmInvertのみを含むBlackWhiteFilterConfigクラスに設定を保存します。



 #ifndef FILTER_VD_BLACK_WHITE_CONFIG #define FILTER_VD_BLACK_WHITE_CONFIG class BlackWhiteFilterConfig { public: BlackWhiteFilterConfig() { mTreshold = 128; mInvert = 0; } public: int mTreshold; int mInvert; }; #endif
      
      





BlackWhiteFilterDialogクラスを編集し、BlackWhiteFilterConfigクラスの2つのインスタンスを追加して、構成mConfigNewおよびmConfigOldを保存します。 これらの変数は、設定の古い状態と変更された状態を保存し、ボタンが機能する必要があります

OKおよびキャンセル。 設定と構成の初期化を保存するパラメーターを追加して、コンストラクターを編集します。



 BlackWhiteFilterDialog::BlackWhiteFilterDialog(BlackWhiteFilterConfig& config, IVDXFilterPreview *ifp):mifp(ifp){ mConfigNew = config; }
      
      





設定はどこかに保存し、BlackWhiteFilterConfig mConfig変数をBlackWhiteFilterクラスに追加し、ConfigureメソッドのBlackWhiteFilterDialogクラスの初期化を新しいものに変更する必要があります。



 bool BlackWhiteFilter::Configure(VDXHWND hwnd) { BlackWhiteFilterDialog dlg(mConfig, fa->ifp); return dlg.Show((HWND)hwnd); }
      
      





次に、Win32コントロールを再度使用する必要があります。 BlackWhiteFilterDialogクラスでは、ダイアログで設定とその実装をリンクする2つのメソッドを記述します。



 void BlackWhiteFilterDialog::LoadFromConfig() { SendDlgItemMessage(mhdlg, IDC_SLIDER_THRESHOLD, TBM_SETPOS, TRUE, mConfigNew.mTreshold); SendMessage(mhdlg, IDC_CHECK_INVERTED, mConfigNew.mInvert, 0); } bool BlackWhiteFilterDialog::SaveToConfig() { int threshold = SendDlgItemMessage(mhdlg, IDC_SLIDER_THRESHOLD, TBM_GETPOS, 0, 0); int inverted = SendDlgItemMessage(mhdlg, IDC_CHECK_INVERTED, BM_GETCHECK, 0, 0); if (threshold != mConfigNew.mTreshold || inverted!= mConfigNew.mInvert) { mConfigNew.mTreshold = threshold; mConfigNew.mInvert = inverted; return true; } return false; }
      
      





対話のライフサイクルでこれらの2つの方法を使用することが残っています。 OnCommandでは、Okボタンに対してSaveToConfig()を呼び出し、Cancelボタンに対しては、mConfigNew = mConfigOldを割り当てることにより、古い設定セットを復元します。 ダイアログの初期パラメーターはOnInit()メソッドで構成され、スライダーの範囲は0〜255に設定され、フォーカスが設定されます。



 bool BlackWhiteFilterDialog::OnInit() { mConfigOld = mConfigNew; // Set up slider to range 0-255 SendDlgItemMessage(mhdlg, IDC_SLIDER_THRESHOLD, TBM_SETRANGE, TRUE, MAKELONG(0, 255)); LoadFromConfig(); // gain focus to slide control HWND hwndFirst = GetDlgItem(mhdlg, IDC_SLIDER_THRESHOLD); if (hwndFirst) SendMessage(mhdlg, WM_NEXTDLGCTL, (WPARAM)hwndFirst, TRUE); // init preview button HWND hwndPreview = GetDlgItem(mhdlg, IDC_PREVIEW); if (hwndPreview && mifp) { EnableWindow(hwndPreview, TRUE); mifp->InitButton((VDXHWND)hwndPreview); } return false; }
      
      





設定の変更は、RedoFrame()メソッドを使用してプレビューウィンドウに表示する必要があります。このため、(mifp && SaveToConfig())mifp-> RedoFrame( ) CheckBoxを処理するには、IDC_CHECK_INVERTED識別子のケースの条件をOnCommandメソッドに追加し、同じ更新を実行します。



 case IDC_CHECK_INVERTED: if (mifp && SaveToConfig())mifp->RedoFrame(); return true;
      
      





ToBlackAndWhiteメソッドを書き換えて、2つのパラメーター、反転としきい値を考慮に入れて、構成を使用します。 定数BST_UNCHECKEDはWin32 APIから継承され、true / falseフラグ値として使用されます。



 if (mConfig.mInvert == BST_UNCHECKED) { dstline[x] = gray < mConfig.mTreshold ? 0x00000000 : 0x00ffffff; } else { dstline[x] = gray > =mConfig.mTreshold ? 0x00000000 : 0x00ffffff; }
      
      





プロジェクトを組み立て、VirtualDubでフィルターを再度テストしました。反転を含めると、かわいい猫がゴシックな怖いものに変わりました。









決勝の少し前に残った。 VirtualDubフィルターは、設定ファイルへのパラメーターの保存をサポートしています。このため、設定クラスをシリアル化する必要があります。 これを行うには、BlackWhiteFilterクラスヘッダーに追加されるマクロVDXVF_DECLARE_SCRIPT_METHODS()と、設定ファイルからパラメーターを解析するためのGetSettingString、GetScriptStringおよびScriptConfigメソッドを記録および表示するための一連のメソッドがあります。 そこにある引数の数は、最後のパラメーターとしてマクロVDXVF_DEFINE_SCRIPT_METHODで設定されます。 BlackWhiteFilterクラスの新しいバージョンは次のようになります



 #include <vd2/VDXFrame/VideoFilter.h> #include <vd2/VDXFrame/VideoFilterEntry.h> #include <BlackWhiteFilterDialog.h> #ifndef FILTER_VD_BLACK_WHITE #define FILTER_VD_BLACK_WHITE extern int g_VFVAPIVersion; class BlackWhiteFilter : public VDXVideoFilter { public: virtual uint32 GetParams(); virtual void Start(); virtual void Run(); virtual bool Configure(VDXHWND hwnd); virtual void GetSettingString(char *buf, int maxlen); virtual void GetScriptString(char *buf, int maxlen); VDXVF_DECLARE_SCRIPT_METHODS(); protected: void ToBlackAndWhite(void *dst, ptrdiff_t dstpitch, const void *src, ptrdiff_t srcpitch, uint32 w, uint32 h); BlackWhiteFilterConfig mConfig; void ScriptConfig(IVDXScriptInterpreter *isi, const VDXScriptValue *argv, int argc); }; #endif
      
      





十分ではないメソッドを実装します。 VDXVF_DEFINE_SCRIPT_METHODマクロでパラメーターの数とそのタイプを宣言します。パラメーターは2つあり、両方とも整数であるため、初期化ストリングは「ii」になります。 サポートされている形式のリストはIVDXScriptInterpreterクラスにあり、整数、小数、および文字列パラメーターを使用できます。 GetSettingStringメソッドは、設定バーにパラメーターを表示します;これは、[フィルター]ウィンドウの[フィルターの説明]列でパラメーターをすばやく確認できる人に必要です。 GetScriptStringメソッドは、VirtualDub構成(* .vcf)ファイルに保存し、ScriptConfigメソッドを使用して読み取るためのパラメーターをフォーマットします。



 VDXVF_BEGIN_SCRIPT_METHODS(BlackWhiteFilter) VDXVF_DEFINE_SCRIPT_METHOD(BlackWhiteFilter, ScriptConfig, "ii") VDXVF_END_SCRIPT_METHODS() void BlackWhiteFilter::GetSettingString(char *buf, int maxlen) { SafePrintf(buf, maxlen, " (Treshold:%d, Invert:%d)", mConfig.mTreshold, mConfig.mInvert); } void BlackWhiteFilter::GetScriptString(char *buf, int maxlen) { SafePrintf(buf, maxlen, "Config(%d, %d)", mConfig.mTreshold, mConfig.mInvert); } void BlackWhiteFilter::ScriptConfig(IVDXScriptInterpreter *isi, const VDXScriptValue *argv, int argc) { mConfig.mTreshold = argv[0].asInt(); mConfig.mInvert = argv[1].asInt(); }
      
      





このコードを追加してプラグインを収集すると、[フィルター]ウィンドウでフィルター設定を確認し、[処理設定ファイルの保存]メニューからファイルに保存できます。









デフォルトでは、プロジェクトはシステムにインストールされたVCランタイムに依存してビルドされます。他のコンピューターで使用する予定の場合、アセンブリ中に、設定メニューConfiguration-> C / C ++-> Code Generation-> Runtime Libraryからマルチスレッド(/ MT)パラメーターを指定する必要があります。 プラグインのサイズは10倍になりますが、ユーザーは開発者が使用したバージョンのVisual Studioのランタイムを選択する必要はありません。









プロジェクトコードはgithubで入手できます。 この資料は、何かを迅速に行う必要があり、Win32 APIを使用することの微妙な点を思い出す必要がある人々を対象としています。 ビデオをシングルビットカラー表現のプラットフォームに転送するためにこのプラグインが必要であり、XnViewを介して毎回フレームを実行するのにうんざりしていました。



All Articles