Log in

View Full Version : How to Write Dual Avisynth/VapourSynth Filters


MysteryX
17th June 2021, 23:53
I was wondering about how to port Avisynth filters to VapourSynth, and how to write code that works for both. I developed an elegant solution that will make it easy to make any filter cross-platform compatible. Compiles in Visual Studio without requiring GCC or any other fancy compilers.

The solution came thinking about what I learnt from Dependency Injection design patterns: turning spaghetti code into ravioli code. The idea is that modules should only communicate between each other via standard interfaces, without caring about how they are implemented. In particular, writing unit testing code requires inputs and outputs to pass through interfaces that can be swapped out. The same idea works perfectly here.

So I wrote base interfaces containing base features of Avisynth and VapourSynth. Simply copy/paste this folder into your project. (https://github.com/mysteryx93/FrameRateConverter/tree/master/Src/Environments) You can then add methods to ICommonEnvironment, ICommonVideo and ICommonFrame to suit your needs, with their respective implementations for both Avisynth and VapourSynth.

The next step is to turn your filter code into a base class (https://github.com/mysteryx93/FrameRateConverter/blob/master/Src/Common/ContinuousMaskBase.cpp) and REMOVE ANY DEPENDENCY TO AVISYNTH OR VAPOURSYNTH. Any platform-specific reference will show in red, which you can then easily replace with the ICommon interfaces.

Any work that remains platform-specific can remain in derived Avisynth and VapourSynth classes.

For a very simple filter, the VapourSynth-specific code is now this: (https://github.com/mysteryx93/FrameRateConverter/blob/master/Src/VapourSynth/ContinuousMaskVpy.cpp)

#include "ContinuousMaskVpy.h"

void VS_CC ContinuousMaskVpy::Create(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* api)
{
VpyPropReader prop = VpyPropReader(api, in);
VSNodeRef* src = prop.GetNode("clip");
int radius = prop.GetInt("radius", 16);

auto f = new ContinuousMaskVpy(in, out, src, core, api, radius);
f->CreateFilter(in, out);
}

ContinuousMaskVpy::ContinuousMaskVpy(const VSMap* in, VSMap* out, VSNodeRef* node, VSCore* core, const VSAPI* api, int _radius) :
VpyFilter(in, out, node, core, api),
ContinuousMaskBase(new VpyVideo(node, api), VpyEnvironment(api, core), _radius)
{
}

void ContinuousMaskVpy::Init(VSMap* in, VSMap* out, VSNode* node)
{
}

VSFrameRef* ContinuousMaskVpy::GetFrame(int n, int activationReason, void** frameData, VSFrameContext* frameCtx)
{
if (activationReason == arInitial)
{
api->requestFrameFilter(n, Node, frameCtx);
}
else if (activationReason == arAllFramesReady)
{
const VSFrameRef* src = api->getFrameFilter(n, Node, frameCtx);
VSFrameRef* dst = api->newVideoFrame(viSrc->format, viSrc->width, viSrc->height, src, core);

ProcessFrame(VpyFrame(src, api), VpyFrame(dst, api));

api->freeFrame(src);
return dst;
}

return nullptr;
}

void ContinuousMaskVpy::Free()
{
}


Avisynth version (https://github.com/mysteryx93/FrameRateConverter/blob/master/Src/Avisynth/ContinuousMaskAvs.cpp)
#include "ContinuousMaskAvs.h"

AVSValue __cdecl ContinuousMaskAvs::Create(AVSValue args, void* user_data, IScriptEnvironment* env)
{
return new ContinuousMaskAvs(args[0].AsClip(), args[1].AsInt(16), env);
}

ContinuousMaskAvs::ContinuousMaskAvs(PClip _child, int _radius, IScriptEnvironment* env) :
GenericVideoFilter(_child),
ContinuousMaskBase(new AvsVideo(_child), AvsEnvironment(env), _radius)
{
}

PVideoFrame __stdcall ContinuousMaskAvs::GetFrame(int n, IScriptEnvironment* env)
{
PVideoFrame src = child->GetFrame(n, env);
PVideoFrame dst = env->NewVideoFrame(vi);
ProcessFrame(AvsFrame(src, vi), AvsFrame(dst, vi));
return dst;
}

// Marks filter as multi-threading friendly.
int __stdcall ContinuousMaskAvs::SetCacheHints(int cachehints, int frame_range)
{
return cachehints == CachePolicyHint::CACHE_GET_MTMODE ? MT_NICE_FILTER : 0;
}


This method also has the benefit that VapourSynth code uses standard class instances just like Avisynth.

And by the way, the same DLL can be compatible with both Avisynth and VapourSynth, no need to compile 2 different projects.

I hope this will make it easier for others to write cross-platform plugins!

Note: I just finished porting my first plugins and there may still be bugs and improvements to add. Feel free to post any suggestions for improvements!