Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH v2] libavformat/vapoursynth: Update to API version 4, load library at runtime
@ 2024-07-06 21:08 Stefan Oltmanns via ffmpeg-devel
  2024-07-18  9:37 ` Stefan Oltmanns via ffmpeg-devel
  2024-07-18 11:08 ` Ramiro Polla
  0 siblings, 2 replies; 22+ messages in thread
From: Stefan Oltmanns via ffmpeg-devel @ 2024-07-06 21:08 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Stefan Oltmanns

[-- Attachment #1: Type: text/plain, Size: 2159 bytes --]

Hello,

this is revised patch, to sum up the changes:

The current VapourSynth implementation is rarely used, as it links the
VapourSynth library at build time, making the resulting build unable to
run when VapourSynth is not installed. Therefore barely anyone compiles
with VapourSynth activated.

I changed it, so that it loads the library at runtime when a VapourSynth
script should be opened, just like AviSynth does.
On Windows the DLL from VapourSynth is not installed in the system
directory, but the location is stored in the Registry. Therefore I added
some code to read that information from the registry.

As the V4 API is designed with dynamic loading in mind (only a single
import), I updated the implementation to V4 (changes are mostly
superficial, no structural changes). The V4 API is already several years
old, fully supported since R55 released in 2021.


Changes from first patch:
-Separated the Windows-specific function for getting the DLL location
from the platform-specific includes
-It is not enabled by default in configure
-The header files are not included anymore


I would like to include the header files for this reason:
While most Linux distributions ship ffmpeg, only very few contain
VapourSynth. Therefore ffmpeg won't be compiled with VapourSynth support
by these distributions, because no VapourSynth headers. Including the
headers in ffmpeg would mean they can compile with VapourSynth support
and if a user decided to install VapourSynth from somewhere else or
compile it by himself, ffmpeg support would be ready and no need for the
user to install another ffmpeg or compile one.
I'm not sure what the rules for shipping include files are, as there are
a few 3rd-party include files in ffmpeg. License is not an issue
(Vapourynth is LGPL v2.1 or later like ffmpeg).



make fate runs without any issue. I tested VapourSynth input scripts
with various color formats on different platforms:

Ubuntu 22.04
macOS 13 (x86_64)
macOS 13 (arm64)
Windows 10 (msys2/gcc)

It compiles on these platforms without any warning and runs without any
issues.

Best regards
Stefan

[-- Attachment #2: 0001-avformat-vapoursynth-Update-to-API-version-4-load-li.patch --]
[-- Type: text/x-patch, Size: 12682 bytes --]

From 759b097865953ee66949ecbcdadbebfad623c29a Mon Sep 17 00:00:00 2001
From: Stefan Oltmanns <stefan-oltmanns@gmx.net>
Date: Sat, 6 Jul 2024 22:56:53 +0200
Subject: [PATCH] avformat/vapoursynth: Update to API version 4, load library
 at runtime

Signed-off-by: Stefan Oltmanns <stefan-oltmanns@gmx.net>
---
 configure                 |   3 +-
 libavformat/vapoursynth.c | 171 +++++++++++++++++++++++++++++---------
 2 files changed, 136 insertions(+), 38 deletions(-)

diff --git a/configure b/configure
index b28221f258..e43f3827ec 100755
--- a/configure
+++ b/configure
@@ -3575,6 +3575,7 @@ libxevd_decoder_deps="libxevd"
 libxeve_encoder_deps="libxeve"
 libxvid_encoder_deps="libxvid"
 libzvbi_teletext_decoder_deps="libzvbi"
+vapoursynth_deps_any="libdl LoadLibrary"
 vapoursynth_demuxer_deps="vapoursynth"
 videotoolbox_suggest="coreservices"
 videotoolbox_deps="corefoundation coremedia corevideo"
@@ -7080,7 +7081,7 @@ enabled rkmpp             && { require_pkg_config rkmpp rockchip_mpp  rockchip/r
                                { enabled libdrm ||
                                  die "ERROR: rkmpp requires --enable-libdrm"; }
                              }
-enabled vapoursynth       && require_pkg_config vapoursynth "vapoursynth-script >= 42" VSScript.h vsscript_init
+enabled vapoursynth       && require_headers "vapoursynth/VSScript4.h vapoursynth/VapourSynth4.h"
 
 
 if enabled gcrypt; then
diff --git a/libavformat/vapoursynth.c b/libavformat/vapoursynth.c
index 8a2519e19a..9c82f8f3b8 100644
--- a/libavformat/vapoursynth.c
+++ b/libavformat/vapoursynth.c
@@ -25,9 +25,6 @@
 
 #include <limits.h>
 
-#include <VapourSynth.h>
-#include <VSScript.h>
-
 #include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
 #include "libavutil/eval.h"
@@ -40,19 +37,46 @@
 #include "demux.h"
 #include "internal.h"
 
+/* Platform-specific directives. */
+#ifdef _WIN32
+  #include <windows.h>
+  #include "compat/w32dlfcn.h"
+  #include "libavutil/wchar_filename.h"
+  #undef EXTERN_C
+  #define VSSCRIPT_LIB "VSScript.dll"
+  #define VS_DLOPEN() ({ void *handle = NULL; \
+                        char *dll_name = get_vs_script_dll_name(); \
+                        handle = dlopen(dll_name, RTLD_NOW | RTLD_GLOBAL); \
+                        av_free(dll_name); \
+                        handle; })
+#else
+  #include <dlfcn.h>
+  #define VSSCRIPT_NAME "libvapoursynth-script"
+  #define VSSCRIPT_LIB VSSCRIPT_NAME SLIBSUF
+  #define VS_DLOPEN() dlopen(VSSCRIPT_LIB, RTLD_NOW | RTLD_GLOBAL)
+#endif
+
+#include <vapoursynth/VSScript4.h>
+
 struct VSState {
     VSScript *vss;
 };
 
