Merge branches 'bugfixes' and 'vapoursynth' into feature

This commit is contained in:
arch1t3cht 2023-03-24 20:31:07 +01:00
commit 1de8d04a43
18 changed files with 222 additions and 88 deletions

View file

@ -136,6 +136,7 @@ jobs:
with:
name: ${{ matrix.config.name }} - installer
path: build/Aegisub-*.exe
if-no-files-found: error
- name: Upload artifacts - portable.zip
uses: actions/upload-artifact@v3
@ -157,3 +158,4 @@ jobs:
with:
name: ${{ matrix.config.name }} - installer
path: build/Aegisub-*.dmg
if-no-files-found: error

View file

@ -1,14 +1,14 @@
"""
Utility functions for loading video files into Aegisub using the Vapoursynth
Utility functions for loading video files into Aegisub using the VapourSynth
video provider.
When encountering a file whose file extension is not .py or .vpy, the
Vapoursynth audio and video providers will execute the respective default
VapourSynth audio and video providers will execute the respective default
script set in Aegisub's configuration, with the following string variables set:
- filename: The path to the file that's being opened.
- __aegi_data, __aegi_dictionary, __aegi_local, __aegi_script, __aegi_temp, __aegi_user:
The values of ?data, ?dictionary, etc. respectively.
- __aegi_vscache: The path to a directory where the Vapoursynth script can
- __aegi_vscache: The path to a directory where the VapourSynth script can
store cache files. This directory is cleaned by Aegisub when it gets too
large (as defined by Aegisub's configuration).
@ -34,6 +34,43 @@ from typing import Any, Dict, List, Tuple
import vapoursynth as vs
core = vs.core
aegi_vscache: str = ""
aegi_vsplugins: str = ""
plugin_extension = ".dll" if os.name == "nt" else ".so"
def set_paths(vars: dict):
"""
Initialize the wrapper library with the given configuration directories.
Should usually be called at the start of the default script as
set_paths(globals())
"""
global aegi_vscache
global aegi_vsplugins
aegi_vscache = vars["__aegi_vscache"]
aegi_vsplugins = vars["__aegi_vsplugins"]
def ensure_plugin(name: str, loadname: str, errormsg: str):
"""
Ensures that the VapourSynth plugin with the given name exists.
If it doesn't, it tries to load it from `loadname`.
If that fails, it raises an error with the given error message.
"""
if hasattr(core, name):
return
if aegi_vsplugins and loadname:
try:
core.std.LoadPlugin(os.path.join(aegi_vsplugins, loadname + plugin_extension))
if hasattr(core, name):
return
except vs.Error:
pass
raise vs.Error(errormsg)
def make_lwi_cache_filename(filename: str) -> str:
"""
Given a path to a video, will return a file name like the one LWLibavSource
@ -103,21 +140,23 @@ def info_from_lwindex(indexfile: str) -> Dict[str, List[int]]:
}
def wrap_lwlibavsource(filename: str, cachedir: str, **kwargs: Any) -> Tuple[vs.VideoNode, Dict[str, List[int]]]:
def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any) -> Tuple[vs.VideoNode, Dict[str, List[int]]]:
"""
Given a path to a video file and a directory to store index files in
(usually __aegi_vscache), will open the video with LWLibavSource and read
the generated .lwi file to obtain the timecodes and keyframes.
Additional keyword arguments are passed on to LWLibavSource.
"""
if cachedir is None:
cachedir = aegi_vscache
try:
os.mkdir(cachedir)
except FileExistsError:
pass
cachefile = os.path.join(cachedir, make_lwi_cache_filename(filename))
if not hasattr(core, "lsmas"):
raise vs.Error("To use Aegisub's LWLibavSource wrapper, the `lsmas` plugin for VapourSynth must be installed")
ensure_plugin("lsmas", "libvslsmashsource", "To use Aegisub's LWLibavSource wrapper, the `lsmas` plugin for VapourSynth must be installed")
if b"-Dcachedir" not in core.lsmas.Version()["config"]: # type: ignore
raise vs.Error("To use Aegisub's LWLibavSource wrapper, the `lsmas` plugin must support the `cachedir` option for LWLibavSource.")
@ -128,7 +167,7 @@ def wrap_lwlibavsource(filename: str, cachedir: str, **kwargs: Any) -> Tuple[vs.
def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
resize_h: int = 360, resize_format: int = vs.YUV420P8,
resize_h: int = 360, resize_format: int = vs.GRAY8,
**kwargs: Any) -> List[int]:
"""
Generates a list of keyframes from a clip, using either WWXD or Scxvid.
@ -142,12 +181,14 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
The remaining keyword arguments are passed on to the respective filter.
"""
clip = core.resize.Bilinear(clip, width=resize_h * clip.width // clip.height, height=resize_h, format=resize_format);
try:
clip = core.scxvid.Scxvid(clip, **kwargs) if use_scxvid else core.wwxd.WWXD(clip, **kwargs)
except AttributeError:
raise vs.Error("To use the keyframe generation, the `{}` plugin for VapourSynth must be installed"
.format("scxvid" if use_scxvid else "wwxd"))
clip = core.resize.Bilinear(clip, width=resize_h * clip.width // clip.height, height=resize_h, format=resize_format)
if use_scxvid:
ensure_plugin("scxvid", "libscxvid", "To use the keyframe generation, the scxvid plugin for VapourSynth must be installed")
clip = core.scxvid.Scxvid(clip, **kwargs)
else:
ensure_plugin("wwxd", "libwwxd64", "To use the keyframe generation, the wwxdplugin for VapourSynth must be installed")
clip = core.wwxd.WWXD(clip, **kwargs)
keyframes = {}
done = 0
@ -175,6 +216,16 @@ def save_keyframes(filename: str, keyframes: List[int]):
f.write("".join(f"{n}\n" for n in keyframes))
def try_get_keyframes(filename: str, default: str | List[int]) -> str | List[int]:
"""
Checks if a keyframes file for the given filename is present and, if so,
returns it. Otherwise, returns the given list of keyframes.
"""
kffilename = make_keyframes_filename(filename)
return kffilename if os.path.exists(kffilename) else default
def get_keyframes(filename: str, clip: vs.VideoNode, **kwargs: Any) -> str:
"""
When not already present, creates a keyframe file for the given clip next
@ -199,6 +250,7 @@ def check_audio(filename: str, **kwargs: Any) -> bool:
Additional keyword arguments are passed on to BestAudioSource.
"""
try:
ensure_plugin("bas", "BestAudioSource", "")
vs.core.bas.Source(source=filename, **kwargs)
return True
except AttributeError:

View file

@ -5,3 +5,8 @@ DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\AviSynth.dll; Flags: igno
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\plugins\DirectShowSource.dll; Flags: ignoreversion; Components: main
; VSFilter
DestDir: {app}\csri; Source: {#DEPS_DIR}\VSFilter\x64\VSFilter.dll; Flags: ignoreversion; Components: main
; VapourSynth
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\L-SMASH-Works\libvslsmashsource.dll; Flags: ignoreversion; Components: vapoursynth
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\bestaudiosource\win64\BestAudioSource.dll; Flags: ignoreversion; Components: vapoursynth
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\SCXVid\libscxvid.dll; Flags: ignoreversion; Components: vapoursynth
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\WWXD\libwwxd64.dll; Flags: ignoreversion; Components: vapoursynth

View file

@ -1,5 +1,6 @@
[Components]
Name: "main"; Description: "Main Files"; Types: full compact custom; Flags: fixed
Name: "vapoursynth"; Description: "Bundled VapourSynth Plugins"; Types: full
Name: "macros"; Description: "Automation Scripts"; Types: full
Name: "macros\bundled"; Description: "Bundled macros"; Types: full
Name: "macros\demos"; Description: "Example macros/Demos"; Types: full

View file

@ -1,5 +1,5 @@
; This file declares all installables related to spell checking and thesaurii in Aegisub
[Files]
Source: {#DEPS_DIR}\dictionaries\en_US.aff; DestDir: {app}\dictionaries; Flags: skipifsourcedoesntexist ignoreversion
Source: {#DEPS_DIR}\dictionaries\en_US.dic; DestDir: {app}\dictionaries; Flags: skipifsourcedoesntexist ignoreversion
Source: {#DEPS_DIR}\dictionaries\en_US.aff; DestDir: {app}\dictionaries; Flags: ignoreversion; Components: dictionaries/en_US
Source: {#DEPS_DIR}\dictionaries\en_US.dic; DestDir: {app}\dictionaries; Flags: ignoreversion; Components: dictionaries/en_US

View file

@ -51,11 +51,19 @@ Copy-New-Item $InstallerDir\bin\aegisub.exe $PortableOutputDir
Write-Output 'Copying - translations'
Copy-New-Items "$InstallerDir\share\locale\*" "$PortableOutputDir\locale" -Recurse
Write-Output 'Copying - dictionaries'
Copy-New-Item $InstallerDepsDir\dictionaries\en_US.aff $PortableOutputDir\dictionaries
Copy-New-Item $InstallerDepsDir\dictionaries\en_US.dic $PortableOutputDir\dictionaries
Write-Output 'Copying - codecs'
Write-Output 'Copying - codecs\Avisynth'
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\system\DevIL.dll $PortableOutputDir
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\AviSynth.dll $PortableOutputDir
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\plugins\DirectShowSource.dll $PortableOutputDir
Write-Output 'Copying - codecs\VapourSynth'
Copy-New-Item $InstallerDepsDir\L-SMASH-Works\libvslsmashsource.dll $PortableOutputDir\vapoursynth
Copy-New-Item $InstallerDepsDir\bestaudiosource\win64\BestAudioSource.dll $PortableOutputDir\vapoursynth
Copy-New-Item $InstallerDepsDir\SCXVid\libscxvid.dll $PortableOutputDir\vapoursynth
Copy-New-Item $InstallerDepsDir\WWXD\libwwxd64.dll $PortableOutputDir\vapoursynth
Write-Output 'Copying - codecs\VSFilter'
Copy-New-Item $InstallerDepsDir\VSFilter\x64\VSFilter.dll $PortableOutputDir\csri
Write-Output 'Copying - runtimes\MS-CRT'

View file

@ -32,7 +32,7 @@ using namespace agi;
std::unique_ptr<AudioProvider> CreateAvisynthAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateBSAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateVapoursynthAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateVapourSynthAudioProvider(fs::path const& filename, BackgroundRunner *);
namespace {
struct factory {
@ -54,7 +54,7 @@ const factory providers[] = {
{"BestSource", CreateBSAudioProvider, false},
#endif
#ifdef WITH_VAPOURSYNTH
{"Vapoursynth", CreateVapoursynthAudioProvider, false},
{"VapourSynth", CreateVapourSynthAudioProvider, false},
#endif
};
}

View file

@ -15,7 +15,7 @@
// Aegisub Project http://www.aegisub.org/
/// @file audio_provider_vs.cpp
/// @brief Vapoursynth-based audio provider
/// @brief VapourSynth-based audio provider
/// @ingroup audio_input
///
@ -38,7 +38,7 @@
#include "VSScript4.h"
namespace {
class VapoursynthAudioProvider final : public agi::AudioProvider {
class VapourSynthAudioProvider final : public agi::AudioProvider {
VapourSynthWrapper vs;
VSScript *script = nullptr;
VSNode *node = nullptr;
@ -47,36 +47,36 @@ class VapoursynthAudioProvider final : public agi::AudioProvider {
void FillBufferWithFrame(void *buf, int frame, int64_t start, int64_t count) const;
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
public:
VapoursynthAudioProvider(agi::fs::path const& filename);
~VapoursynthAudioProvider();
VapourSynthAudioProvider(agi::fs::path const& filename);
~VapourSynthAudioProvider();
bool NeedsCache() const override { return true; }
};
VapoursynthAudioProvider::VapoursynthAudioProvider(agi::fs::path const& filename) try {
VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename) try {
std::lock_guard<std::mutex> lock(vs.GetMutex());
VSCleanCache();
script = vs.GetScriptAPI()->createScript(nullptr);
if (script == nullptr) {
throw VapoursynthError("Error creating script API");
throw VapourSynthError("Error creating script API");
}
vs.GetScriptAPI()->evalSetWorkingDir(script, 1);
if (OpenScriptOrVideo(vs.GetAPI(), vs.GetScriptAPI(), script, filename, OPT_GET("Provider/Audio/VapourSynth/Default Script")->GetString())) {
std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script));
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError(msg);
throw VapourSynthError(msg);
}
node = vs.GetScriptAPI()->getOutputNode(script, 0);
if (node == nullptr) {
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("No output node set");
throw VapourSynthError("No output node set");
}
if (vs.GetAPI()->getNodeType(node) != mtAudio) {
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("Output node isn't an audio node");
throw VapourSynthError("Output node isn't an audio node");
}
vi = vs.GetAPI()->getAudioInfo(node);
float_samples = vi->format.sampleType == stFloat;
@ -85,10 +85,10 @@ VapoursynthAudioProvider::VapoursynthAudioProvider(agi::fs::path const& filename
channels = vi->format.numChannels;
num_samples = vi->numSamples;
}
catch (VapoursynthError const& err) {
catch (VapourSynthError const& err) {
// Unlike the video provider manager, the audio provider factory catches AudioProviderErrors and picks whichever source doesn't throw one.
// So just rethrow the Error here with an extra label so the user will see the error message and know the audio wasn't loaded with VS
throw VapoursynthError(agi::format("Vapoursynth error: %s", err.GetMessage()));
throw VapourSynthError(agi::format("VapourSynth error: %s", err.GetMessage()));
}
template<typename T>
@ -102,19 +102,19 @@ static void PackChannels(const uint8_t **Src, void *Dst, size_t Length, size_t C
}
}
void VapoursynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t start, int64_t count) const {
void VapourSynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t start, int64_t count) const {
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));
throw VapourSynthError(agi::format("Error getting frame: %s", errorMsg));
}
if (vs.GetAPI()->getFrameLength(frame) < count) {
vs.GetAPI()->freeFrame(frame);
throw VapoursynthError("Audio frame too short");
throw VapourSynthError("Audio frame too short");
}
if (vs.GetAPI()->getAudioFrameFormat(frame)->numChannels != channels || vs.GetAPI()->getAudioFrameFormat(frame)->bytesPerSample != bytes_per_sample) {
vs.GetAPI()->freeFrame(frame);
throw VapoursynthError("Audio format is not constant");
throw VapourSynthError("Audio format is not constant");
}
std::vector<const uint8_t *> planes(channels);
@ -122,7 +122,7 @@ void VapoursynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t sta
planes[c] = vs.GetAPI()->getReadPtr(frame, c) + bytes_per_sample * start;
if (planes[c] == nullptr) {
vs.GetAPI()->freeFrame(frame);
throw VapoursynthError("Failed to read audio channel");
throw VapourSynthError("Failed to read audio channel");
}
}
@ -138,7 +138,7 @@ void VapoursynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t sta
vs.GetAPI()->freeFrame(frame);
}
void VapoursynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
void VapourSynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
int end = start + count; // exclusive
int startframe = start / VS_AUDIO_FRAME_SAMPLES;
int endframe = (end - 1) / VS_AUDIO_FRAME_SAMPLES;
@ -154,7 +154,7 @@ void VapoursynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t coun
}
}
VapoursynthAudioProvider::~VapoursynthAudioProvider() {
VapourSynthAudioProvider::~VapourSynthAudioProvider() {
if (node != nullptr) {
vs.GetAPI()->freeNode(node);
}
@ -164,7 +164,7 @@ VapoursynthAudioProvider::~VapoursynthAudioProvider() {
}
}
std::unique_ptr<agi::AudioProvider> CreateVapoursynthAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) {
return agi::make_unique<VapoursynthAudioProvider>(file);
std::unique_ptr<agi::AudioProvider> CreateVapourSynthAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) {
return agi::make_unique<VapourSynthAudioProvider>(file);
}
#endif

View file

@ -351,7 +351,7 @@
"Aegisub Cache" : true
},
"VapourSynth" : {
"Default Script" : "# This default script will load an audio file using BestAudioSource.\n# It requires the `bas` plugin.\n\nimport vapoursynth as vs\ntry:\n vs.core.bas.Source(source=filename).set_output()\nexcept AttributeError:\n raise vs.Error(\"To use Aegisub's default audio loader, the `bas` plugin for VapourSynth must be installed\")"
"Default Script" : "# This default script will load an audio file using BestAudioSource.\n# It requires the `bas` plugin.\n\nimport vapoursynth as vs\nimport aegisub_vs as a\na.set_paths(locals())\n\na.ensure_plugin(\"bas\", \"BestAudioSource\", \"To use Aegisub's default audio loader, the `bas` plugin for VapourSynth must be installed\")\nvs.core.bas.Source(source=filename).set_output()"
}
},
"Avisynth" : {
@ -392,7 +392,7 @@
},
"VapourSynth" : {
"Log Level": "Information",
"Default Script" : "# This default script will load a video file using LWLibavSource.\n# It requires the `lsmas` plugin.\n# See ?data/automation/vapoursynth/aegisub_vs.py for more information.\n\nimport aegisub_vs as a\nimport vapoursynth as vs\n\nclip, videoinfo = a.wrap_lwlibavsource(filename, __aegi_vscache)\nclip.set_output()\n__aegi_timecodes = videoinfo[\"timecodes\"]\n__aegi_keyframes = videoinfo[\"keyframes\"]\n# Uncomment this to automatically generate keyframes at scene changes.\n#__aegi_keyframes = a.get_keyframes(filename, clip)\n\n# Check if the file has an audio track. This requires the `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
"Default Script" : "# This default script will load a video file using LWLibavSource.\n# It requires the `lsmas` plugin.\n# See ?data/automation/vapoursynth/aegisub_vs.py for more information.\n\nimport vapoursynth as vs\nimport time\nimport aegisub_vs as a\na.set_paths(locals())\n\nclip, videoinfo = a.wrap_lwlibavsource(filename)\nclip.set_output()\n__aegi_timecodes = videoinfo[\"timecodes\"]\n__aegi_keyframes = videoinfo[\"keyframes\"]\n\n# Uncomment the first following line to read keyframes from a file when present.\n#__aegi_keyframes = a.try_get_keyframes(filename, __aegi_keyframes)\n\n# Uncomment the following line to automatically generate keyframes at scene changes. This will take some time when first loading a video.\n__aegi_keyframes = a.get_keyframes(filename, clip)\n\n# Check if the file has an audio track. This requires the `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
}
}
},

