Skip to content

Instantly share code, notes, and snippets.

@stuxcrystal
Last active December 2, 2019 08:02
Show Gist options
  • Save stuxcrystal/f6a1722acb12d8ce6bef51b0ceb42d11 to your computer and use it in GitHub Desktop.
Save stuxcrystal/f6a1722acb12d8ce6bef51b0ceb42d11 to your computer and use it in GitHub Desktop.
I noticed writing plugins in c++ is way too boilerplaty...
/**
* C++ corutine based Vapoursynth-Filter-API
* Copyright (C) 2019 StuxCrystal
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _VSXA_HPP_
#define _VSXA_HPP_
#include "VapourSynth.h"
#include "VSHelper.h"
#include <functional>
#include <stdexcept>
#include <variant>
#include <memory>
namespace vsxa
{
template<typename T>
class bounded {
size_t size;
T* data;
public:
bounded() : data(nullptr), size(0) {}
bounded(T* d, size_t s) : data(d), size(s) {}
bounded(size_t s) : data(new (std::nothrow) T[s]), size(s) {};
bounded(T&& d) : data(new (std::nothrow) T(std::move(d))), size(1) {};
bounded(const T&& d) : data(new (std::nothrow) T(d)), size(1) {};
bounded(bounded<T>&& b) : size(b.size), data(b.detach()) {};
bounded(const bounded<T>&& b) : bounded(b.length()) {
T* ary = b;
if (ary == nullptr) {
delete[] this->data;
this->data = nullptr;
return;
}
std::copy(b.begin(), b.end(), this->begin());
}
bounded(std::initializer_list<T> values) : size(values.size()) {
std::move(values.begin(), values.end(), this->begin());
}
bounded<T> operator=(const bounded<T>&& other) {
this->destroy();
size = other.length();
if (size == 0) return;
data = new (std::nothrow) T[size];
std::copy(other.begin(), other.end(), this->begin());
}
inline operator T* () {
return data;
}
inline T& operator[](size_t n) {
if (n >= length()) throw std::out_of_range("Specified index out of range.");
return data[n];
}
inline T* get(size_t n) noexcept {
if (n >= length()) return nullptr;
return &data[n];
}
inline size_t length() {
return data == nullptr ? 0 : size;
}
inline T* detach() {
T* data = this->data;
this->data = nullptr;
return data;
}
inline void destroy() {
if (this->data != nullptr) {
for (T* obj = begin(); obj != end(); obj++) {
obj->~T();
}
delete this->data;
}
this->data = nullptr;
this->size = 0;
}
inline T* begin() {
return this->data;
}
inline T* end() {
return &this->data[length()];
}
~bounded() {
this->destroy();
}
};
template<typename T, typename... Args>
bounded<T> make_bounded(Args... v) {
bounded<T> result = bounded<T>(T(std::forward<Args>(v)...));
return result;
}
class callctx
{
VSCore* core;
const VSAPI* vsapi;
public:
callctx() : core(nullptr), vsapi(nullptr) {}
callctx(VSCore* c, const VSAPI* a) : core(c), vsapi(a) {}
const VSAPI* operator->() { return vsapi; }
operator VSCore* () { return core; }
};
template<typename F>
class filter
{
public:
virtual const char* name() = 0;
virtual VSFilterMode mode() { return fmParallel; };
virtual VSNodeFlags flags() { return (VSNodeFlags) 0; };
virtual const VSVideoInfo* info() = 0;
virtual F* init(int n, VSFrameContext* fctx, callctx cctx) = 0;
virtual VSFrameRef* advance(F* state, VSActivationReason r) = 0;
virtual void finish(F* state) = 0;
virtual ~filter() = 0;
};
namespace interop
{
template<typename F>
struct VSXAFilterInstanceData {
std::shared_ptr<bounded<F>> ary;
size_t position;
inline F* get() {
return this->ary->get(position);
}
};
template<typename T, typename F>
using VSXAFilterFunction = std::function<bounded<F>(const VSMap* in, VSMap* out, T userData, callctx ctx)>;
template<typename T, typename F>
struct VSXAFunctionData {
VSXAFilterFunction<T, F> func;
const char* name;
T data;
};
template<typename F>
static void VS_CC vsxaInit(VSMap* in, VSMap* out, void** instanceData, VSNode* node, VSCore* core, const VSAPI* vsapi) noexcept {
F* d = static_cast<VSXAFilterInstanceData<F>*>(*instanceData)->get();
VSVideoInfo vi;
d->info(vi);
vsapi->setVideoInfo(&vi, 1, node);
}
template<typename F>
static const VSFrameRef* VS_CC vsxaGetFrame(int n, int activationReason, void** instanceData, void** frameData, VSFrameContext* frameCtx, VSCore* core, const VSAPI* vsapi) noexcept {
F* d = static_cast<VSXAFilterInstanceData<F>*>(*instanceData)->get();
using request_data = typename F::request_data;
request_data* value = nullptr;
const VSFrameRef* ref;
try {
if (activationReason == arInitial) {
value = d->init(n, frameCtx, callctx(core, vsapi));
*frameData = value;
}
else {
value = static_cast<typename F::request_data*>(*frameData);
}
ref = d->advance(value, static_cast<VSActivationReason>(activationReason), frameCtx);
if (ref != nullptr) {
d->finish(value, frameCtx);
}
} catch (std::exception &exc) {
vsapi->setFilterError(exc.what(), frameCtx);
if (value != nullptr)
d->finish(value, frameCtx);
return nullptr;
} catch (...) {
vsapi->setFilterError("An unknown exception occured.", frameCtx);
if (value != nullptr)
d->finish(value, frameCtx);
return nullptr;
}
return ref;
}
template<typename F>
static void VS_CC vsxaFree(void* instanceData, VSCore* core, const VSAPI* vsapi) noexcept {
VSXAFilterInstanceData<F>* d = (VSXAFilterInstanceData<F>*)instanceData;
delete d;
}
template<typename T, typename F>
static void VS_CC vsxaCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi) noexcept {
VSXAFunctionData<T, F>* funcData = static_cast<VSXAFunctionData<T, F>*>(userData);
callctx ctx{ core, vsapi };
std::shared_ptr<bounded<F>> result;
try {
result = std::make_shared<bounded<F>>(std::move(funcData->func(in, out, funcData->data, ctx)));
}
catch (std::exception & exc) {
vsapi->setError(out, exc.what());
return;
}
catch (...) {
vsapi->setError(out, "Unknown exception occured.");
return;
}
for (size_t i = 0; i < result->length(); i++) {
F* filter = result->get(i);
vsapi->createFilter(in, out, funcData->name, vsxaInit<F>, vsxaGetFrame<F>, vsxaFree<F>, filter->mode(), filter->flags(), new VSXAFilterInstanceData<F>{ result, i }, core);
}
}
template<typename T, typename F>
static bounded<F> makeFilterByConstructor(const VSMap* in, VSMap* out, T userData, callctx ctx) {
return make_bounded<F>(in, out, userData, ctx);
}
template<typename T, typename F>
inline void vsxaRegister(VSRegisterFunction reg, VSPlugin* plugin, const char* name, const char* args, T data, VSXAFilterFunction<T, F> func) {
reg(name, args, vsxaCreate<T, F>, new VSXAFunctionData<T, F>{func, name, std::move(data) }, plugin);
}
template<typename T, typename F>
inline void vsxaRegister(VSRegisterFunction reg, VSPlugin* plugin, const char* name, const char* args, T data) {
vsxaRegister<T, F>(reg, plugin, name, args, data, makeFilterByConstructor<T, F>);
}
template<typename F>
inline void vsxaRegister(VSRegisterFunction reg, VSPlugin* plugin, const char* name, const char* args, VSXAFilterFunction<std::monostate, F> func) {
vsxaRegister<std::monostate, F>(reg, plugin, name, args, std::monostate(), func);
}
template<typename F>
inline void vsxaRegister(VSRegisterFunction reg, VSPlugin* plugin, const char* name, const char* args) {
vsxaRegister<std::monostate, F>(reg, plugin, name, args, std::monostate(), makeFilterByConstructor<std::monostate, F>);
}
}
}
#endif
#include <string>
#include <memory>
#include "VapourSynth.h"
#include "VSHelper.h"
#include "vsxa.hpp"
#include "vsxacoro.hpp"
#include "vsxautils.hpp"
using namespace vsxa;
class generic_error : public std::exception {
public:
generic_error(const char* msg) : std::exception(msg) {};
};
bounded<clip<async>> PropToClip(const VSMap* in, VSMap* out, std::monostate unused, vsxa::callctx ctx) {
VSNodeRef* clip = ctx->propGetNode(in, "clip", 0, 0);
VSVideoInfo vi = *ctx->getVideoInfo(clip);
vi.format = ctx->registerFormat(cmGray, stFloat, 32, 0, 0, ctx);
vi.width = 1;
vi.height = 1;
std::string propName = ctx->propGetData(in, "prop", 0, 0);
float def = 0;
if (ctx->propGetType(in, "default") == ptFloat)
def = static_cast<float>(ctx->propGetFloat(in, "prop", 0, 0));
return vsxa::clip<async>("PropToClip", vi, async([=](int n) -> async::renderer* {
vsxa::callctx ctx = co_await get_callctx();
vsxa::frame frame = co_await get_frame(n, clip);
const VSMap* props = ctx->getFramePropsRO(frame);
VSFrameRef* newFrame = ctx->newVideoFrame(vi.format, 1, 1, frame, ctx);
float* data = reinterpret_cast<float*>(ctx->getWritePtr(newFrame, 0));
switch (ctx->propGetType(props, propName.c_str())) {
case ptInt:
data[0] = (float)ctx->propGetInt(props, propName.c_str(), 0, 0);
break;
case ptFloat:
data[0] = (float)ctx->propGetFloat(props, propName.c_str(), 0, 0);
break;
default:
data[0] = def;
break;
}
co_return newFrame;
}));
}
bounded<clip<async>> ImportProp(const VSMap* in, VSMap* out, std::monostate unused, vsxa::callctx ctx) {
VSNodeRef* dst = ctx->propGetNode(in, "clip", 0, 0);
VSNodeRef* src = ctx->propGetNode(in, "src", 0, 0);
std::string dstPropName = ctx->propGetData(in, "dstprop", 0, 0);
std::string srcPropName = ctx->propGetData(in, "srcprop", 0, 0);
return vsxa::clip<async>("ImportProp", ctx->getVideoInfo(dst), async([=](int n) -> async::renderer* {
vsxa::callctx ctx = co_await get_callctx();
bounded<vsxa::frame> frames = co_await (get_frame(n, src) + get_frame(n, dst));
const VSMap* srcProps = ctx->getFramePropsRO(frames[0]);
VSPropTypes type = static_cast<VSPropTypes>(ctx->propGetType(srcProps, srcPropName.c_str()));
if (type == ptUnset)
return frames[1];
VSFrameRef* newFrame = ctx->copyFrame(frames[1], ctx);
VSMap* dstProps = ctx->getFramePropsRW(newFrame);
if (ctx->propGetType(dstProps, dstPropName.c_str()) != ptUnset)
ctx->propDeleteKey(dstProps, dstPropName.c_str());
auto size = ctx->propNumElements(srcProps, srcPropName.c_str());
switch (type) {
case ptFloat:
ctx->propSetFloatArray(dstProps, dstPropName.c_str(), ctx->propGetFloatArray(srcProps, srcPropName.c_str(), 0), size);
break;
case ptInt:
ctx->propSetIntArray(dstProps, dstPropName.c_str(), ctx->propGetIntArray(srcProps, srcPropName.c_str(), 0), size);
break;
case ptData:
for (int i = 0; i < size; i++)
ctx->propSetData(dstProps, dstPropName.c_str(), ctx->propGetData(srcProps, srcPropName.c_str(), i, 0), ctx->propGetDataSize(srcProps, srcPropName.c_str(), i, 0), paAppend);
break;
case ptFunction:
for (int i = 0; i < size; i++)
ctx->propSetFunc(dstProps, dstPropName.c_str(), ctx->propGetFunc(srcProps, srcPropName.c_str(), i, 0), paAppend);
break;
case ptFrame:
for (int i = 0; i < size; i++)
ctx->propSetFrame(dstProps, dstPropName.c_str(), ctx->propGetFrame(srcProps, srcPropName.c_str(), i, 0), paAppend);
break;
case ptNode:
for (int i = 0; i < size; i++)
ctx->propSetNode(dstProps, dstPropName.c_str(), ctx->propGetNode(srcProps, srcPropName.c_str(), i, 0), paAppend);
break;
default:
throw generic_error("Unknown prop type detected. Cannot copy prop.");
}
return newFrame;
}));
}
VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin* plugin) {
configFunc("moe.encode.proputils", "proputils", "Prop Utils", VAPOURSYNTH_API_VERSION, 1, plugin);
vsxa::interop::vsxaRegister<clip<async>>(registerFunc, plugin, "PropToClip", "clip:clip;prop:data;default:float:opt;", PropToClip);
vsxa::interop::vsxaRegister<clip<async>>(registerFunc, plugin, "ImportProp", "clip:clip;src:clip;dstprop:data;srcprop:data;", ImportProp);
}
#ifndef _VSXA_CORO_HPP_
#define _VSXA_CORO_HPP_
#include "vsxa.hpp"
#include "vsxautils.hpp"
#include <functional>
#include <variant>
#include <experimental/coroutine>
#define coroutines std::experimental
namespace vsxa
{
class _request_failed : public std::exception {};
struct get_frames {
struct get_frame {
int n;
VSNodeRef* node;
public:
get_frame() : n(0), node(nullptr) {}
get_frame(int n, VSNodeRef* node) : n(n), node(node) {}
get_frames operator+(get_frame other) {
return get_frames(get_frame(n, node), other);
}
};
bounded<get_frame> f;
get_frames(get_frame a, get_frame b) : f(bounded<get_frame>(2)) {
f[0] = std::move(a);
f[1] = std::move(b);
}
get_frames(get_frames a, get_frame b) : f(bounded<get_frame>(a.f.length() + 1)) {
std::copy(a.f.begin(), a.f.end(), f.begin());
f[a.f.length()] = std::move(b);
}
get_frames(bounded<get_frame>&& frames) : f(std::move(frames)) {};
get_frames(get_frames& mv) : f(std::move(mv.f)) {};
get_frames(const get_frames&& cp) : f(cp.f) {};
get_frames operator+(get_frame& other) {
return get_frames(get_frames(std::move(f)), other);
}
};
using get_frame = get_frames::get_frame;
class get_callctx {};
class get_frame_context {};
struct async
{
using _promise_result = std::variant<std::monostate, const VSFrameRef*, std::exception_ptr>;
class renderer {
coroutines::coroutine_handle<> handle;
_promise_result* result;
public:
callctx ctx;
VSFrameContext* currentContext = nullptr;
VSActivationReason currentAr = arError;
public:
renderer(coroutines::coroutine_handle<> handle, _promise_result* result) : handle(handle), result(result) {}
const VSFrameRef* resume(VSActivationReason ar, VSFrameContext* ctx) {
currentContext = ctx;
currentAr = ar;
if (ar == arAllFramesReady || ar == arError || ar == arInitial)
handle.resume();
else
return nullptr;
switch (result->index()) {
case 1:
return std::get<const VSFrameRef*>(*result);
case 0:
break;
case 2:
try {
std::rethrow_exception(std::get<std::exception_ptr>(*result));
}
catch (_request_failed &) {
}
catch (std::exception & e) {
this->ctx->setFilterError(e.what(), ctx);
}
catch (...) {
this->ctx->setFilterError("A unknown exception occured.", ctx);
}
break;
default:
this->ctx->setFilterError("This should not happen.", ctx);
}
return nullptr;
}
~renderer() {
handle.destroy();
}
};
struct promise {
template<typename T>
class _value_awaiter {
T value;
public:
_value_awaiter(T v) : value(v) {}
bool await_ready() { return true; }
void await_suspend(coroutines::coroutine_handle<vsxa::async::promise> coro) {}
T await_resume() {
return value;
}
};
_promise_result result = std::monostate{};
renderer* r = new renderer{ coroutines::coroutine_handle<promise>::from_promise(this), &result };
renderer* get_renderer() {
return r;
};
renderer* get_return_object() {
return r;
}
coroutines::suspend_always initial_suspend() { return {}; };
coroutines::suspend_always final_suspend() { return {}; };
void return_value(const VSFrameRef* ref) {
result.emplace<const VSFrameRef*>(ref);
}
void unhandled_exception() {
result.emplace<std::exception_ptr>(std::move(std::current_exception()));
}
template<typename U>
coroutines::suspend_always yield_value(U& v) = delete;
auto await_transform(get_callctx ctx) {
return _value_awaiter<callctx>(r->ctx);
}
auto await_transform(get_frame_context ctx) {
return _value_awaiter<VSFrameContext*>(r->currentContext);
}
auto await_transform(get_frame frame) {
class _awaiter
{
promise* p;
get_frame f;
public:
_awaiter(promise* p, get_frame f) : p(p), f(f) {}
bool await_ready() { return false; }
void await_suspend(coroutines::coroutine_handle<vsxa::async::promise> coro) {
p->r->ctx->requestFrameFilter(f.n, f.node, p->r->currentContext);
}
vsxa::frame await_resume() {
if (p->r->currentAr == arAllFramesReady) {
return vsxa::frame(p->r->ctx->getFrameFilter(f.n, f.node, p->r->currentContext), p->r->ctx);
} else {
throw _request_failed{};
}
}
};
return _awaiter(this, frame);
}
auto await_transform(get_frames frames) {
class _awaiter
{
promise* p;
get_frames f;
public:
_awaiter(promise* p, get_frames f) : p(p), f(f) {}
bool await_ready() { return false; }
void await_suspend(coroutines::coroutine_handle<vsxa::async::promise> coro) {
for (get_frame* current = f.f.begin(); current != f.f.end(); current++)
p->r->ctx->requestFrameFilter(current->n, current->node, p->r->currentContext);
}
bounded<frame> await_resume() {
if (p->r->currentAr == arAllFramesReady) {
bounded<frame> result = bounded<frame>(f.f.length());
for (size_t i = 0; i < result.length(); i++) {
get_frame* current = f.f.get(i);
result[i] = frame(p->r->ctx->getFrameFilter(current->n, current->node, p->r->currentContext), p->r->ctx);
}
return result;
}
else {
throw _request_failed{};
}
}
};
return _awaiter(this, frames);
}
};
using renderfunc = std::function<renderer*(int n)>;
private:
renderfunc func;
public:
async(renderfunc func) : func(func) {}
const char* name() { return "_async_"; };
VSFilterMode mode() { return fmParallel; };
VSNodeFlags flags() { return static_cast<VSNodeFlags>(0); };
void info(VSVideoInfo& vi) {};
using request_data = renderer;
renderer* init(int n, VSFrameContext* fctx, callctx cctx) {
renderer* render = func(n);
render->ctx = cctx;
return render;
};
const VSFrameRef* advance(renderer* state, VSActivationReason r, VSFrameContext* fctx) {
return state->resume(r, fctx);
};
void finish(renderer* state, VSFrameContext* fctx) {
delete state;
};
};
}
template<>
struct coroutines::coroutine_traits<vsxa::async::renderer*, int> {
using promise_type = vsxa::async::promise;
};
template<typename T>
struct coroutines::coroutine_traits<vsxa::async::renderer*, T, int> {
using promise_type = vsxa::async::promise;
};
#endif
#ifndef _VSXA_FUNCTIONAL_HPP_
#define _VSXA_FUNCTIONAL_HPP_
#include "vsxa.hpp"
#include "VapourSynth.h"
#include <functional>
namespace vsxa
{
class frame
{
const VSFrameRef* ref;
callctx cctx;
public:
frame() : ref(nullptr) {};
frame(const VSFrameRef* ref, callctx cctx) : ref(ref), cctx(cctx) {}
frame(frame&& orig) noexcept : ref(orig.detach()), cctx(orig.cctx) {}
frame(const frame&& frame) noexcept : cctx(frame.cctx) {
if (frame.ref == nullptr)
ref = nullptr;
else
ref = cctx->cloneFrameRef(frame.ref);
}
frame& operator=(frame&& src) noexcept {
_destroy();
ref = src.ref;
cctx = src.cctx;
}
operator const VSFrameRef* () { return ref; }
const VSFrameRef* detach() noexcept { const VSFrameRef* r = ref; ref = nullptr; return r; }
~frame() { _destroy(); }
private:
void _destroy() { if (ref != nullptr) cctx->freeFrame(ref); }
};
template<typename T>
class clip
{
const char* _name;
VSFilterMode _mode;
VSNodeFlags _flags;
VSVideoInfo _vi;
T filter;
public:
clip(const char* name, VSFilterMode mode, VSNodeFlags flags, VSVideoInfo vi, T filter) : _name(name), _mode(mode), _flags(flags), _vi(vi), filter(std::move(filter)) {};
clip(const char* name, VSFilterMode mode, VSVideoInfo vi, T filter) : _name(name), _mode(mode), _flags((VSNodeFlags)0), _vi(vi), filter(std::move(filter)) {};
clip(const char* name, VSVideoInfo vi, T filter) : _name(name), _mode(fmParallel), _flags((VSNodeFlags)0), _vi(vi), filter(std::move(filter)) {};
clip(const char* name, VSFilterMode mode, VSNodeFlags flags, const VSVideoInfo* vi, T filter) : _name(name), _mode(mode), _flags(flags), _vi(*vi), filter(std::move(filter)) {};
clip(const char* name, VSFilterMode mode, const VSVideoInfo* vi, T filter) : _name(name), _mode(mode), _flags((VSNodeFlags)0), _vi(*vi), filter(std::move(filter)) {};
clip(const char* name, const VSVideoInfo* vi, T filter) : _name(name), _mode(fmParallel), _flags((VSNodeFlags)0), _vi(*vi), filter(std::move(filter)) {};
const char* name() { return _name; };
VSFilterMode mode() { return _mode; };
VSNodeFlags flags() { return _flags; };
void info(VSVideoInfo& vi) {
vi = _vi;
};
using request_data = typename T::request_data;
request_data* init(int n, VSFrameContext* fctx, callctx cctx) {
return filter.init(n, fctx, cctx);
};
const VSFrameRef* advance(request_data* state, VSActivationReason r, VSFrameContext* fctx) {
return filter.advance(state, r, fctx);
};
void finish(request_data* state, VSFrameContext* fctx) {
filter.finish(state, fctx);
};
};
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment