From 3093d93e1f0baadf4c1ec866adacb244d10fabbe Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 12 Oct 2014 01:31:20 +0200 Subject: vf_vapoursynth: add standalone Lua scripting --- DOCS/man/vf.rst | 25 ++++ old-configure | 23 +++- old-makefile | 2 +- video/filter/vf.c | 6 +- video/filter/vf_vapoursynth.c | 257 ++++++++++++++++++++++++++++++++++++++++++ wscript | 30 ++--- wscript_build.py | 2 +- 7 files changed, 326 insertions(+), 19 deletions(-) diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index b8a258bbd9..6e7418c0c3 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -910,6 +910,31 @@ Available filters are: making it higher than the number of cores can actually make it slower. +``vapoursynth-lazy`` + The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a + custom backend using Lua and the raw VapourSynth API is used. The syntax + is completely different, and absolutely no conveniencve features are + provided. There's no type checking either, and you can trigger crashes. + + .. admonition:: Example: + + :: + + video_out = invoke("morpho", "Open", {clip = video_in}) + + The special variable ``video_in`` is the mpv video source, while the + special variable ``video_out`` is used to read video from. The 1st argument + is the plugin (queried with ``getPluginByNs``), the 2nd is the filter name, + and the 3rd argument is a table with the arguments. Positional arguments + are not supported. The types must match exactly. Since Lua is terrible and + can't distinguish integers and floats, integer arguments must be prefixed + with ``i_``, in which case the prefix is removed and the argument is cast + to an integer. Should the argument's name start with ``i_``, you're out of + luck. + + Clips (VSNodeRef) are passed as light userdata, so trying to pass any + other userdata type will result in hard crashes. + ``vavpp`` VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl`` only. Currently deinterlaces. This filter is automatically inserted if diff --git a/old-configure b/old-configure index bd31bfc1c1..cf2c4c9cde 100755 --- a/old-configure +++ b/old-configure @@ -186,7 +186,6 @@ options_state_machine() { opt_yes_no _libpostproc "postprocess filter (vf_pp)" opt_yes_no _libavdevice "libavdevice demuxers" opt_yes_no _libavfilter "libavfilter" - opt_yes_no _vapoursynth "VapourSynth filter bridge" opt_yes_no _jpeg "support for writing JPEG screenshots" opt_yes_no _libcdio "libcdio support" opt_yes_no _ffmpeg "skip FFmpeg/Libav autodetection" @@ -215,6 +214,8 @@ options_state_machine() { opt_yes_no _openal "OpenAL audio output" opt_yes_no _shm "X11/Xv shared memory" opt_yes_no _lua "Lua scripting" + opt_yes_no _vapoursynth "VapourSynth filter bridge (Python)" + opt_yes_no _vapoursynth_lazy "VapourSynth filter bridge (Lua)" opt_yes_no _encoding "encoding functionality" yes opt_yes_no _build_man "building manpage" } @@ -759,8 +760,6 @@ check_pkg_config "libbs2b audio filter support" $_libbs2b LIBBS2B 'libbs2b' check_pkg_config "LCMS2 support" $_lcms2 LCMS2 'lcms2 >= 2.6' -check_pkg_config "VapourSynth support" $_vapoursynth VAPOURSYNTH 'vapoursynth >= 23 vapoursynth-script >= 23' - check_pkg_config "FFmpeg/Libav" $_ffmpeg FFMPEG \ "libavutil >= 52.48.101 libavcodec >= 55.34.1 libavformat >= 55.12.0 libswscale >= 2.1.2" test $(defretval) = no && die "Unable to find development files for some of the required Libav libraries above. Aborting." @@ -883,6 +882,24 @@ test_lua "lua5.2 >= 5.2.0" # debian test "$_lua" != yes && check_yes_no no LUA +if ! ( $_pkg_config 'vapoursynth >= 23' ) ; then + _vapoursynth=no + _vapoursynth_lazy=no +fi +check_pkg_config "VapourSynth support (Python)" $_vapoursynth VAPOURSYNTH 'vapoursynth >= 23 vapoursynth-script >= 23' +_vapoursynth=$(defretval) +if test "$_lua" = no ; then + _vapoursynth_lazy=no +fi +check_pkg_config "VapourSynth support (Lua)" $_vapoursynth_lazy VAPOURSYNTH_LAZY 'vapoursynth >= 23' +_vapoursynth_lazy=$(defretval) + +_vapoursynth_core=yes +if test "$_vapoursynth" = no && test "$_vapoursynth_lazy" = no ; then + _vapoursynth_core=no +fi +check_trivial "VapourSynth core" $_vapoursynth_core VAPOURSYNTH_CORE + check_trivial "joystick" $_joystick JOYSTICK check_statement_libs "lirc" $_lirc LIRC lirc/lirc_client.h "" -llirc_client diff --git a/old-makefile b/old-makefile index d0b7d678e6..519da884f1 100644 --- a/old-makefile +++ b/old-makefile @@ -105,7 +105,7 @@ SOURCES-$(LIBAVFILTER) += video/filter/vf_lavfi.c \ audio/filter/af_lavfi.c SOURCES-$(LUA) += player/lua.c -SOURCES-$(VAPOURSYNTH) += video/filter/vf_vapoursynth.c +SOURCES-$(VAPOURSYNTH_CORE) += video/filter/vf_vapoursynth.c SOURCES-$(DLOPEN) += video/filter/vf_dlopen.c SOURCES = audio/audio.c \ diff --git a/video/filter/vf.c b/video/filter/vf.c index f00e9689c6..4910974d6a 100644 --- a/video/filter/vf.c +++ b/video/filter/vf.c @@ -71,6 +71,7 @@ extern const vf_info_t vf_info_dlopen; extern const vf_info_t vf_info_lavfi; extern const vf_info_t vf_info_vaapi; extern const vf_info_t vf_info_vapoursynth; +extern const vf_info_t vf_info_vapoursynth_lazy; extern const vf_info_t vf_info_vdpaupp; extern const vf_info_t vf_info_buffer; @@ -114,9 +115,12 @@ static const vf_info_t *const filter_list[] = { #if HAVE_DLOPEN &vf_info_dlopen, #endif -#if HAVE_VAPOURSYNTH +#if HAVE_VAPOURSYNTH_CORE && HAVE_VAPOURSYNTH &vf_info_vapoursynth, #endif +#if HAVE_VAPOURSYNTH_CORE && HAVE_VAPOURSYNTH_LAZY + &vf_info_vapoursynth_lazy, +#endif #if HAVE_VAAPI_VPP &vf_info_vaapi, #endif diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c index f5122df544..1ebf971125 100644 --- a/video/filter/vf_vapoursynth.c +++ b/video/filter/vf_vapoursynth.c @@ -27,6 +27,8 @@ #include +#include "config.h" + #include "common/msg.h" #include "options/m_option.h" @@ -42,7 +44,14 @@ struct vf_priv_s { VSNodeRef *in_node; const struct script_driver *drv; + // drv_vss struct VSScript *se; + // drv_lazy + struct lua_State *ls; + VSNodeRef **gc_noderef; + int num_gc_noderef; + VSMap **gc_map; + int num_gc_map; struct mp_image_params fmt_in; @@ -687,6 +696,8 @@ static const m_option_t vf_opts_fields[] = { {0} }; +#if HAVE_VAPOURSYNTH + #include static int drv_vss_init(struct vf_instance *vf) @@ -763,3 +774,249 @@ const vf_info_t vf_info_vapoursynth = { .priv_size = sizeof(struct vf_priv_s), .options = vf_opts_fields, }; + +#endif + +#if HAVE_VAPOURSYNTH_LAZY + +#include +#include +#include + +#if LUA_VERSION_NUM <= 501 +#define mp_cpcall lua_cpcall +#define FUCKYOUOHGODWHY(L) lua_pushvalue(L, LUA_GLOBALSINDEX) +#else +// Curse whoever had this stupid idea. Curse whoever thought it would be a good +// idea not to include an emulated lua_cpcall() even more. +static int mp_cpcall (lua_State *L, lua_CFunction func, void *ud) +{ + lua_pushcfunction(L, func); // doesn't allocate in 5.2 (but does in 5.1) + lua_pushlightuserdata(L, ud); + return lua_pcall(L, 1, 0, 0); +} +// Hey, let's replace old mechanisms with something slightly different! +#define FUCKYOUOHGODWHY lua_pushglobaltable +#endif + +static int drv_lazy_init(struct vf_instance *vf) +{ + struct vf_priv_s *p = vf->priv; + p->ls = luaL_newstate(); + if (!p->ls) + return -1; + p->vsapi = getVapourSynthAPI(VAPOURSYNTH_API_VERSION); + p->vscore = p->vsapi ? p->vsapi->createCore(0) : NULL; + if (!p->vscore) { + MP_FATAL(vf, "Could not load VapourSynth.\n"); + lua_close(p->ls); + return -1; + } + return 0; +} + +static void drv_lazy_uninit(struct vf_instance *vf) +{ + struct vf_priv_s *p = vf->priv; + lua_close(p->ls); + p->vsapi->freeCore(p->vscore); +} + +static int drv_lazy_load_core(struct vf_instance *vf) +{ + // not needed + return 0; +} + +static struct vf_instance *get_vf(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "p"); // p + struct vf_instance *vf = lua_touserdata(L, -1); // p + lua_pop(L, 1); // - + return vf; +} + +static void vsmap_to_table(lua_State *L, int index, VSMap *map) +{ + struct vf_instance *vf = get_vf(L); + struct vf_priv_s *p = vf->priv; + const VSAPI *vsapi = p->vsapi; + for (int n = 0; n < vsapi->propNumKeys(map); n++) { + const char *key = vsapi->propGetKey(map, n); + VSPropTypes t = vsapi->propGetType(map, key); + switch (t) { + case ptInt: + lua_pushnumber(L, vsapi->propGetInt(map, key, 0, NULL)); + break; + case ptNode: { + VSNodeRef *r = vsapi->propGetNode(map, key, 0, NULL); + MP_TARRAY_APPEND(p, p->gc_noderef, p->num_gc_noderef, r); + lua_pushlightuserdata(L, r); + break; + } + default: + luaL_error(L, "unknown map type"); + } + lua_setfield(L, index, key); + } +} + +static VSMap *table_to_vsmap(lua_State *L, int index) +{ + struct vf_instance *vf = get_vf(L); + struct vf_priv_s *p = vf->priv; + const VSAPI *vsapi = p->vsapi; + assert(index > 0); + VSMap *map = vsapi->createMap(); + MP_TARRAY_APPEND(p, p->gc_map, p->num_gc_map, map); + if (!map) + luaL_error(L, "out of memory"); + lua_pushnil(L); // nil + while (lua_next(L, index) != 0) { // key value + if (lua_type(L, -2) != LUA_TSTRING) { + luaL_error(L, "key must be a string, but got %s", + lua_typename(L, -2)); + } + const char *key = lua_tostring(L, -2); + switch (lua_type(L, -1)) { + case LUA_TNUMBER: { + // gross hack because we hate everything + if (strncmp(key, "i_", 2) == 0) { + vsapi->propSetInt(map, key + 2, lua_tointeger(L, -1), 0); + } else { + vsapi->propSetFloat(map, key, lua_tonumber(L, -1), 0); + } + break; + } + case LUA_TSTRING: { + const char *s = lua_tostring(L, -1); + vsapi->propSetData(map, key, s, strlen(s), 0); + break; + } + case LUA_TLIGHTUSERDATA: { // assume it's VSNodeRef* + VSNodeRef *node = lua_touserdata(L, -1); + vsapi->propSetNode(map, key, node, 0); + break; + } + default: + luaL_error(L, "unknown type"); + break; + } + lua_pop(L, 1); // key + } + return map; +} + +static int l_invoke(lua_State *L) +{ + struct vf_instance *vf = get_vf(L); + struct vf_priv_s *p = vf->priv; + const VSAPI *vsapi = p->vsapi; + + VSPlugin *plugin = vsapi->getPluginByNs(luaL_checkstring(L, 1), p->vscore); + if (!plugin) + luaL_error(L, "plugin not found"); + VSMap *map = table_to_vsmap(L, 3); + VSMap *r = vsapi->invoke(plugin, luaL_checkstring(L, 2), map); + MP_TARRAY_APPEND(p, p->gc_map, p->num_gc_map, r); + if (!r) + luaL_error(L, "?"); + const char *err = vsapi->getError(r); + if (err) + luaL_error(L, "error calling invoke(): %s", err); + int err2 = 0; + VSNodeRef *node = vsapi->propGetNode(r, "clip", 0, &err2); + MP_TARRAY_APPEND(p, p->gc_noderef, p->num_gc_noderef, node); + if (!node) + luaL_error(L, "invoke() didn't return clip (error %d)", err2); + lua_pushlightuserdata(L, node); + return 1; +} + +struct load_ctx { + struct vf_instance *vf; + VSMap *vars; + int status; +}; + +static int load_stuff(lua_State *L) +{ + struct load_ctx *ctx = lua_touserdata(L, -1); + lua_pop(L, 1); // - + struct vf_instance *vf = ctx->vf; + struct vf_priv_s *p = vf->priv; + + // setup stuff; should be idempotent + lua_pushlightuserdata(L, vf); + lua_setfield(L, LUA_REGISTRYINDEX, "p"); // - + lua_pushcfunction(L, l_invoke); + lua_setglobal(L, "invoke"); + + FUCKYOUOHGODWHY(L); + vsmap_to_table(L, lua_gettop(L), ctx->vars); + if (luaL_dofile(L, p->cfg_file)) + lua_error(L); + lua_pop(L, 1); + + lua_getglobal(L, "video_out"); // video_out + if (!lua_islightuserdata(L, -1)) + luaL_error(L, "video_out not set or has wrong type"); + p->out_node = p->vsapi->cloneNodeRef(lua_touserdata(L, -1)); + return 0; +} + +static int drv_lazy_load(struct vf_instance *vf, VSMap *vars) +{ + struct vf_priv_s *p = vf->priv; + struct load_ctx ctx = {vf, vars, 0}; + if (mp_cpcall(p->ls, load_stuff, &ctx)) { + MP_FATAL(vf, "filter creation failed: %s\n", lua_tostring(p->ls, -1)); + lua_pop(p->ls, 1); + ctx.status = -1; + } + assert(lua_gettop(p->ls) == 0); + return ctx.status; +} + +static void drv_lazy_unload(struct vf_instance *vf) +{ + struct vf_priv_s *p = vf->priv; + + for (int n = 0; n < p->num_gc_noderef; n++) { + VSNodeRef *ref = p->gc_noderef[n]; + if (ref) + p->vsapi->freeNode(ref); + } + p->num_gc_noderef = 0; + for (int n = 0; n < p->num_gc_map; n++) { + VSMap *map = p->gc_map[n]; + if (map) + p->vsapi->freeMap(map); + } + p->num_gc_map = 0; +} + +static const struct script_driver drv_lazy = { + .init = drv_lazy_init, + .uninit = drv_lazy_uninit, + .load_core = drv_lazy_load_core, + .load = drv_lazy_load, + .unload = drv_lazy_unload, +}; + +static int vf_open_lazy(vf_instance_t *vf) +{ + struct vf_priv_s *p = vf->priv; + p->drv = &drv_lazy; + return vf_open(vf); +} + +const vf_info_t vf_info_vapoursynth_lazy = { + .description = "VapourSynth bridge (Lua)", + .name = "vapoursynth-lazy", + .open = vf_open_lazy, + .priv_size = sizeof(struct vf_priv_s), + .options = vf_opts_fields, +}; + +#endif diff --git a/wscript b/wscript index da056d9b51..881333f675 100644 --- a/wscript +++ b/wscript @@ -243,6 +243,10 @@ iconv support use --disable-iconv.", 'desc': 'libquvi support', 'deps_any': [ 'libquvi4', 'libquvi9' ], 'func': check_true + }, { + 'name' : '--lua', + 'desc' : 'Lua', + 'func': check_lua, }, { 'name': '--libass', 'desc': 'SSA/ASS support', @@ -318,10 +322,20 @@ If you really mean to compile without libass support use --disable-libass." 'name': '--lcms2', 'desc': 'LCMS2 support', 'func': check_pkg_config('lcms2', '>= 2.6'), + }, { + 'name': 'vapoursynth-core', + 'desc': 'VapourSynth filter bridge (core)', + 'func': check_pkg_config('vapoursynth >= 23'), }, { 'name': '--vapoursynth', - 'desc': 'VapourSynth filter bridge', - 'func': check_pkg_config('vapoursynth >= 23 vapoursynth-script >= 23'), + 'desc': 'VapourSynth filter bridge (Python)', + 'deps': ['vapoursynth-core'], + 'func': check_pkg_config('vapoursynth-script >= 23'), + }, { + 'name': '--vapoursynth-lazy', + 'desc': 'VapourSynth filter bridge (Lazy Lua)', + 'deps': ['vapoursynth-core', 'lua'], + 'func': check_true, } ] @@ -725,14 +739,6 @@ radio_and_tv_features = [ } ] -scripting_features = [ - { - 'name' : '--lua', - 'desc' : 'Lua', - 'func': check_lua, - } -] - standalone_features = [ { 'name': '--cplayer', @@ -796,10 +802,9 @@ def options(opt): opt.parse_features('video outputs', video_output_features) opt.parse_features('hwaccels', hwaccel_features) opt.parse_features('tv features', radio_and_tv_features) - opt.parse_features('scripting', scripting_features) opt.parse_features('standalone app', standalone_features) - group = opt.get_option_group("scripting") + group = opt.get_option_group("optional feaures") group.add_option('--lua', type = 'string', dest = 'LUA_VER', @@ -856,7 +861,6 @@ def configure(ctx): if ctx.options.LUA_VER: ctx.options.enable_lua = True - ctx.parse_dependencies(scripting_features) ctx.parse_dependencies(standalone_features) ctx.define('HAVE_SYS_SOUNDCARD_H', diff --git a/wscript_build.py b/wscript_build.py index 2f2b4afd02..f7df1c5aad 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -329,7 +329,7 @@ def build(ctx): ( "video/filter/vf_sub.c" ), ( "video/filter/vf_swapuv.c" ), ( "video/filter/vf_unsharp.c" ), - ( "video/filter/vf_vapoursynth.c", "vapoursynth" ), + ( "video/filter/vf_vapoursynth.c", "vapoursynth-core" ), ( "video/filter/vf_vavpp.c", "vaapi-vpp"), ( "video/filter/vf_vdpaupp.c", "vdpau" ), ( "video/filter/vf_yadif.c" ), -- cgit v1.2.3