+typedef const VSSCRIPTAPI *(*VSScriptGetAPIFunc)(int version);
+
+typedef struct VSScriptLibrary {
+    void *library;
+    const VSSCRIPTAPI *vssapi;
+} VSScriptLibrary;
+
 typedef struct VSContext {
     const AVClass *class;
 
     AVBufferRef *vss_state;
 
     const VSAPI *vsapi;
-    VSCore *vscore;
 
-    VSNodeRef *outnode;
+    VSNode *outnode;
     int is_cfr;
     int current_frame;
 
@@ -70,19 +94,72 @@ static const AVOption options[] = {
     {NULL}
 };
 
+static VSScriptLibrary vs_script_library;
+
+static int vs_atexit_called = 0;
+
+static av_cold void vs_atexit_handler(void);
+
+#ifdef _WIN32
+static av_cold char* get_vs_script_dll_name(void) {
+     LONG r;
+     WCHAR vss_path[512];
+     char *vss_path_utf8;
+     DWORD buf_size = sizeof(vss_path) - 2;
+     r = RegGetValueW(HKEY_CURRENT_USER, L"SOFTWARE\\VapourSynth",
+                      L"VSScriptDLL", RRF_RT_REG_SZ, NULL,
+                      &vss_path, &buf_size);
+     if (r == ERROR_SUCCESS && wchartoutf8(vss_path, &vss_path_utf8) == 0)
+         return vss_path_utf8;
+     r = RegGetValueW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\VapourSynth",
+                      L"VSScriptDLL", RRF_RT_REG_SZ, NULL,
+                      &vss_path, &buf_size);
+     if (r == ERROR_SUCCESS && wchartoutf8(vss_path, &vss_path_utf8) == 0)
+         return vss_path_utf8;
+     if (wchartoutf8(L"VSScript.dll", &vss_path_utf8) == 0)
+         return vss_path_utf8;
+     return 0;
+}
+#endif
+
+static av_cold int vs_load_library(void)
+{
+    VSScriptGetAPIFunc get_vs_script_api;
+    vs_script_library.library = VS_DLOPEN();
+    if (!vs_script_library.library)
+        return -1;
+    get_vs_script_api = (VSScriptGetAPIFunc)dlsym(vs_script_library.library,
+                                                  "getVSScriptAPI");
+    if (!get_vs_script_api) {
+        dlclose(vs_script_library.library);
+        return -2;
+    }
+    vs_script_library.vssapi = get_vs_script_api(VSSCRIPT_API_VERSION);
+    if (!vs_script_library.vssapi) {
+        dlclose(vs_script_library.library);
+        return -3;
+    }
+    atexit(vs_atexit_handler);
+    return 0;
+}
+
 static void free_vss_state(void *opaque, uint8_t *data)
 {
     struct VSState *vss = opaque;
 
     if (vss->vss) {
-        vsscript_freeScript(vss->vss);
-        vsscript_finalize();
+        vs_script_library.vssapi->freeScript(vss->vss);
     }
 }
 
 static av_cold int read_close_vs(AVFormatContext *s)
 {
-    VSContext *vs = s->priv_data;
+    VSContext *vs;
+
+    if (vs_atexit_called)
+        return 0;
+
+    vs = s->priv_data;
 
     if (vs->outnode)
         vs->vsapi->freeNode(vs->outnode);
@@ -90,12 +167,17 @@ static av_cold int read_close_vs(AVFormatContext *s)
     av_buffer_unref(&vs->vss_state);
 
     vs->vsapi = NULL;
-    vs->vscore = NULL;
     vs->outnode = NULL;
 
     return 0;
 }
 