View file

@ -351,7 +351,7 @@
"Aegisub Cache" : true
},
"VapourSynth" : {
"Default Script" : "# This default script will load an audio file using BestAudioSource.\n# It requires the `bas` plugin.\n\nimport vapoursynth as vs\ntry:\n vs.core.bas.Source(source=filename).set_output()\nexcept AttributeError:\n raise vs.Error(\"To use Aegisub's default audio loader, the `bas` plugin for VapourSynth must be installed\")"
"Default Script" : "# This default script will load an audio file using BestAudioSource.\n# It requires the `bas` plugin.\n\nimport vapoursynth as vs\nimport aegisub_vs as a\na.set_paths(globals())\n\na.ensure_plugin(\"bas\", \"BestAudioSource\", \"To use Aegisub's default audio loader, the `bas` plugin for VapourSynth must be installed\")\nvs.core.bas.Source(source=filename).set_output()"
}
},
"Avisynth" : {
@ -392,7 +392,7 @@
},
"VapourSynth" : {
"Log Level": "Information",
"Default Script" : "# This default script will load a video file using LWLibavSource.\n# It requires the `lsmas` plugin.\n# See ?data/automation/vapoursynth/aegisub_vs.py for more information.\n\nimport aegisub_vs as a\nimport vapoursynth as vs\n\nclip, videoinfo = a.wrap_lwlibavsource(filename, __aegi_vscache)\nclip.set_output()\n__aegi_timecodes = videoinfo[\"timecodes\"]\n__aegi_keyframes = videoinfo[\"keyframes\"]\n# Uncomment this to automatically generate keyframes at scene changes.\n#__aegi_keyframes = a.get_keyframes(filename, clip)\n\n# Check if the file has an audio track. This requires the `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
"Default Script" : "# This default script will load a video file using LWLibavSource.\n# It requires the `lsmas` plugin.\n# See ?data/automation/vapoursynth/aegisub_vs.py for more information.\n\nimport vapoursynth as vs\nimport time\nimport aegisub_vs as a\na.set_paths(locals())\n\nclip, videoinfo = a.wrap_lwlibavsource(filename)\nclip.set_output()\n__aegi_timecodes = videoinfo[\"timecodes\"]\n__aegi_keyframes = videoinfo[\"keyframes\"]\n\n# Uncomment the first following line to read keyframes from a file when present.\n#__aegi_keyframes = a.try_get_keyframes(filename, __aegi_keyframes)\n\n# Uncomment the following line to automatically generate keyframes at scene changes. This will take some time when first loading a video.\n__aegi_keyframes = a.get_keyframes(filename, clip)\n\n# Check if the file has an audio track. This requires the `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
}
}
},

