diff options
Diffstat (limited to 'video/filter/vf_vapoursynth.c')
-rw-r--r-- | video/filter/vf_vapoursynth.c | 682 |
1 files changed, 346 insertions, 336 deletions
diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c index b5aad7abbf..4de09794e6 100644 --- a/video/filter/vf_vapoursynth.c +++ b/video/filter/vf_vapoursynth.c @@ -34,13 +34,27 @@ #include "common/msg.h" #include "options/m_option.h" #include "options/path.h" - +#include "filters/f_autoconvert.h" +#include "filters/f_utils.h" +#include "filters/filter.h" +#include "filters/filter_internal.h" +#include "filters/user_filters.h" #include "video/img_format.h" #include "video/mp_image.h" #include "video/sws_utils.h" -#include "vf.h" -struct vf_priv_s { +struct vapoursynth_opts { + char *file; + int maxbuffer; + int maxrequests; + + const struct script_driver *drv; +}; + +struct priv { + struct mp_log *log; + struct vapoursynth_opts *opts; + VSCore *vscore; const VSAPI *vsapi; VSNodeRef *out_node; @@ -56,16 +70,20 @@ struct vf_priv_s { VSMap **gc_map; int num_gc_map; + struct mp_filter *f; + struct mp_pin *in_pin; + + // Format for which VS is currently configured. struct mp_image_params fmt_in; pthread_mutex_t lock; pthread_cond_t wakeup; // --- the following members are all protected by lock - struct mp_image *next_image;// used to compute frame duration of oldest image struct mp_image **buffered; // oldest image first int num_buffered; int in_frameno; // frame number of buffered[0] (the oldest) + int requested_frameno; // last frame number for which we woke up core int out_frameno; // frame number of first requested/ready frame double out_pts; // pts corresponding to first requested/ready frame struct mp_image **requested;// frame callback results (can point to dummy_img) @@ -74,25 +92,25 @@ struct vf_priv_s { bool failed; // frame callback returned with an error bool shutdown; // ask node to return bool eof; // drain remaining data - int64_t frames_sent; + int64_t frames_sent; // total nr. of frames ever added to input queue bool initializing; // filters are being built bool in_node_active; // node might still be called - - // --- options - char *cfg_file; - int cfg_maxbuffer; - int cfg_maxrequests; }; // priv->requested[n] points to this if a request for frame n is in-progress static const struct mp_image dummy_img; +// or if a request failed during EOF/reinit draining +static const struct mp_image dummy_img_eof; + +static void destroy_vs(struct priv *p); +static int reinit_vs(struct priv *p); struct script_driver { - int (*init)(struct vf_instance *vf); // first time init - void (*uninit)(struct vf_instance *vf); // last time uninit - int (*load_core)(struct vf_instance *vf); // make vsapi/vscore available - int (*load)(struct vf_instance *vf, VSMap *vars); // also set p->out_node - void (*unload)(struct vf_instance *vf); // unload script and maybe vs + int (*init)(struct priv *p); // first time init + void (*uninit)(struct priv *p); // last time uninit + int (*load_core)(struct priv *p); // make vsapi/vscore available + int (*load)(struct priv *p, VSMap *vars); // also sets p->out_node + void (*unload)(struct priv *p); // unload script and maybe vs }; struct mpvs_fmt { @@ -166,7 +184,7 @@ static int mp_from_vs(VSPresetFormat vs) return 0; } -static void copy_mp_to_vs_frame_props_map(struct vf_priv_s *p, VSMap *map, +static void copy_mp_to_vs_frame_props_map(struct priv *p, VSMap *map, struct mp_image *img) { struct mp_image_params *params = &img->params; @@ -197,7 +215,7 @@ static void copy_mp_to_vs_frame_props_map(struct vf_priv_s *p, VSMap *map, p->vsapi->propSetInt(map, "_FieldBased", field, 0); } -static int set_vs_frame_props(struct vf_priv_s *p, VSFrameRef *frame, +static int set_vs_frame_props(struct priv *p, VSFrameRef *frame, struct mp_image *img, int dur_num, int dur_den) { VSMap *map = p->vsapi->getFramePropsRW(frame); @@ -209,14 +227,14 @@ static int set_vs_frame_props(struct vf_priv_s *p, VSFrameRef *frame, return 0; } -static VSFrameRef *alloc_vs_frame(struct vf_priv_s *p, struct mp_image_params *fmt) +static VSFrameRef *alloc_vs_frame(struct priv *p, struct mp_image_params *fmt) { const VSFormat *vsfmt = p->vsapi->getFormatPreset(mp_to_vs(fmt->imgfmt), p->vscore); return p->vsapi->newVideoFrame(vsfmt, fmt->w, fmt->h, NULL, p->vscore); } -static struct mp_image map_vs_frame(struct vf_priv_s *p, const VSFrameRef *ref, +static struct mp_image map_vs_frame(struct priv *p, const VSFrameRef *ref, bool w) { const VSFormat *fmt = p->vsapi->getFrameFormat(ref); @@ -238,7 +256,7 @@ static struct mp_image map_vs_frame(struct vf_priv_s *p, const VSFrameRef *ref, return img; } -static void drain_oldest_buffered_frame(struct vf_priv_s *p) +static void drain_oldest_buffered_frame(struct priv *p) { if (!p->num_buffered) return; @@ -252,185 +270,170 @@ static void drain_oldest_buffered_frame(struct vf_priv_s *p) static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n, VSNodeRef *node, const char *errorMsg) { - struct vf_instance *vf = userData; - struct vf_priv_s *p = vf->priv; + struct priv *p = userData; pthread_mutex_lock(&p->lock); // If these assertions fail, n is an unrequested frame (or filtered twice). assert(n >= p->out_frameno && n < p->out_frameno + p->max_requests); int index = n - p->out_frameno; - MP_TRACE(vf, "filtered frame %d (%d)\n", n, index); + MP_TRACE(p, "filtered frame %d (%d)\n", n, index); assert(p->requested[index] == &dummy_img); struct mp_image *res = NULL; if (f) { struct mp_image img = map_vs_frame(p, f, false); - img.pts = MP_NOPTS_VALUE; + img.pkt_duration = -1; const VSMap *map = p->vsapi->getFramePropsRO(f); if (map) { int err1, err2; int num = p->vsapi->propGetInt(map, "_DurationNum", 0, &err1); int den = p->vsapi->propGetInt(map, "_DurationDen", 0, &err2); if (!err1 && !err2) - img.pts = num / (double)den; // abusing pts for frame length + img.pkt_duration = num / (double)den; } - if (img.pts == MP_NOPTS_VALUE) - MP_ERR(vf, "No PTS after filter at frame %d!\n", n); + if (img.pkt_duration < 0) + MP_ERR(p, "No PTS after filter at frame %d!\n", n); res = mp_image_new_copy(&img); p->vsapi->freeFrame(f); } - if (!res) { - p->failed = true; - MP_ERR(vf, "Filter error at frame %d: %s\n", n, errorMsg); + if (!res && !p->shutdown) { + if (p->eof) { + res = (struct mp_image *)&dummy_img_eof; + } else { + p->failed = true; + MP_ERR(p, "Filter error at frame %d: %s\n", n, errorMsg); + } } p->requested[index] = res; pthread_cond_broadcast(&p->wakeup); pthread_mutex_unlock(&p->lock); + mp_filter_wakeup(p->f); } -static bool locked_need_input(struct vf_instance *vf) +static void vf_vapoursynth_process(struct mp_filter *f) { - struct vf_priv_s *p = vf->priv; - return p->num_buffered < MP_TALLOC_AVAIL(p->buffered); -} + struct priv *p = f->priv; -// Return true if progress was made. -static bool locked_read_output(struct vf_instance *vf) -{ - struct vf_priv_s *p = vf->priv; - bool r = false; + pthread_mutex_lock(&p->lock); - // Move finished frames from the request slots to the vf output queue. - while (p->requested[0] && p->requested[0] != &dummy_img) { - struct mp_image *out = p->requested[0]; - if (out->pts != MP_NOPTS_VALUE) { - double duration = out->pts; - out->pts = p->out_pts; - p->out_pts += duration; - } - vf_add_output_frame(vf, out); - for (int n = 0; n < p->max_requests - 1; n++) - p->requested[n] = p->requested[n + 1]; - p->requested[p->max_requests - 1] = NULL; - p->out_frameno++; - r = true; + if (p->failed) { + // Not sure what we do on errors, but at least don't deadlock. + MP_ERR(f, "failed, no action taken\n"); + mp_filter_internal_mark_failed(f); + goto done; } - // Don't request frames if we haven't sent any input yet. - if (p->num_buffered + p->in_frameno == 0) - return r; - - // Request new future frames as far as possible. - for (int n = 0; n < p->max_requests; n++) { - if (!p->requested[n]) { - // Note: this assumes getFrameAsync() will never call - // infiltGetFrame (if it does, we would deadlock) - p->requested[n] = (struct mp_image *)&dummy_img; - p->failed = false; - MP_TRACE(vf, "requesting frame %d (%d)\n", p->out_frameno + n, n); - p->vsapi->getFrameAsync(p->out_frameno + n, p->out_node, - vs_frame_done, vf); + // Read input and pass it to the input queue VS reads. + if (p->num_buffered < MP_TALLOC_AVAIL(p->buffered) && !p->eof) { + // Note: this requests new input frames even if no output was ever + // requested. Normally this is not how mp_filter works, but since VS + // works asynchronously, it's probably ok. + struct mp_frame frame = mp_pin_out_read(p->in_pin); + if (frame.type == MP_FRAME_EOF) { + if (p->out_node) { + MP_VERBOSE(p, "initiate EOF\n"); + p->eof = true; + pthread_cond_broadcast(&p->wakeup); + } else if (mp_pin_in_needs_data(f->ppins[1])) { + MP_VERBOSE(p, "return EOF\n"); + mp_pin_in_write(f->ppins[1], frame); + frame = MP_NO_FRAME; + } + // Keep it until we can propagate it. + mp_pin_out_unread(p->in_pin, frame); + } else if (frame.type == MP_FRAME_VIDEO) { + struct mp_image *mpi = frame.data; + // Init VS script, or reinit it to change video format. (This + // includes derived parameters we pass manually to the script.) + if (!p->out_node || mpi->imgfmt != p->fmt_in.imgfmt || + mpi->w != p->fmt_in.w || mpi->h != p->fmt_in.h || + mpi->params.p_w != p->fmt_in.p_w || + mpi->params.p_h != p->fmt_in.p_h) + { + if (p->out_node) { + // Drain still buffered frames. + MP_VERBOSE(p, "draining VS for format change\n"); + mp_pin_out_unread(p->in_pin, frame); + p->eof = true; + pthread_cond_broadcast(&p->wakeup); + mp_filter_internal_mark_progress(f); + goto done; + } + pthread_mutex_unlock(&p->lock); + if (p->out_node) + destroy_vs(p); + p->fmt_in = mpi->params; + if (reinit_vs(p) < 0) { + MP_ERR(p, "could not init VS\n"); + mp_frame_unref(&frame); + return; + } + } + if (p->out_pts == MP_NOPTS_VALUE) + p->out_pts = mpi->pts; + p->frames_sent++; + p->buffered[p->num_buffered++] = mpi; + pthread_cond_broadcast(&p->wakeup); + } else if (frame.type != MP_FRAME_NONE) { + MP_ERR(p, "discarding unknown frame type\n"); + goto done; } } - return r; -} + // Read output and return them from the VS output queue. + if (mp_pin_in_needs_data(f->ppins[1]) && p->requested[0] && + p->requested[0] != &dummy_img && + p->requested[0] != &dummy_img_eof) + { + struct mp_image *out = p->requested[0]; -static int filter_ext(struct vf_instance *vf, struct mp_image *mpi) -{ - struct vf_priv_s *p = vf->priv; - int ret = 0; - bool eof = !mpi; + out->pts = p->out_pts; + if (p->out_pts != MP_NOPTS_VALUE && out->pkt_duration >= 0) + p->out_pts += out->pkt_duration; - if (!p->out_node) { - talloc_free(mpi); - return -1; - } + mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, out)); - MPSWAP(struct mp_image *, p->next_image, mpi); - - if (mpi) { - // Turn PTS into frame duration (the pts field is abused for storing it) - if (p->out_pts == MP_NOPTS_VALUE) - p->out_pts = mpi->pts; - mpi->pts = p->next_image ? p->next_image->pts - mpi->pts : 0; + for (int n = 0; n < p->max_requests - 1; n++) + p->requested[n] = p->requested[n + 1]; + p->requested[p->max_requests - 1] = NULL; + p->out_frameno++; } - // Try to get new frames until we get rid of the input mpi. - pthread_mutex_lock(&p->lock); - while (1) { - // Not sure what we do on errors, but at least don't deadlock. - if (p->failed) { - p->failed = false; - talloc_free(mpi); - ret = -1; - break; - } - - // Make the input frame available to infiltGetFrame(). - if (mpi && locked_need_input(vf)) { - p->frames_sent++; - p->buffered[p->num_buffered++] = talloc_steal(p->buffered, mpi); - mpi = NULL; - pthread_cond_broadcast(&p->wakeup); - } - - locked_read_output(vf); - - if (!mpi) { - if (eof && p->frames_sent && !p->eof) { - MP_VERBOSE(vf, "input EOF\n"); - p->eof = true; - pthread_cond_broadcast(&p->wakeup); - } - break; - } - pthread_cond_wait(&p->wakeup, &p->lock); + // This happens on EOF draining and format changes. + if (p->requested[0] == &dummy_img_eof) { + MP_VERBOSE(p, "finishing up\n"); + assert(p->eof); + pthread_mutex_unlock(&p->lock); + destroy_vs(p); + mp_filter_internal_mark_progress(f); + return; } - pthread_mutex_unlock(&p->lock); - return ret; -} -// Fetch 1 outout frame, or 0 if we probably need new input. -static int filter_out(struct vf_instance *vf) -{ - struct vf_priv_s *p = vf->priv; - int ret = 0; - pthread_mutex_lock(&p->lock); - while (1) { - if (p->failed) { - ret = -1; - break; + // Don't request frames if we haven't sent any input yet. + if (p->frames_sent && p->out_node) { + // Request new future frames as far as possible. + for (int n = 0; n < p->max_requests; n++) { + if (!p->requested[n]) { + // Note: this assumes getFrameAsync() will never call + // infiltGetFrame (if it does, we would deadlock) + p->requested[n] = (struct mp_image *)&dummy_img; + p->failed = false; + MP_TRACE(p, "requesting frame %d (%d)\n", p->out_frameno + n, n); + p->vsapi->getFrameAsync(p->out_frameno + n, p->out_node, + vs_frame_done, p); + } } - if (locked_read_output(vf)) - break; - // If the VS filter wants new input, there's no guarantee that we can - // actually finish any time soon without feeding new input. - if (!p->eof && locked_need_input(vf)) - break; - pthread_cond_wait(&p->wakeup, &p->lock); } - pthread_mutex_unlock(&p->lock); - return ret; -} -static bool needs_input(struct vf_instance *vf) -{ - struct vf_priv_s *p = vf->priv; - bool r = false; - pthread_mutex_lock(&p->lock); - locked_read_output(vf); - r = vf->num_out_queued < p->max_requests && locked_need_input(vf); +done: pthread_mutex_unlock(&p->lock); - return r; } static void VS_CC infiltInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) { - struct vf_instance *vf = *instanceData; - struct vf_priv_s *p = vf->priv; + struct priv *p = *instanceData; // The number of frames of our input node is obviously unknown. The user // could for example seek any time, randomly "ending" the clip. // This specific value was suggested by the VapourSynth developer. @@ -458,30 +461,29 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { - struct vf_instance *vf = *instanceData; - struct vf_priv_s *p = vf->priv; + struct priv *p = *instanceData; VSFrameRef *ret = NULL; pthread_mutex_lock(&p->lock); - MP_TRACE(vf, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno); + MP_TRACE(p, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno); while (1) { if (p->shutdown) { - p->vsapi->setFilterError("EOF or filter reinit/uninit", frameCtx); - MP_DBG(vf, "returning error on EOF/reset\n"); + p->vsapi->setFilterError("EOF or filter reset/uninit", frameCtx); + MP_DBG(p, "returning error on reset/uninit\n"); break; } if (p->initializing) { - MP_WARN(vf, "Frame requested during init! This is unsupported.\n" + MP_WARN(p, "Frame requested during init! This is unsupported.\n" "Returning black dummy frame with 0 duration.\n"); - ret = alloc_vs_frame(p, &vf->fmt_in); + ret = alloc_vs_frame(p, &p->fmt_in); if (!ret) { p->vsapi->setFilterError("Could not allocate VS frame", frameCtx); break; } struct mp_image vsframe = map_vs_frame(p, ret, true); - mp_image_clear(&vsframe, 0, 0, vf->fmt_in.w, vf->fmt_in.h); + mp_image_clear(&vsframe, 0, 0, p->fmt_in.w, p->fmt_in.h); struct mp_image dummy = {0}; - mp_image_set_params(&dummy, &vf->fmt_in); + mp_image_set_params(&dummy, &p->fmt_in); set_vs_frame_props(p, ret, &dummy, 0, 1); break; } @@ -491,7 +493,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, "Frame %d requested, but only have frames starting from %d. " "Try increasing the buffered-frames suboption.", frameno, p->in_frameno); - MP_FATAL(vf, "%s\n", msg); + MP_FATAL(p, "%s\n", msg); p->vsapi->setFilterError(msg, frameCtx); break; } @@ -501,20 +503,23 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, if (p->num_buffered) { drain_oldest_buffered_frame(p); pthread_cond_broadcast(&p->wakeup); - if (vf->chain->wakeup_callback) - vf->chain->wakeup_callback(vf->chain->wakeup_callback_ctx); + mp_filter_wakeup(p->f); continue; } } if (frameno >= p->in_frameno + p->num_buffered) { - // If we think EOF was reached, don't wait for new input, and assume - // the VS filter has reached EOF. + // If there won't be any new frames, abort the request. if (p->eof) { - p->shutdown = true; - continue; + p->vsapi->setFilterError("EOF or filter EOF/reinit", frameCtx); + MP_DBG(p, "returning error on EOF/reinit\n"); + break; } - } - if (frameno < p->in_frameno + p->num_buffered) { + // Request more frames. + if (p->requested_frameno <= p->in_frameno + p->num_buffered) { + p->requested_frameno = p->in_frameno + p->num_buffered + 1; + mp_filter_wakeup(p->f); + } + } else { struct mp_image *img = p->buffered[frameno - p->in_frameno]; ret = alloc_vs_frame(p, &img->params); if (!ret) { @@ -524,7 +529,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, struct mp_image vsframe = map_vs_frame(p, ret, true); mp_image_copy(&vsframe, img); int res = 1e6; - int dur = img->pts * res + 0.5; + int dur = img->pkt_duration * res + 0.5; set_vs_frame_props(p, ret, img, dur, res); break; } @@ -537,8 +542,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, static void VS_CC infiltFree(void *instanceData, VSCore *core, const VSAPI *vsapi) { - struct vf_instance *vf = instanceData; - struct vf_priv_s *p = vf->priv; + struct priv *p = instanceData; pthread_mutex_lock(&p->lock); p->in_node_active = false; @@ -548,7 +552,7 @@ static void VS_CC infiltFree(void *instanceData, VSCore *core, const VSAPI *vsap // number of getAsyncFrame calls in progress // must be called with p->lock held -static int num_requested(struct vf_priv_s *p) +static int num_requested(struct priv *p) { int r = 0; for (int n = 0; n < p->max_requests; n++) @@ -556,11 +560,12 @@ static int num_requested(struct vf_priv_s *p) return r; } -static void destroy_vs(struct vf_instance *vf) +static void destroy_vs(struct priv *p) { - struct vf_priv_s *p = vf->priv; + if (!p->out_node && !p->initializing) + return; - MP_DBG(vf, "destroying VS filters\n"); + MP_DBG(p, "destroying VS filters\n"); // Wait until our frame callbacks return. pthread_mutex_lock(&p->lock); @@ -571,7 +576,7 @@ static void destroy_vs(struct vf_instance *vf) pthread_cond_wait(&p->wakeup, &p->lock); pthread_mutex_unlock(&p->lock); - MP_DBG(vf, "all requests terminated\n"); + MP_DBG(p, "all requests terminated\n"); if (p->in_node) p->vsapi->freeNode(p->in_node); @@ -579,7 +584,7 @@ static void destroy_vs(struct vf_instance *vf) p->vsapi->freeNode(p->out_node); p->in_node = p->out_node = NULL; - p->drv->unload(vf); + p->drv->unload(p); assert(!p->in_node_active); assert(num_requested(p) == 0); // async callback didn't return? @@ -588,34 +593,42 @@ static void destroy_vs(struct vf_instance *vf) p->eof = false; p->frames_sent = 0; // Kill filtered images that weren't returned yet - for (int n = 0; n < p->max_requests; n++) - mp_image_unrefp(&p->requested[n]); + for (int n = 0; n < p->max_requests; n++) { + if (p->requested[n] != &dummy_img_eof) + mp_image_unrefp(&p->requested[n]); + p->requested[n] = NULL; + } // Kill queued frames too for (int n = 0; n < p->num_buffered; n++) talloc_free(p->buffered[n]); p->num_buffered = 0; - talloc_free(p->next_image); - p->next_image = NULL; p->out_pts = MP_NOPTS_VALUE; p->out_frameno = p->in_frameno = 0; + p->requested_frameno = 0; p->failed = false; - MP_DBG(vf, "uninitialized.\n"); + MP_DBG(p, "uninitialized.\n"); } -static int reinit_vs(struct vf_instance *vf) +static int reinit_vs(struct priv *p) { - struct vf_priv_s *p = vf->priv; VSMap *vars = NULL, *in = NULL, *out = NULL; int res = -1; - destroy_vs(vf); + destroy_vs(p); + + MP_DBG(p, "initializing...\n"); + + struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p->fmt_in.imgfmt); + if (p->fmt_in.w % desc.align_x || p->fmt_in.h % desc.align_y) { + MP_FATAL(p, "VapourSynth does not allow unaligned/cropped video sizes.\n"); + return -1; + } - MP_DBG(vf, "initializing...\n"); p->initializing = true; - if (p->drv->load_core(vf) < 0 || !p->vsapi || !p->vscore) { - MP_FATAL(vf, "Could not get vapoursynth API handle.\n"); + if (p->drv->load_core(p) < 0 || !p->vsapi || !p->vscore) { + MP_FATAL(p, "Could not get vapoursynth API handle.\n"); goto error; } @@ -626,11 +639,11 @@ static int reinit_vs(struct vf_instance *vf) goto error; p->vsapi->createFilter(in, out, "Input", infiltInit, infiltGetFrame, - infiltFree, fmSerial, 0, vf, p->vscore); + infiltFree, fmSerial, 0, p, p->vscore); int vserr; p->in_node = p->vsapi->propGetNode(out, "clip", 0, &vserr); if (!p->in_node) { - MP_FATAL(vf, "Could not get our own input node.\n"); + MP_FATAL(p, "Could not get our own input node.\n"); goto error; } @@ -642,26 +655,36 @@ static int reinit_vs(struct vf_instance *vf) p->vsapi->propSetInt(vars, "video_in_dw", d_w, 0); p->vsapi->propSetInt(vars, "video_in_dh", d_h, 0); - p->vsapi->propSetFloat(vars, "container_fps", vf->chain->container_fps, 0); - p->vsapi->propSetFloat(vars, "display_fps", vf->chain->display_fps, 0); - if (p->drv->load(vf, vars) < 0) + struct mp_stream_info *info = mp_filter_find_stream_info(p->f); + double container_fps = 0; + double display_fps = 0; + if (info) { + if (info->get_container_fps) + container_fps = info->get_container_fps(info); + if (info->get_display_fps) + display_fps = info->get_display_fps(info); + } + p->vsapi->propSetFloat(vars, "container_fps", container_fps, 0); + p->vsapi->propSetFloat(vars, "display_fps", display_fps, 0); + + if (p->drv->load(p, vars) < 0) goto error; if (!p->out_node) { - MP_FATAL(vf, "Could not get script output node.\n"); + MP_FATAL(p, "Could not get script output node.\n"); goto error; } const VSVideoInfo *vi = p->vsapi->getVideoInfo(p->out_node); - if (!isConstantFormat(vi)) { - MP_FATAL(vf, "Video format is required to be constant.\n"); + if (!mp_from_vs(vi->format->id)) { + MP_FATAL(p, "Unsupported output format.\n"); goto error; } pthread_mutex_lock(&p->lock); p->initializing = false; pthread_mutex_unlock(&p->lock); - MP_DBG(vf, "initialized.\n"); + MP_DBG(p, "initialized.\n"); res = 0; error: if (p->vsapi) { @@ -670,104 +693,110 @@ error: p->vsapi->freeMap(vars); } if (res < 0) - destroy_vs(vf); + destroy_vs(p); return res; } -static int reconfig(struct vf_instance *vf, struct mp_image_params *in, - struct mp_image_params *out) +static void vf_vapoursynth_reset(struct mp_filter *f) { - struct vf_priv_s *p = vf->priv; + struct priv *p = f->priv; - *out = *in; - p->fmt_in = *in; + destroy_vs(p); +} - if (reinit_vs(vf) < 0) - return -1; +static void vf_vapoursynth_destroy(struct mp_filter *f) +{ + struct priv *p = f->priv; - const VSVideoInfo *vi = p->vsapi->getVideoInfo(p->out_node); - out->w = vi->width; - out->h = vi->height; - out->imgfmt = mp_from_vs(vi->format->id); - if (!out->imgfmt) { - MP_FATAL(vf, "Unsupported output format.\n"); - destroy_vs(vf); - return -1; - } + destroy_vs(p); + p->drv->uninit(p); - struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(in->imgfmt); - if (in->w % desc.align_x || in->h % desc.align_y) { - MP_FATAL(vf, "VapourSynth does not allow unaligned/cropped video sizes.\n"); - destroy_vs(vf); - return -1; - } + pthread_cond_destroy(&p->wakeup); + pthread_mutex_destroy(&p->lock); - return 0; + mp_filter_free_children(f); } -static int query_format(struct vf_instance *vf, unsigned int fmt) -{ - return mp_to_vs(fmt) != pfNone; -} +static const struct mp_filter_info vf_vapoursynth_filter = { + .name = "vapoursynth", + .process = vf_vapoursynth_process, + .reset = vf_vapoursynth_reset, + .destroy = vf_vapoursynth_destroy, + .priv_size = sizeof(struct priv), +}; -static int control(vf_instance_t *vf, int request, void *data) +static struct mp_filter *vf_vapoursynth_create(struct mp_filter *parent, + void *options) { - struct vf_priv_s *p = vf->priv; - switch (request) { - case VFCTRL_SEEK_RESET: - if (p->out_node && reinit_vs(vf) < 0) - return CONTROL_ERROR; - return CONTROL_OK; + struct mp_filter *f = mp_filter_create(parent, &vf_vapoursynth_filter); + if (!f) { + talloc_free(options); + return NULL; } - return CONTROL_UNKNOWN; -} -static void uninit(struct vf_instance *vf) -{ - struct vf_priv_s *p = vf->priv; + // In theory, we could allow multiple inputs and outputs, but since this + // wrapper is for --vf only, we don't. + mp_filter_add_pin(f, MP_PIN_IN, "in"); + mp_filter_add_pin(f, MP_PIN_OUT, "out"); - destroy_vs(vf); - p->drv->uninit(vf); - - pthread_cond_destroy(&p->wakeup); - pthread_mutex_destroy(&p->lock); -} -static int vf_open(vf_instance_t *vf) -{ - struct vf_priv_s *p = vf->priv; - if (p->drv->init(vf) < 0) - return 0; - if (!p->cfg_file || !p->cfg_file[0]) { - MP_FATAL(vf, "'file' parameter must be set.\n"); - return 0; - } - talloc_steal(vf, p->cfg_file); - p->cfg_file = mp_get_user_path(vf, vf->chain->global, p->cfg_file); + struct priv *p = f->priv; + p->opts = talloc_steal(p, options); + p->log = f->log; + p->drv = p->opts->drv; + p->f = f; pthread_mutex_init(&p->lock, NULL); pthread_cond_init(&p->wakeup, NULL); - vf->reconfig = reconfig; - vf->filter_ext = filter_ext; - vf->filter_out = filter_out; - vf->needs_input = needs_input; - vf->query_format = query_format; - vf->control = control; - vf->uninit = uninit; - p->max_requests = p->cfg_maxrequests; + + if (!p->opts->file || !p->opts->file[0]) { + MP_FATAL(p, "'file' parameter must be set.\n"); + goto error; + } + talloc_steal(p, p->opts->file); + p->opts->file = mp_get_user_path(p, f->global, p->opts->file); + + p->max_requests = p->opts->maxrequests; if (p->max_requests < 0) p->max_requests = av_cpu_count(); - MP_VERBOSE(vf, "using %d concurrent requests.\n", p->max_requests); - int maxbuffer = p->cfg_maxbuffer * p->max_requests; - p->buffered = talloc_array(vf, struct mp_image *, maxbuffer); - p->requested = talloc_zero_array(vf, struct mp_image *, p->max_requests); - return 1; + MP_VERBOSE(p, "using %d concurrent requests.\n", p->max_requests); + int maxbuffer = p->opts->maxbuffer * p->max_requests; + p->buffered = talloc_array(p, struct mp_image *, maxbuffer); + p->requested = talloc_zero_array(p, struct mp_image *, p->max_requests); + + struct mp_autoconvert *conv = mp_autoconvert_create(f); + if (!conv) + goto error; + + for (int n = 0; mpvs_fmt_table[n].bits; n++) { + int imgfmt = mp_from_vs(mpvs_fmt_table[n].vs); + if (imgfmt) + mp_autoconvert_add_imgfmt(conv, imgfmt, 0); + } + + struct mp_filter *dur = mp_compute_frame_duration_create(f); + if (!dur) + goto error; + + mp_pin_connect(conv->f->pins[0], f->ppins[0]); + mp_pin_connect(dur->pins[0], conv->f->pins[1]); + p->in_pin = dur->pins[1]; + + if (p->drv->init(p) < 0) + goto error; + + return f; + +error: + talloc_free(f); + return NULL; } -#define OPT_BASE_STRUCT struct vf_priv_s + +#define OPT_BASE_STRUCT struct vapoursynth_opts static const m_option_t vf_opts_fields[] = { - OPT_STRING("file", cfg_file, M_OPT_FILE), - OPT_INTRANGE("buffered-frames", cfg_maxbuffer, 0, 1, 9999, OPTDEF_INT(4)), - OPT_CHOICE_OR_INT("concurrent-frames", cfg_maxrequests, 0, 1, 99, + OPT_STRING("file", file, M_OPT_FILE), + OPT_INTRANGE("buffered-frames", maxbuffer, 0, 1, 9999, OPTDEF_INT(4)), + OPT_CHOICE_OR_INT("concurrent-frames", maxrequests, 0, 1, 99, ({"auto", -1}), OPTDEF_INT(-1)), {0} }; @@ -776,24 +805,22 @@ static const m_option_t vf_opts_fields[] = { #include <VSScript.h> -static int drv_vss_init(struct vf_instance *vf) +static int drv_vss_init(struct priv *p) { if (!vsscript_init()) { - MP_FATAL(vf, "Could not initialize VapourSynth scripting.\n"); + MP_FATAL(p, "Could not initialize VapourSynth scripting.\n"); return -1; } return 0; } -static void drv_vss_uninit(struct vf_instance *vf) +static void drv_vss_uninit(struct priv *p) { vsscript_finalize(); } -static int drv_vss_load_core(struct vf_instance *vf) +static int drv_vss_load_core(struct priv *p) { - struct vf_priv_s *p = vf->priv; - // First load an empty script to get a VSScript, so that we get the vsapi // and vscore. if (vsscript_createScript(&p->se)) @@ -803,24 +830,20 @@ static int drv_vss_load_core(struct vf_instance *vf) return 0; } -static int drv_vss_load(struct vf_instance *vf, VSMap *vars) +static int drv_vss_load(struct priv *p, VSMap *vars) { - struct vf_priv_s *p = vf->priv; - vsscript_setVariable(p->se, vars); - if (vsscript_evaluateFile(&p->se, p->cfg_file, 0)) { - MP_FATAL(vf, "Script evaluation failed:\n%s\n", vsscript_getError(p->se)); + if (vsscript_evaluateFile(&p->se, p->opts->file, 0)) { + MP_FATAL(p, "Script evaluation failed:\n%s\n", vsscript_getError(p->se)); return -1; } p->out_node = vsscript_getOutput(p->se, 0); return 0; } -static void drv_vss_unload(struct vf_instance *vf) +static void drv_vss_unload(struct priv *p) { - struct vf_priv_s *p = vf->priv; - if (p->se) vsscript_freeScript(p->se); p->se = NULL; @@ -836,19 +859,17 @@ static const struct script_driver drv_vss = { .unload = drv_vss_unload, }; -static int vf_open_vss(vf_instance_t *vf) -{ - struct vf_priv_s *p = vf->priv; - p->drv = &drv_vss; - return vf_open(vf); -} - -const vf_info_t vf_info_vapoursynth = { - .description = "VapourSynth bridge (Python)", - .name = "vapoursynth", - .open = vf_open_vss, - .priv_size = sizeof(struct vf_priv_s), - .options = vf_opts_fields, +const struct mp_user_filter_entry vf_vapoursynth = { + .desc = { + .description = "VapourSynth bridge (Python)", + .name = "vapoursynth", + .priv_size = sizeof(OPT_BASE_STRUCT), + .priv_defaults = &(const OPT_BASE_STRUCT){ + .drv = &drv_vss, |