+static av_cold void vs_atexit_handler(void)
+{
+    dlclose(vs_script_library.library);
+    vs_atexit_called = 1;
+}
+
 static av_cold int is_native_endian(enum AVPixelFormat pixfmt)
 {
     enum AVPixelFormat other = av_pix_fmt_swap_endianness(pixfmt);
@@ -106,7 +188,7 @@ static av_cold int is_native_endian(enum AVPixelFormat pixfmt)
     return pd && (!!HAVE_BIGENDIAN == !!(pd->flags & AV_PIX_FMT_FLAG_BE));
 }
 
-static av_cold enum AVPixelFormat match_pixfmt(const VSFormat *vsf, int c_order[4])
+static av_cold enum AVPixelFormat match_pixfmt(const VSVideoFormat *vsf, int c_order[4])
 {
     static const int yuv_order[4] = {0, 1, 2, 0};
     static const int rgb_order[4] = {1, 2, 0, 0};
@@ -128,13 +210,12 @@ static av_cold enum AVPixelFormat match_pixfmt(const VSFormat *vsf, int c_order[
             pd->log2_chroma_h != vsf->subSamplingH)
             continue;
 
-        is_rgb = vsf->colorFamily == cmRGB;
+        is_rgb = vsf->colorFamily == cfRGB;
         if (is_rgb != !!(pd->flags & AV_PIX_FMT_FLAG_RGB))
             continue;
 
-        is_yuv = vsf->colorFamily == cmYUV ||
-                 vsf->colorFamily == cmYCoCg ||
-                 vsf->colorFamily == cmGray;
+        is_yuv = vsf->colorFamily == cfYUV ||
+                 vsf->colorFamily == cfGray;
         if (!is_rgb && !is_yuv)
             continue;
 
@@ -176,6 +257,7 @@ static av_cold int read_header_vs(AVFormatContext *s)
     int64_t sz = avio_size(pb);
     char *buf = NULL;
     char dummy;
+    char vsfmt[32];
     const VSVideoInfo *info;
     struct VSState *vss_state;
     int err = 0;
@@ -193,16 +275,30 @@ static av_cold int read_header_vs(AVFormatContext *s)
         goto done;
     }
 
-    if (!vsscript_init()) {
-        av_log(s, AV_LOG_ERROR, "Failed to initialize VSScript (possibly PYTHONPATH not set).\n");
+    if (err = vs_load_library()) {
+        if (err == -1) av_log(s, AV_LOG_ERROR, "Could not open " VSSCRIPT_LIB
+                              ". Check VapourSynth installation.\n");
+        else if (err == -2) av_log(s, AV_LOG_ERROR,
+                                   "Could not load VapourSynth library. "
+                                   "VapourSynth installation may be outdated "
+                                   "or broken.\n");
+        else if (err == -3) av_log(s, AV_LOG_ERROR,
+                                   "Failed to initialize VSScript "
+                                   "(possibly PYTHONPATH not set).\n");
         err = AVERROR_EXTERNAL;
         goto done;
     }
 
-    if (vsscript_createScript(&vss_state->vss)) {
+    if (!(vs->vsapi = vs_script_library.vssapi->getVSAPI(VAPOURSYNTH_API_VERSION))) {
+        av_log(s, AV_LOG_ERROR, "Could not get VSAPI. "
+                                "Check VapourSynth installation.\n");
+        err = AVERROR_EXTERNAL;
+        goto done;
+    }
+
+    if (!(vss_state->vss = vs_script_library.vssapi->createScript(NULL))) {
         av_log(s, AV_LOG_ERROR, "Failed to create script instance.\n");
         err = AVERROR_EXTERNAL;
-        vsscript_finalize();
         goto done;
     }
 
@@ -235,17 +331,14 @@ static av_cold int read_header_vs(AVFormatContext *s)
     }
 
     buf[sz] = '\0';
-    if (vsscript_evaluateScript(&vss_state->vss, buf, s->url, 0)) {
-        const char *msg = vsscript_getError(vss_state->vss);
+    if (vs_script_library.vssapi->evaluateBuffer(vss_state->vss, buf, s->url)) {
+        const char *msg = vs_script_library.vssapi->getError(vss_state->vss);
         av_log(s, AV_LOG_ERROR, "Failed to parse script: %s\n", msg ? msg : "(unknown)");
         err = AVERROR_EXTERNAL;
         goto done;
     }
 
-    vs->vsapi = vsscript_getVSApi();
-    vs->vscore = vsscript_getCore(vss_state->vss);
-
-    vs->outnode = vsscript_getOutput(vss_state->vss, 0);
+    vs->outnode = vs_script_library.vssapi->getOutputNode(vss_state->vss, 0);
     if (!vs->outnode) {
         av_log(s, AV_LOG_ERROR, "Could not get script output node.\n");
         err = AVERROR_EXTERNAL;
@@ -260,7 +353,7 @@ static av_cold int read_header_vs(AVFormatContext *s)
 
     info = vs->vsapi->getVideoInfo(vs->outnode);
 
-    if (!info->format || !info->width || !info->height) {
+    if (!info->format.colorFamily || !info->width || !info->height) {
         av_log(s, AV_LOG_ERROR, "Non-constant input format not supported.\n");
         err = AVERROR_PATCHWELCOME;
         goto done;
@@ -280,18 +373,22 @@ static av_cold int read_header_vs(AVFormatContext *s)
     st->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME;
     st->codecpar->width = info->width;
     st->codecpar->height = info->height;
-    st->codecpar->format = match_pixfmt(info->format, vs->c_order);
+    st->codecpar->format = match_pixfmt(&info->format, vs->c_order);
 
     if (st->codecpar->format == AV_PIX_FMT_NONE) {
-        av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", info->format->name);
+        if(vs->vsapi->getVideoFormatName(&info->format, vsfmt))
+            av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", vsfmt);
+        else
+            av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format\n");
         err = AVERROR_EXTERNAL;
         goto done;
     }
-    av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n", info->format->name,
-           av_get_pix_fmt_name(st->codecpar->format));
-
-    if (info->format->colorFamily == cmYCoCg)
-        st->codecpar->color_space = AVCOL_SPC_YCGCO;
+    if (vs->vsapi->getVideoFormatName(&info->format, vsfmt))
+        av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n",
+               vsfmt, av_get_pix_fmt_name(st->codecpar->format));
+    else
+        av_log(s, AV_LOG_VERBOSE, "VS format -> pixfmt %s\n",
+               av_get_pix_fmt_name(st->codecpar->format));
 
 done:
     av_free(buf);
@@ -311,13 +408,13 @@ static int get_vs_prop_int(AVFormatContext *s, const VSMap *map, const char *nam
     int64_t res;
     int err = 1;
 
-    res = vs->vsapi->propGetInt(map, name, 0, &err);
+    res = vs->vsapi->mapGetInt(map, name, 0, &err);
     return err || res < INT_MIN || res > INT_MAX ? def : res;
 }
 
 struct vsframe_ref_data {
     const VSAPI *vsapi;
-    const VSFrameRef *frame;
+    const VSFrame *frame;
     AVBufferRef *vss_state;
 };
 
@@ -339,7 +436,7 @@ static int read_packet_vs(AVFormatContext *s, AVPacket *pkt)
     AVStream *st = s->streams[0];
     AVFrame *frame = NULL;
     char vserr[80];
-    const VSFrameRef *vsframe;
+    const VSFrame *vsframe;
     const VSVideoInfo *info = vs->vsapi->getVideoInfo(vs->outnode);
     const VSMap *props;
     const AVPixFmtDescriptor *desc;
@@ -381,7 +478,7 @@ static int read_packet_vs(AVFormatContext *s, AVPacket *pkt)
         goto end;
     }
 
-    props = vs->vsapi->getFramePropsRO(vsframe);
+    props = vs->vsapi->getFramePropertiesRO(vsframe);
 
     frame = av_frame_alloc();
     if (!frame) {
@@ -410,7 +507,7 @@ static int read_packet_vs(AVFormatContext *s, AVPacket *pkt)
 
     desc = av_pix_fmt_desc_get(frame->format);
 
-    for (i = 0; i < info->format->numPlanes; i++) {
+    for (i = 0; i < info->format.numPlanes; i++) {
         int p = vs->c_order[i];
         ptrdiff_t plane_h = frame->height;
 
-- 
2.34.1


[-- Attachment #3: Type: text/plain, Size: 251 bytes --]

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2024-07-23  0:25 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-07-06 21:08 [FFmpeg-devel] [PATCH v2] libavformat/vapoursynth: Update to API version 4, load library at runtime Stefan Oltmanns via ffmpeg-devel
2024-07-18  9:37 ` Stefan Oltmanns via ffmpeg-devel
2024-07-18 11:25   ` Anton Khirnov
2024-07-18 15:38     ` Stefan Oltmanns via ffmpeg-devel
2024-07-22  6:57       ` Anton Khirnov
2024-07-23  0:24         ` Stefan Oltmanns via ffmpeg-devel
2024-07-18 11:08 ` Ramiro Polla
2024-07-18 12:48   ` Stefan Oltmanns via ffmpeg-devel
2024-07-18 13:04     ` Ramiro Polla
2024-07-18 13:41       ` Stefan Oltmanns via ffmpeg-devel
2024-07-18 14:21         ` Ramiro Polla
2024-07-18 14:53           ` Stefan Oltmanns via ffmpeg-devel
2024-07-19 17:05             ` Stephen Hutchinson
2024-07-18 15:23           ` epirat07
2024-07-21 22:08             ` Stefan Oltmanns via ffmpeg-devel
2024-07-21 22:15               ` Hendrik Leppkes
2024-07-22  0:42                 ` Stefan Oltmanns via ffmpeg-devel
2024-07-22  6:36                   ` Hendrik Leppkes
2024-07-22 12:13                 ` Ramiro Polla
2024-07-22 13:41                   ` Hendrik Leppkes
2024-07-22 18:28                     ` Ramiro Polla
2024-07-22 13:52                   ` Stefan Oltmanns via ffmpeg-devel

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git