Add vapoursynth video provider
This commit is contained in:
parent
4a5a212f35
commit
3f298bf03a
12 changed files with 454 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -43,3 +43,4 @@ subprojects/zlib-*
|
|||
subprojects/dirent-*
|
||||
subprojects/hunspell-*
|
||||
subprojects/uchardet-*
|
||||
subprojects/vapoursynth
|
||||
|
|
|
@ -242,6 +242,13 @@ if host_machine.system() == 'windows' and get_option('avisynth').enabled()
|
|||
conf.set('WITH_AVISYNTH', 1) # bundled separately with installer
|
||||
endif
|
||||
|
||||
if get_option('vapoursynth').enabled()
|
||||
conf.set('WITH_VAPOURSYNTH', 1)
|
||||
vs_sub = subproject('vapoursynth')
|
||||
deps_inc += vs_sub.get_variable('vs_inc')
|
||||
dep_avail += 'VapourSynth'
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'windows' and not get_option('directsound').disabled()
|
||||
dsound_dep = cc.find_library('dsound', required: get_option('directsound'))
|
||||
winmm_dep = cc.find_library('winmm', required: get_option('directsound'))
|
||||
|
|
|
@ -8,6 +8,7 @@ option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL'
|
|||
option('ffms2', type: 'feature', description: 'FFMS2 video source')
|
||||
option('avisynth', type: 'feature', description: 'AviSynth video source')
|
||||
option('bestsource', type: 'feature', description: 'BestSource video source')
|
||||
option('vapoursynth', type: 'feature', description: 'VapourSynth video source')
|
||||
|
||||
option('fftw3', type: 'feature', description: 'FFTW3 support')
|
||||
option('hunspell', type: 'feature', description: 'Hunspell spell checker')
|
||||
|
|
|
@ -29,6 +29,7 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes";
|
|||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".wav"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".ogg"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".avs"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".vpy"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".opus"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".h264"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".hevc"; ValueData: ""; Flags: uninsdeletekey
|
||||
|
@ -165,6 +166,7 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\.m4a\OpenWithProgids"; ValueType: string;
|
|||
Root: HKLM; Subkey: "SOFTWARE\Classes\.wav\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.ogg\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.avs\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.vpy\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.opus\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.h264\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.hevc\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
|
||||
|
|
|
@ -564,7 +564,7 @@ struct video_open final : public Command {
|
|||
STR_HELP("Open a video file")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.y4m;*.yuv|"
|
||||
auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.vpy,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.vpy;*.y4m;*.yuv|"
|
||||
+ _("All Files") + " (*.*)|*.*");
|
||||
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
|
||||
if (!filename.empty())
|
||||
|
|
|
@ -239,6 +239,8 @@ opt_src = [
|
|||
['BestSource', ['audio_provider_bestsource.cpp',
|
||||
'video_provider_bestsource.cpp',
|
||||
'bestsource_common.cpp']],
|
||||
['VapourSynth', ['vapoursynth_wrap.cpp',
|
||||
'video_provider_vs.cpp']],
|
||||
|
||||
['Hunspell', 'spellchecker_hunspell.cpp'],
|
||||
]
|
||||
|
|
105
src/vapoursynth_wrap.cpp
Normal file
105
src/vapoursynth_wrap.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file vapoursynth_wrap.cpp
|
||||
/// @brief Wrapper-layer for Vapoursynth
|
||||
/// @ingroup video_input audio_input
|
||||
///
|
||||
|
||||
#ifdef WITH_VAPOURSYNTH
|
||||
#include "vapoursynth_wrap.h"
|
||||
|
||||
#include "VSScript4.h"
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VSSCRIPT_SO "vsscript.dll"
|
||||
#else
|
||||
#define VSSCRIPT_SO "libvapoursynth-script.so"
|
||||
#endif
|
||||
|
||||
// Allocate storage for and initialise static members
|
||||
namespace {
|
||||
bool vs_loaded = false;
|
||||
#ifdef _WIN32
|
||||
HINSTANCE hLib = nullptr;
|
||||
#else
|
||||
void* hLib = nullptr;
|
||||
#endif
|
||||
const VSAPI *api = nullptr;
|
||||
VSSCRIPTAPI *scriptapi = nullptr;
|
||||
std::mutex VapourSynthMutex;
|
||||
}
|
||||
|
||||
typedef VSSCRIPTAPI* VS_CC FUNC(int);
|
||||
|
||||
VapourSynthWrapper::VapourSynthWrapper() {
|
||||
// VSScript assumes it's only loaded once, so unlike AVS we can't unload it when the refcount reaches zero
|
||||
if (!vs_loaded) {
|
||||
vs_loaded = true;
|
||||
#ifdef _WIN32
|
||||
#define CONCATENATE(x, y) x ## y
|
||||
#define _Lstr(x) CONCATENATE(L, x)
|
||||
hLib = LoadLibraryW(_Lstr(VSSCRIPT_SO));
|
||||
#undef _Lstr
|
||||
#undef CONCATENATE
|
||||
#else
|
||||
hLib = dlopen(VSSCRIPT_SO, RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND);
|
||||
#endif
|
||||
|
||||
if (!hLib)
|
||||
throw VapoursynthError("Could not load " VSSCRIPT_SO);
|
||||
|
||||
#ifdef _WIN32
|
||||
FUNC* getVSScriptAPI = (FUNC*)GetProcAddress(hLib, "getVSScriptAPI");
|
||||
#else
|
||||
FUNC* getVSScriptAPI = (FUNC*)dlsym(hLib, "getVSScriptAPI");
|
||||
#endif
|
||||
if (!getVSScriptAPI)
|
||||
throw VapoursynthError("Failed to get address of getVSScriptAPI from " VSSCRIPT_SO);
|
||||
|
||||
scriptapi = getVSScriptAPI(VSSCRIPT_API_VERSION);
|
||||
|
||||
if (!scriptapi)
|
||||
throw VapoursynthError("Failed to get Vapoursynth ScriptAPI");
|
||||
|
||||
api = scriptapi->getVSAPI(VAPOURSYNTH_API_VERSION);
|
||||
|
||||
if (!api)
|
||||
throw VapoursynthError("Failed to get Vapoursynth API");
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex& VapourSynthWrapper::GetMutex() const {
|
||||
return VapourSynthMutex;
|
||||
}
|
||||
|
||||
const VSAPI *VapourSynthWrapper::GetAPI() const {
|
||||
return api;
|
||||
}
|
||||
|
||||
const VSSCRIPTAPI *VapourSynthWrapper::GetScriptAPI() const {
|
||||
return scriptapi;
|
||||
}
|
||||
|
||||
#endif
|
42
src/vapoursynth_wrap.h
Normal file
42
src/vapoursynth_wrap.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file vapoursynth_wrap.h
|
||||
/// @see vapoursynth_wrap.cpp
|
||||
/// @ingroup video_input audio_input
|
||||
///
|
||||
|
||||
#ifdef WITH_VAPOURSYNTH
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
|
||||
DEFINE_EXCEPTION(VapoursynthError, agi::Exception);
|
||||
|
||||
struct VSAPI;
|
||||
struct VSSCRIPTAPI;
|
||||
namespace std { class mutex; }
|
||||
|
||||
class VapourSynthWrapper {
|
||||
VapourSynthWrapper(VapourSynthWrapper const&);
|
||||
public:
|
||||
std::mutex& GetMutex() const;
|
||||
const VSAPI *GetAPI() const;
|
||||
const VSSCRIPTAPI *GetScriptAPI() const;
|
||||
|
||||
VapourSynthWrapper();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -30,6 +30,7 @@ std::unique_ptr<VideoProvider> CreateYUV4MPEGVideoProvider(agi::fs::path const&,
|
|||
std::unique_ptr<VideoProvider> CreateFFmpegSourceVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
|
||||
std::unique_ptr<VideoProvider> CreateAvisynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
|
||||
std::unique_ptr<VideoProvider> CreateBSVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
|
||||
std::unique_ptr<VideoProvider> CreateVapoursynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
|
||||
|
||||
std::unique_ptr<VideoProvider> CreateCacheVideoProvider(std::unique_ptr<VideoProvider>);
|
||||
|
||||
|
@ -51,6 +52,9 @@ namespace {
|
|||
#endif
|
||||
#ifdef WITH_BESTSOURCE
|
||||
{"BestSource", CreateBSVideoProvider, false},
|
||||
#endif
|
||||
#ifdef WITH_VAPOURSYNTH
|
||||
{"Vapoursynth", CreateVapoursynthVideoProvider, false},
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
|
282
src/video_provider_vs.cpp
Normal file
282
src/video_provider_vs.cpp
Normal file
|
@ -0,0 +1,282 @@
|
|||
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
#ifdef WITH_VAPOURSYNTH
|
||||
#include "include/aegisub/video_provider.h"
|
||||
|
||||
#include "options.h"
|
||||
#include "video_frame.h"
|
||||
|
||||
#include <libaegisub/access.h>
|
||||
#include <libaegisub/format.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/path.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "vapoursynth_wrap.h"
|
||||
#include "VSScript4.h"
|
||||
#include "VSHelper4.h"
|
||||
#include "VSConstants4.h"
|
||||
|
||||
namespace {
|
||||
class VapoursynthVideoProvider: public VideoProvider {
|
||||
VapourSynthWrapper vs;
|
||||
VSScript *script = nullptr;
|
||||
VSNode *node = nullptr;
|
||||
const VSVideoInfo *vi = nullptr;
|
||||
|
||||
double dar = 0;
|
||||
agi::vfr::Framerate fps;
|
||||
std::vector<int> keyframes;
|
||||
std::string colorspace;
|
||||
std::string real_colorspace;
|
||||
|
||||
const VSFrame *GetVSFrame(int n);
|
||||
void SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int deflt, int unspecified = -1);
|
||||
|
||||
public:
|
||||
VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix);
|
||||
~VapoursynthVideoProvider();
|
||||
|
||||
void GetFrame(int n, VideoFrame &frame) override;
|
||||
|
||||
void SetColorSpace(std::string const& matrix) override { }
|
||||
|
||||
int GetFrameCount() const override { return vi->numFrames; }
|
||||
agi::vfr::Framerate GetFPS() const override { return fps; }
|
||||
int GetWidth() const override { return vi->width; }
|
||||
int GetHeight() const override { return vi->height; }
|
||||
double GetDAR() const override { return dar; }
|
||||
std::vector<int> GetKeyFrames() const override { return keyframes; }
|
||||
std::string GetColorSpace() const override { return colorspace; }
|
||||
std::string GetRealColorSpace() const override { return colorspace; }
|
||||
bool HasAudio() const override { return false; }
|
||||
virtual bool WantsCaching() const override { return true; }
|
||||
virtual std::string GetDecoderName() const override { return "VapourSynth"; }
|
||||
};
|
||||
|
||||
std::string colormatrix_description(int colorFamily, int colorRange, int matrix) {
|
||||
if (colorFamily != cfYUV) {
|
||||
return "None";
|
||||
}
|
||||
// Assuming TV for unspecified
|
||||
std::string str = colorRange == VSC_RANGE_FULL ? "PC" : "TV";
|
||||
|
||||
switch (matrix) {
|
||||
case VSC_MATRIX_RGB:
|
||||
return "None";
|
||||
case VSC_MATRIX_BT709:
|
||||
return str + ".709";
|
||||
case VSC_MATRIX_FCC:
|
||||
return str + ".FCC";
|
||||
case VSC_MATRIX_BT470_BG:
|
||||
case VSC_MATRIX_ST170_M:
|
||||
return str + ".601";
|
||||
case VSC_MATRIX_ST240_M:
|
||||
return str + ".240M";
|
||||
default:
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
// Adds an argument to the rescaler if the corresponding frameprop does not exist or is set as unspecified
|
||||
void VapoursynthVideoProvider::SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int deflt, int unspecified) {
|
||||
int err;
|
||||
int result = vs.GetAPI()->mapGetInt(props, prop_name, 0, &err);
|
||||
if (err != 0 || result == unspecified) {
|
||||
result = deflt;
|
||||
vs.GetAPI()->mapSetInt(args, arg_name, result, maAppend);
|
||||
}
|
||||
}
|
||||
|
||||
VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix) try {
|
||||
agi::acs::CheckFileRead(filename);
|
||||
std::lock_guard<std::mutex> lock(vs.GetMutex());
|
||||
|
||||
script = vs.GetScriptAPI()->createScript(nullptr);
|
||||
if (script == nullptr) {
|
||||
throw VapoursynthError("Error creating script API");
|
||||
}
|
||||
vs.GetScriptAPI()->evalSetWorkingDir(script, 1);
|
||||
if (vs.GetScriptAPI()->evaluateFile(script, filename.string().c_str())) {
|
||||
std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script));
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw VapoursynthError(msg);
|
||||
}
|
||||
node = vs.GetScriptAPI()->getOutputNode(script, 0);
|
||||
if (node == nullptr) {
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw VapoursynthError("No output node set");
|
||||
}
|
||||
if (vs.GetAPI()->getNodeType(node) != mtVideo) {
|
||||
vs.GetAPI()->freeNode(node);
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw VapoursynthError("Output node isn't a video node");
|
||||
}
|
||||
vi = vs.GetAPI()->getVideoInfo(node);
|
||||
if (!vsh::isConstantVideoFormat(vi)) {
|
||||
vs.GetAPI()->freeNode(node);
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw VapoursynthError("Video doesn't have constant format");
|
||||
}
|
||||
|
||||
// Assume constant frame rate, since handling VFR would require going through all frames when loading.
|
||||
// Users can load custom timecodes files to deal with VFR.
|
||||
// Alternatively (TODO) the provider could read timecodes and keyframes from a second output node.
|
||||
fps = (double) vi->fpsNum / vi->fpsDen;
|
||||
|
||||
// Find the first frame to get some info
|
||||
const VSFrame *frame;
|
||||
try {
|
||||
frame = GetVSFrame(0);
|
||||
} catch (VapoursynthError const& err) {
|
||||
vs.GetAPI()->freeNode(node);
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw err;
|
||||
}
|
||||
int err1, err2;
|
||||
const VSMap *props = vs.GetAPI()->getFramePropertiesRO(frame);
|
||||
int sarn = vs.GetAPI()->mapGetInt(props, "_SARNum", 0, &err1);
|
||||
int sard = vs.GetAPI()->mapGetInt(props, "_SARDen", 0, &err2);
|
||||
if (!err1 && !err2) {
|
||||
dar = ((double) vi->width * sarn) / (vi->height * sard);
|
||||
}
|
||||
|
||||
int range = vs.GetAPI()->mapGetInt(props, "_ColorRange", 0, &err1);
|
||||
int matrix = vs.GetAPI()->mapGetInt(props, "_Matrix", 0, &err2);
|
||||
colorspace = colormatrix_description(vi->format.colorFamily, err1 == 0 ? range : -1, err2 == 0 ? matrix : -1);
|
||||
|
||||
vs.GetAPI()->freeFrame(frame);
|
||||
|
||||
if (vi->format.colorFamily != cfRGB || vi->format.bitsPerSample != 8) {
|
||||
// Convert to RGB24 format
|
||||
VSPlugin *resize = vs.GetAPI()->getPluginByID(VSH_RESIZE_PLUGIN_ID, vs.GetScriptAPI()->getCore(script));
|
||||
if (resize == nullptr) {
|
||||
throw VapoursynthError("Couldn't find resize plugin");
|
||||
}
|
||||
VSMap *args = vs.GetAPI()->createMap();
|
||||
if (args == nullptr) {
|
||||
throw VapoursynthError("Failed to create argument map");
|
||||
}
|
||||
|
||||
vs.GetAPI()->mapSetNode(args, "clip", node, maAppend);
|
||||
vs.GetAPI()->mapSetInt(args, "format", pfRGB24, maAppend);
|
||||
if (vi->format.colorFamily != cfGray)
|
||||
SetResizeArg(args, props, "matrix_in", "_Matrix", VSC_MATRIX_BT709, VSC_MATRIX_UNSPECIFIED);
|
||||
SetResizeArg(args, props, "transfer_in", "_Transfer", VSC_TRANSFER_BT709, VSC_TRANSFER_UNSPECIFIED);
|
||||
SetResizeArg(args, props, "primaries_in", "_Primaries", VSC_PRIMARIES_BT709, VSC_PRIMARIES_UNSPECIFIED);
|
||||
SetResizeArg(args, props, "range_in", "_ColorRange", VSC_RANGE_LIMITED);
|
||||
SetResizeArg(args, props, "chromaloc_in", "_ChromaLocation", VSC_CHROMA_LEFT);
|
||||
|
||||
VSMap *result = vs.GetAPI()->invoke(resize, "Bicubic", args);
|
||||
vs.GetAPI()->freeMap(args);
|
||||
const char *error = vs.GetAPI()->mapGetError(result);
|
||||
if (error) {
|
||||
vs.GetAPI()->freeMap(result);
|
||||
vs.GetAPI()->freeNode(node);
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw VideoProviderError(agi::format("Failed to convert to RGB24: %s", error));
|
||||
}
|
||||
int err;
|
||||
vs.GetAPI()->freeNode(node);
|
||||
node = vs.GetAPI()->mapGetNode(result, "clip", 0, &err);
|
||||
vs.GetAPI()->freeMap(result);
|
||||
if (err) {
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw VideoProviderError("Failed to get resize output node");
|
||||
}
|
||||
|
||||
// Finally, try to get the first frame again, so if the filter does crash, it happens before loading finishes
|
||||
const VSFrame *rgbframe;
|
||||
try {
|
||||
rgbframe = GetVSFrame(0);
|
||||
} catch (VapoursynthError const& err) {
|
||||
vs.GetAPI()->freeNode(node);
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
throw err;
|
||||
}
|
||||
vs.GetAPI()->freeFrame(rgbframe);
|
||||
}
|
||||
}
|
||||
catch (VapoursynthError const& err) {
|
||||
throw VideoProviderError(agi::format("Vapoursynth error: %s", err.GetMessage()));
|
||||
}
|
||||
|
||||
const VSFrame *VapoursynthVideoProvider::GetVSFrame(int n) {
|
||||
char errorMsg[1024];
|
||||
const VSFrame *frame = vs.GetAPI()->getFrame(n, node, errorMsg, sizeof(errorMsg));
|
||||
if (frame == nullptr) {
|
||||
throw VapoursynthError(agi::format("Error getting frame: %s", errorMsg));
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
void VapoursynthVideoProvider::GetFrame(int n, VideoFrame &out) {
|
||||
std::lock_guard<std::mutex> lock(vs.GetMutex());
|
||||
|
||||
const VSFrame *frame = GetVSFrame(n);
|
||||
|
||||
const VSVideoFormat *format = vs.GetAPI()->getVideoFrameFormat(frame);
|
||||
if (format->colorFamily != cfRGB || format->numPlanes != 3 || format->bitsPerSample != 8 || format->subSamplingH != 0 || format->subSamplingW != 0) {
|
||||
throw VapoursynthError("Frame not in RGB24 format");
|
||||
}
|
||||
|
||||
out.width = vs.GetAPI()->getFrameWidth(frame, 0);
|
||||
out.height = vs.GetAPI()->getFrameHeight(frame, 0);
|
||||
out.pitch = out.width * 4;
|
||||
out.flipped = false;
|
||||
|
||||
out.data.resize(out.pitch * out.height);
|
||||
|
||||
for (int p = 0; p < format->numPlanes; p++) {
|
||||
ptrdiff_t stride = vs.GetAPI()->getStride(frame, p);
|
||||
const uint8_t *readPtr = vs.GetAPI()->getReadPtr(frame, p);
|
||||
uint8_t *writePtr = &out.data[2 - p];
|
||||
int rows = vs.GetAPI()->getFrameHeight(frame, p);
|
||||
int cols = vs.GetAPI()->getFrameWidth(frame, p);
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
const uint8_t *rowPtr = readPtr;
|
||||
uint8_t *rowWritePtr = writePtr;
|
||||
for (int col = 0; col < cols; col++) {
|
||||
*rowWritePtr = *rowPtr++;
|
||||
rowWritePtr += 4;
|
||||
}
|
||||
readPtr += stride;
|
||||
writePtr += out.pitch;
|
||||
}
|
||||
}
|
||||
|
||||
vs.GetAPI()->freeFrame(frame);
|
||||
}
|
||||
|
||||
VapoursynthVideoProvider::~VapoursynthVideoProvider() {
|
||||
if (node != nullptr) {
|
||||
vs.GetAPI()->freeNode(node);
|
||||
}
|
||||
if (script != nullptr) {
|
||||
vs.GetScriptAPI()->freeScript(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace agi { class BackgroundRunner; }
|
||||
std::unique_ptr<VideoProvider> CreateVapoursynthVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *) {
|
||||
return agi::make_unique<VapoursynthVideoProvider>(path, colormatrix);
|
||||
}
|
||||
#endif // HAVE_VAPOURSYNTH
|
3
subprojects/packagefiles/vapoursynth/meson.build
Normal file
3
subprojects/packagefiles/vapoursynth/meson.build
Normal file
|
@ -0,0 +1,3 @@
|
|||
project('vapoursynth', 'cpp')
|
||||
|
||||
vs_inc = include_directories('include')
|
4
subprojects/vapoursynth.wrap
Normal file
4
subprojects/vapoursynth.wrap
Normal file
|
@ -0,0 +1,4 @@
|
|||
[wrap-git]
|
||||
url = https://github.com/Vapoursynth/vapoursynth.git
|
||||
revision = R59
|
||||
patch_directory = vapoursynth
|
Loading…
Reference in a new issue