View file

@ -28,7 +28,7 @@
void SetStringVar(const VSAPI *api, VSMap *map, std::string variable, std::string value) {
if (api->mapSetData(map, variable.c_str(), value.c_str(), -1, dtUtf8, 1))
throw VapoursynthError("Failed to set VSMap entry");
throw VapourSynthError("Failed to set VSMap entry");
}
int OpenScriptOrVideo(const VSAPI *api, const VSSCRIPTAPI *sapi, VSScript *script, agi::fs::path const& filename, std::string default_script) {
@ -38,16 +38,21 @@ int OpenScriptOrVideo(const VSAPI *api, const VSSCRIPTAPI *sapi, VSScript *scrip
} else {
VSMap *map = api->createMap();
if (map == nullptr)
throw VapoursynthError("Failed to create VSMap for script info");
throw VapourSynthError("Failed to create VSMap for script info");
SetStringVar(api, map, "filename", filename.string());
SetStringVar(api, map, "__aegi_vscache", config::path->Decode("?local/vscache").string());
#ifdef WIN32
SetStringVar(api, map, "__aegi_vsplugins", config::path->Decode("?data/vapoursynth").string());
#else
SetStringVar(api, map, "__aegi_vsplugins", "");
#endif
for (std::string dir : { "data", "dictionary", "local", "script", "temp", "user", })
// Don't include ?audio and ?video in here since these only hold the paths to the previous audio/video files.
SetStringVar(api, map, "__aegi_" + dir, config::path->Decode("?" + dir).string());
if (sapi->setVariables(script, map))
throw VapoursynthError("Failed to set script info variables");
throw VapourSynthError("Failed to set script info variables");
api->freeMap(map);

View file

@ -15,7 +15,7 @@
// Aegisub Project http://www.aegisub.org/
/// @file vapoursynth_wrap.cpp
/// @brief Wrapper-layer for Vapoursynth
/// @brief Wrapper-layer for VapourSynth
/// @ingroup video_input audio_input
///
@ -67,7 +67,7 @@ VapourSynthWrapper::VapourSynthWrapper() {
#endif
if (!hLib)
throw VapoursynthError("Could not load " VSSCRIPT_SO);
throw VapourSynthError("Could not load " VSSCRIPT_SO);
#ifdef _WIN32
FUNC* getVSScriptAPI = (FUNC*)GetProcAddress(hLib, "getVSScriptAPI");
@ -75,7 +75,7 @@ VapourSynthWrapper::VapourSynthWrapper() {
FUNC* getVSScriptAPI = (FUNC*)dlsym(hLib, "getVSScriptAPI");
#endif
if (!getVSScriptAPI)
throw VapoursynthError("Failed to get address of getVSScriptAPI from " VSSCRIPT_SO);
throw VapourSynthError("Failed to get address of getVSScriptAPI from " VSSCRIPT_SO);
// Python will set the program's locale to the user's default locale, which will break
// half of wxwidgets on some operating systems due to locale mismatches. There's not really anything
@ -85,12 +85,12 @@ VapourSynthWrapper::VapourSynthWrapper() {
setlocale(LC_ALL, oldlocale.c_str());
if (!scriptapi)
throw VapoursynthError("Failed to get Vapoursynth ScriptAPI");
throw VapourSynthError("Failed to get VapourSynth ScriptAPI");
api = scriptapi->getVSAPI(VAPOURSYNTH_API_VERSION);
if (!api)
throw VapoursynthError("Failed to get Vapoursynth API");
throw VapourSynthError("Failed to get VapourSynth API");
vs_loaded = true;
}

View file

@ -23,7 +23,7 @@
#include <libaegisub/exception.h>
DEFINE_EXCEPTION(VapoursynthError, agi::Exception);
DEFINE_EXCEPTION(VapourSynthError, agi::Exception);
struct VSAPI;
struct VSSCRIPTAPI;

View file

@ -30,7 +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> CreateVapourSynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
std::unique_ptr<VideoProvider> CreateCacheVideoProvider(std::unique_ptr<VideoProvider>);
@ -54,7 +54,7 @@ namespace {
{"BestSource (SLOW)", CreateBSVideoProvider, false},
#endif
#ifdef WITH_VAPOURSYNTH
{"Vapoursynth", CreateVapoursynthVideoProvider, false},
{"VapourSynth", CreateVapourSynthVideoProvider, false},
#endif
};
}

View file

@ -42,7 +42,7 @@ static const char *tc_key = "__aegi_timecodes";
static const char *audio_key = "__aegi_hasaudio";
namespace {
class VapoursynthVideoProvider: public VideoProvider {
class VapourSynthVideoProvider: public VideoProvider {
VapourSynthWrapper vs;
VSScript *script = nullptr;
VSNode *node = nullptr;
@ -59,8 +59,8 @@ class VapoursynthVideoProvider: public VideoProvider {
void SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int64_t deflt, int64_t unspecified = -1);
public:
VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br);
~VapoursynthVideoProvider();
VapourSynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br);
~VapourSynthVideoProvider();
void GetFrame(int n, VideoFrame &frame) override;
@ -105,7 +105,7 @@ std::string colormatrix_description(int colorFamily, int colorRange, int matrix)
}
// 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, int64_t deflt, int64_t unspecified) {
void VapourSynthVideoProvider::SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int64_t deflt, int64_t unspecified) {
int err;
int64_t result = vs.GetAPI()->mapGetInt(props, prop_name, 0, &err);
if (err != 0 || result == unspecified) {
@ -117,7 +117,7 @@ void VapoursynthVideoProvider::SetResizeArg(VSMap *args, const VSMap *props, con
}
}
VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try { try {
VapourSynthVideoProvider::VapourSynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try { try {
std::lock_guard<std::mutex> lock(vs.GetMutex());
VSCleanCache();
@ -125,16 +125,16 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
int err1, err2;
VSCore *core = vs.GetAPI()->createCore(0);
if (core == nullptr) {
throw VapoursynthError("Error creating core");
throw VapourSynthError("Error creating core");
}
script = vs.GetScriptAPI()->createScript(core);
if (script == nullptr) {
vs.GetAPI()->freeCore(core);
throw VapoursynthError("Error creating script API");
throw VapourSynthError("Error creating script API");
}
vs.GetScriptAPI()->evalSetWorkingDir(script, 1);
br->Run([&](agi::ProgressSink *ps) {
ps->SetTitle(from_wx(_("Executing Vapoursynth Script")));
ps->SetTitle(from_wx(_("Executing VapourSynth Script")));
ps->SetMessage("");
ps->SetIndeterminate();
@ -148,20 +148,20 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
});
if (err1) {
std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script));
throw VapoursynthError(msg);
throw VapourSynthError(msg);
}
node = vs.GetScriptAPI()->getOutputNode(script, 0);
if (node == nullptr)
throw VapoursynthError("No output node set");
throw VapourSynthError("No output node set");
if (vs.GetAPI()->getNodeType(node) != mtVideo) {
throw VapoursynthError("Output node isn't a video node");
throw VapourSynthError("Output node isn't a video node");
}
vi = vs.GetAPI()->getVideoInfo(node);
if (vi == nullptr)
throw VapoursynthError("Couldn't get video info");
throw VapourSynthError("Couldn't get video info");
if (!vsh::isConstantVideoFormat(vi))
throw VapoursynthError("Video doesn't have constant format");
throw VapourSynthError("Video doesn't have constant format");
int fpsNum = vi->fpsNum;
int fpsDen = vi->fpsDen;
@ -174,7 +174,7 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
// Get timecodes and/or keyframes if provided
VSMap *clipinfo = vs.GetAPI()->createMap();
if (clipinfo == nullptr)
throw VapoursynthError("Couldn't create map");
throw VapourSynthError("Couldn't create map");
vs.GetScriptAPI()->getVariable(script, kf_key, clipinfo);
vs.GetScriptAPI()->getVariable(script, tc_key, clipinfo);
vs.GetScriptAPI()->getVariable(script, audio_key, clipinfo);
@ -190,7 +190,7 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
const int64_t *kfs = vs.GetAPI()->mapGetIntArray(clipinfo, kf_key, &err1);
const char *kfs_path = vs.GetAPI()->mapGetData(clipinfo, kf_key, 0, &err2);
if (err1 && err2)
throw VapoursynthError("Error getting keyframes from returned VSMap");
throw VapourSynthError("Error getting keyframes from returned VSMap");
if (!err1) {
keyframes.reserve(numkf);
@ -199,7 +199,7 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
} else {
int kfs_path_size = vs.GetAPI()->mapGetDataSize(clipinfo, kf_key, 0, &err1);
if (err1)
throw VapoursynthError("Error getting size of keyframes path");
throw VapourSynthError("Error getting size of keyframes path");
try {
keyframes = agi::keyframe::Load(config::path->Decode(std::string(kfs_path, size_t(kfs_path_size))));
@ -213,11 +213,11 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
const int64_t *tcs = vs.GetAPI()->mapGetIntArray(clipinfo, tc_key, &err1);
const char *tcs_path = vs.GetAPI()->mapGetData(clipinfo, tc_key, 0, &err2);
if (err1 && err2)
throw VapoursynthError("Error getting timecodes from returned map");
throw VapourSynthError("Error getting timecodes from returned map");
if (!err1) {
if (numtc != vi->numFrames)
throw VapoursynthError("Number of returned timecodes does not match number of frames");
throw VapourSynthError("Number of returned timecodes does not match number of frames");
std::vector<int> timecodes;
timecodes.reserve(numtc);
@ -228,13 +228,13 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
} else {
int tcs_path_size = vs.GetAPI()->mapGetDataSize(clipinfo, tc_key, 0, &err1);
if (err1)
throw VapoursynthError("Error getting size of keyframes path");
throw VapourSynthError("Error getting size of keyframes path");
try {
fps = agi::vfr::Framerate(config::path->Decode(std::string(tcs_path, size_t(tcs_path_size))));
} catch (agi::Exception const& e) {
// Throw an error here unlike with keyframes since the timecodes not being loaded might not be immediately noticeable
throw VapoursynthError("Failed to open timecodes file specified by script: " + e.GetMessage());
throw VapourSynthError("Failed to open timecodes file specified by script: " + e.GetMessage());
}
}
}
@ -245,7 +245,7 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
const VSMap *props = vs.GetAPI()->getFramePropertiesRO(frame);
if (props == nullptr)
throw VapoursynthError("Couldn't get frame properties");
throw VapourSynthError("Couldn't get frame properties");
int64_t sarn = vs.GetAPI()->mapGetInt(props, "_SARNum", 0, &err1);
int64_t sard = vs.GetAPI()->mapGetInt(props, "_SARDen", 0, &err2);
if (!err1 && !err2) {
@ -262,11 +262,11 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
// 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");
throw VapourSynthError("Couldn't find resize plugin");
VSMap *args = vs.GetAPI()->createMap();
if (args == nullptr)
throw VapoursynthError("Failed to create argument map");
throw VapourSynthError("Failed to create argument map");
vs.GetAPI()->mapSetNode(args, "clip", node, maAppend);
vs.GetAPI()->mapSetInt(args, "format", pfRGB24, maAppend);
@ -299,7 +299,7 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
const VSFrame *rgbframe = GetVSFrame(0);
vs.GetAPI()->freeFrame(rgbframe);
}
} catch (VapoursynthError const& err) { // for try inside of function. We need both here since we need to catch errors from the VapoursynthWrap constructor.
} catch (VapourSynthError const& err) { // for try inside of function. We need both here since we need to catch errors from the VapourSynthWrap constructor.
if (node != nullptr)
vs.GetAPI()->freeNode(node);
if (script != nullptr)
@ -307,27 +307,27 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
throw err;
}
}
catch (VapoursynthError const& err) { // for the entire constructor
throw VideoProviderError(agi::format("Vapoursynth error: %s", err.GetMessage()));
catch (VapourSynthError const& err) { // for the entire constructor
throw VideoProviderError(agi::format("VapourSynth error: %s", err.GetMessage()));
}
const VSFrame *VapoursynthVideoProvider::GetVSFrame(int n) {
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));
throw VapourSynthError(agi::format("Error getting frame: %s", errorMsg));
}
return frame;
}
void VapoursynthVideoProvider::GetFrame(int n, VideoFrame &out) {
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");
throw VapourSynthError("Frame not in RGB24 format");
}
out.width = vs.GetAPI()->getFrameWidth(frame, 0);
@ -359,7 +359,7 @@ void VapoursynthVideoProvider::GetFrame(int n, VideoFrame &out) {
vs.GetAPI()->freeFrame(frame);
}
VapoursynthVideoProvider::~VapoursynthVideoProvider() {
VapourSynthVideoProvider::~VapourSynthVideoProvider() {
if (node != nullptr) {
vs.GetAPI()->freeNode(node);
}
@ -370,7 +370,7 @@ VapoursynthVideoProvider::~VapoursynthVideoProvider() {
}
namespace agi { class BackgroundRunner; }
std::unique_ptr<VideoProvider> CreateVapoursynthVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *br) {
return agi::make_unique<VapoursynthVideoProvider>(path, colormatrix, br);
std::unique_ptr<VideoProvider> CreateVapourSynthVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *br) {
return agi::make_unique<VapourSynthVideoProvider>(path, colormatrix, br);
}
#endif // WITH_VAPOURSYNTH

View file

@ -1,4 +1,4 @@
[wrap-git]
url = https://github.com/Vapoursynth/vapoursynth.git
url = https://github.com/vapoursynth/vapoursynth.git
revision = R59
patch_directory = vapoursynth

View file

@ -61,7 +61,12 @@ echo "---- Copying dictionaries ----"
if test -f "${DICT_DIR}"; then
cp -v "${DICT_DIR}/*" "${PKG_DIR}/Contents/SharedSupport/dictionaries"
else
echo "Specified dictionary directory ${DICT_DIR} not found!"
echo "Specified dictionary directory ${DICT_DIR} not found. Downloading dictionaries:"
mkdir "${BUILD_DIR}/dictionaries"
curl -L "https://downloads.sourceforge.net/project/openofficeorg.mirror/contrib/dictionaries/en_US.zip" -o "${BUILD_DIR}/dictionaries/en_US.zip"
unzip "${BUILD_DIR}/dictionaries/en_US.zip" -d "${BUILD_DIR}/dictionaries"
cp -v "${BUILD_DIR}/dictionaries/en_US.aff" "${PKG_DIR}/Contents/SharedSupport/dictionaries"
cp -v "${BUILD_DIR}/dictionaries/en_US.dic" "${PKG_DIR}/Contents/SharedSupport/dictionaries"
fi
echo

View file

@ -63,13 +63,60 @@ if (!(Test-Path VSFilter)) {
Set-Location $DepsDir
}
### VapourSynth plugins
# L-SMASH-Works
if (!(Test-Path L-SMASH-Works)) {
New-Item -ItemType Directory L-SMASH-Works
$lsmasReleases = Invoke-WebRequest "https://api.github.com/repos/AkarinVS/L-SMASH-Works/releases/latest" -Headers $GitHeaders -UseBasicParsing | ConvertFrom-Json
$lsmasUrl = "https://github.com/AkarinVS/L-SMASH-Works/releases/download/" + $lsmasReleases.tag_name + "/release-x86_64-cachedir-cwd.zip"
Invoke-WebRequest $lsmasUrl -OutFile release-x86_64-cachedir-cwd.zip -UseBasicParsing
Expand-Archive -LiteralPath release-x86_64-cachedir-cwd.zip -DestinationPath L-SMASH-Works
Remove-Item release-x86_64-cachedir-cwd.zip
}
# bestaudiosource
if (!(Test-Path bestaudiosource)) {
$basDir = New-Item -ItemType Directory bestaudiosource
Set-Location $basDir
$basReleases = Invoke-WebRequest "https://api.github.com/repos/vapoursynth/bestaudiosource/releases/latest" -Headers $GitHeaders -UseBasicParsing | ConvertFrom-Json
$basUrl = $basReleases.assets[0].browser_download_url
Invoke-WebRequest $basUrl -OutFile bas-r1.7z -UseBasicParsing
7z x bas-r1.7z
Remove-Item bas-r1.7z
Set-Location $DepsDir
}
# SCXVid
if (!(Test-Path SCXVid)) {
$scxDir = New-Item -ItemType Directory SCXVid
Set-Location $scxDir
$scxReleases = Invoke-WebRequest "https://api.github.com/repos/dubhater/vapoursynth-scxvid/releases/latest" -Headers $GitHeaders -UseBasicParsing | ConvertFrom-Json
$scxUrl = "https://github.com/dubhater/vapoursynth-scxvid/releases/download/" + $scxReleases.tag_name + "/vapoursynth-scxvid-v1-win64.7z"
Invoke-WebRequest $scxUrl -OutFile vapoursynth-scxvid-v1-win64.7z -UseBasicParsing
7z x vapoursynth-scxvid-v1-win64.7z
Remove-Item vapoursynth-scxvid-v1-win64.7z
Set-Location $DepsDir
}
# WWXD
if (!(Test-Path WWXD)) {
New-Item -ItemType Directory WWXD
$wwxdReleases = Invoke-WebRequest "https://api.github.com/repos/dubhater/vapoursynth-wwxd/releases/latest" -Headers $GitHeaders -UseBasicParsing | ConvertFrom-Json
$wwxdUrl = "https://github.com/dubhater/vapoursynth-wwxd/releases/download/" + $wwxdReleases.tag_name + "/libwwxd64.dll"
Invoke-WebRequest $wwxdUrl -OutFile WWXD/libwwxd64.dll -UseBasicParsing
}
# ffi-experiments
if (!(Test-Path ffi-experiments)) {
Get-Command "moonc" # check to ensure Moonscript is present
git clone https://github.com/arch1t3cht/ffi-experiments.git
Set-Location ffi-experiments
meson build -Ddefault_library=static
if(!$?) { Exit $LASTEXITCODE }
meson compile -C build
if(!$?) { Exit $LASTEXITCODE }
Set-Location $DepsDir
}
@ -79,12 +126,21 @@ if (!(Test-Path VC_redist)) {
Invoke-WebRequest https://aka.ms/vs/17/release/VC_redist.x64.exe -OutFile "$redistDir\VC_redist.x64.exe" -UseBasicParsing
}
# TODO dictionaries
# dictionaries
if (!(Test-Path dictionaries)) {
New-Item -ItemType Directory dictionaries
[Net.ServicePointManager]::SecurityProtocol = "Tls12" # Needed since otherwise downloading fails in some places like on the GitHub CI: https://stackoverflow.com/a/66614041/4730656
Invoke-WebRequest https://downloads.sourceforge.net/project/openofficeorg.mirror/contrib/dictionaries/en_US.zip -UserAgent "Wget" -OutFile en_US.zip -UseBasicParsing
Expand-Archive -LiteralPath en_US.zip -DestinationPath dictionaries
Remove-Item en_US.zip
}
# localization
Set-Location $BuildRoot
meson compile aegisub-gmo
if(!$?) { Exit $LASTEXITCODE }
# Invoke InnoSetup
$IssUrl = Join-Path $InstallerDir "aegisub_depctrl.iss"
iscc $IssUrl
if(!$?) { Exit $LASTEXITCODE }