Merge branches 'vapoursynth' and 'fixes' into feature

This commit is contained in:
arch1t3cht 2023-10-18 21:22:54 +02:00
commit 4a939d1954
12 changed files with 107 additions and 15 deletions

View File

@ -1,4 +1,5 @@
subdir('include') subdir('include')
subdir('vapoursynth')
automation_dir = dataroot / 'automation' automation_dir = dataroot / 'automation'

View File

@ -22,6 +22,9 @@ Aegisub using the following variables:
- __aegi_hasaudio: int: If nonzero, Aegisub will try to load an audio track - __aegi_hasaudio: int: If nonzero, Aegisub will try to load an audio track
from the same file. from the same file.
The script can control the progress dialog shown by Aegisub with certain log
messages. Check the functions defined below for more information.
This module provides some utility functions to obtain timecodes, keyframes, and This module provides some utility functions to obtain timecodes, keyframes, and
other data. other data.
""" """
@ -40,6 +43,28 @@ aegi_vsplugins: str = ""
plugin_extension = ".dll" if os.name == "nt" else ".so" plugin_extension = ".dll" if os.name == "nt" else ".so"
def progress_set_message(message: str):
"""
Sets the message of Aegisub's progress dialog.
"""
vs.core.log_message(vs.MESSAGE_TYPE_DEBUG, f"__aegi_set_message,{message}")
def progress_set_progress(percent: float):
"""
Sets the progress shown in Aegisub's progress dialog to
the given percentage.
"""
vs.core.log_message(vs.MESSAGE_TYPE_DEBUG, f"__aegi_set_progress,{percent}")
def progress_set_indeterminate():
"""
Sets Aegisub's progress dialog to show indeterminate progress.
"""
vs.core.log_message(vs.MESSAGE_TYPE_DEBUG, f"__aegi_set_indeterminate,")
def set_paths(vars: dict): def set_paths(vars: dict):
""" """
Initialize the wrapper library with the given configuration directories. Initialize the wrapper library with the given configuration directories.
@ -157,6 +182,9 @@ def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any
pass pass
cachefile = os.path.join(cachedir, make_lwi_cache_filename(filename)) cachefile = os.path.join(cachedir, make_lwi_cache_filename(filename))
progress_set_message("Loading video file")
progress_set_indeterminate()
ensure_plugin("lsmas", "libvslsmashsource", "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 if b"-Dcachedir" not in core.lsmas.Version()["config"]: # type: ignore
@ -164,6 +192,7 @@ def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any
clip = core.lsmas.LWLibavSource(source=filename, cachefile=cachefile, **kwargs) clip = core.lsmas.LWLibavSource(source=filename, cachefile=cachefile, **kwargs)
progress_set_message("Getting timecodes and keyframes from the index file")
return clip, info_from_lwindex(cachefile) return clip, info_from_lwindex(cachefile)
@ -172,7 +201,6 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
**kwargs: Any) -> List[int]: **kwargs: Any) -> List[int]:
""" """
Generates a list of keyframes from a clip, using either WWXD or Scxvid. Generates a list of keyframes from a clip, using either WWXD or Scxvid.
Will be slightly more efficient with the `akarin` plugin installed.
:param clip: Clip to process. :param clip: Clip to process.
:param use_scxvid: Whether to use Scxvid. If False, the function uses WWXD. :param use_scxvid: Whether to use Scxvid. If False, the function uses WWXD.
@ -182,6 +210,9 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
The remaining keyword arguments are passed on to the respective filter. The remaining keyword arguments are passed on to the respective filter.
""" """
progress_set_message("Generating keyframes")
progress_set_progress(1)
clip = core.resize.Bilinear(clip, width=resize_h * clip.width // clip.height, height=resize_h, format=resize_format) clip = core.resize.Bilinear(clip, width=resize_h * clip.width // clip.height, height=resize_h, format=resize_format)
if use_scxvid: if use_scxvid:
@ -197,12 +228,12 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
nonlocal done nonlocal done
keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore
done += 1 done += 1
if done % (clip.num_frames // 25) == 0: if done % (clip.num_frames // 200) == 0:
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Detecting keyframes... {}% done.\n".format(100 * done // clip.num_frames)) progress_set_progress(100 * done / clip.num_frames)
return f return f
deque(clip.std.ModifyFrame(clip, _cb).frames(close=True), 0) deque(clip.std.ModifyFrame(clip, _cb).frames(close=True), 0)
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Done detecting keyframes.\n") progress_set_progress(100)
return [n for n in range(clip.num_frames) if keyframes[n]] return [n for n in range(clip.num_frames) if keyframes[n]]
@ -225,8 +256,12 @@ class GenKeyframesMode(Enum):
def ask_gen_keyframes(_: str) -> bool: def ask_gen_keyframes(_: str) -> bool:
from tkinter.messagebox import askyesno from tkinter.messagebox import askyesno
return askyesno("Generate Keyframes", \ progress_set_message("Asking whether to generate keyframes")
progress_set_indeterminate()
result = askyesno("Generate Keyframes", \
"No keyframes file was found for this video file.\nShould Aegisub detect keyframes from the video?\nThis will take a while.", default="no") "No keyframes file was found for this video file.\nShould Aegisub detect keyframes from the video?\nThis will take a while.", default="no")
progress_set_message("")
return result
def get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int], def get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int],
@ -245,6 +280,9 @@ def get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int],
generated or not generated or not
Additional keyword arguments are passed on to make_keyframes. Additional keyword arguments are passed on to make_keyframes.
""" """
progress_set_message("Looking for keyframes")
progress_set_indeterminate()
kffilename = make_keyframes_filename(filename) kffilename = make_keyframes_filename(filename)
if not os.path.exists(kffilename): if not os.path.exists(kffilename):
@ -253,7 +291,6 @@ def get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int],
if generate == GenKeyframesMode.ASK and not ask_callback(filename): if generate == GenKeyframesMode.ASK and not ask_callback(filename):
return fallback return fallback
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "No keyframes file found, detecting keyframes...\n")
keyframes = make_keyframes(clip, **kwargs) keyframes = make_keyframes(clip, **kwargs)
save_keyframes(kffilename, keyframes) save_keyframes(kffilename, keyframes)
@ -267,6 +304,8 @@ def check_audio(filename: str, **kwargs: Any) -> bool:
won't crash if it's not installed. won't crash if it's not installed.
Additional keyword arguments are passed on to BestAudioSource. Additional keyword arguments are passed on to BestAudioSource.
""" """
progress_set_message("Checking if the file has an audio track")
progress_set_indeterminate()
try: try:
ensure_plugin("bas", "BestAudioSource", "") ensure_plugin("bas", "BestAudioSource", "")
vs.core.bas.Source(source=filename, **kwargs) vs.core.bas.Source(source=filename, **kwargs)

View File

@ -0,0 +1,8 @@
# Copy files to build directory for testing purposes
vs_files = files(
'aegisub_vs.py',
)
foreach f: vs_files
configure_file(input: f, output: '@PLAINNAME@', copy: true)
endforeach

View File

@ -58,7 +58,11 @@ VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename
VSCleanCache(); VSCleanCache();
script = vs.GetScriptAPI()->createScript(nullptr); VSCore *core = vs.GetAPI()->createCore(OPT_GET("Provider/VapourSynth/Autoload User Plugins")->GetBool() ? 0 : VSCoreCreationFlags::ccfDisableAutoLoading);
if (core == nullptr) {
throw VapourSynthError("Error creating core");
}
script = vs.GetScriptAPI()->createScript(core);
if (script == nullptr) { if (script == nullptr) {
throw VapourSynthError("Error creating script API"); throw VapourSynthError("Error creating script API");
} }

View File

@ -256,6 +256,8 @@ void DialogProgress::OnCancel(wxCommandEvent &) {
} }
void DialogProgress::SetProgress(int target) { void DialogProgress::SetProgress(int target) {
pulse_timer.Stop();
if (target == progress_target) return; if (target == progress_target) return;
using namespace std::chrono; using namespace std::chrono;

View File

@ -373,6 +373,7 @@
} }
}, },
"VapourSynth" : { "VapourSynth" : {
"Autoload User Plugins": true,
"Cache" : { "Cache" : {
"Files" : 500, "Files" : 500,
"Size" : 1000 "Size" : 1000

View File

@ -2,7 +2,9 @@
"Audio" : { "Audio" : {
"Player" : "DirectSound" "Player" : "DirectSound"
}, },
"Subtitle" : { "Provider" : {
"Provider" : "CSRI/xy-vsfilter_aegisub" "VapourSynth" : {
"Autoload User Plugins": false
}
} }
} }

View File

@ -19,9 +19,15 @@ resrc = [
output: ['bitmap.cpp', 'bitmap.h']) output: ['bitmap.cpp', 'bitmap.h'])
] ]
conf_platform_json = configure_file(input: 'default_config_platform.json.in', if host_machine.system() == 'windows'
output: '@BASENAME@', conf_platform_json = configure_file(input: 'default_config_win.json',
configuration: conf_platform) output: 'default_config_platform.json',
copy: true)
else
conf_platform_json = configure_file(input: 'default_config_platform.json.in',
output: '@BASENAME@',
configuration: conf_platform)
endif
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'
resmanifest = 'manifest_osx.respack' resmanifest = 'manifest_osx.respack'

View File

@ -373,6 +373,7 @@
} }
}, },
"VapourSynth" : { "VapourSynth" : {
"Autoload User Plugins": true,
"Cache" : { "Cache" : {
"Files" : 500, "Files" : 500,
"Size" : 1000 "Size" : 1000

View File

@ -502,7 +502,9 @@ void VapourSynth(wxTreebook *book, Preferences *parent) {
const wxString log_levels[] = { "Quiet", "Fatal", "Critical", "Warning", "Information", "Debug" }; const wxString log_levels[] = { "Quiet", "Fatal", "Critical", "Warning", "Information", "Debug" };
wxArrayString log_levels_choice(6, log_levels); wxArrayString log_levels_choice(6, log_levels);
p->OptionChoice(general, _("Log Level"), log_levels_choice, "Provider/Video/VapourSynth/Log Level"); p->OptionChoice(general, _("Log level"), log_levels_choice, "Provider/Video/VapourSynth/Log Level");
p->CellSkip(general);
p->OptionAdd(general, _("Load user plugins"), "Provider/VapourSynth/Autoload User Plugins");
auto video = p->PageSizer(_("Default Video Script")); auto video = p->PageSizer(_("Default Video Script"));

View File

@ -21,8 +21,10 @@
#include "options.h" #include "options.h"
#include "utils.h" #include "utils.h"
#include <libaegisub/background_runner.h> #include <libaegisub/background_runner.h>
#include <libaegisub/format.h>
#include <libaegisub/fs.h> #include <libaegisub/fs.h>
#include <libaegisub/path.h> #include <libaegisub/path.h>
#include <libaegisub/util.h>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
@ -67,6 +69,30 @@ int OpenScriptOrVideo(const VSAPI *api, const VSSCRIPTAPI *sapi, VSScript *scrip
} }
void VSLogToProgressSink(int msgType, const char *msg, void *userData) { void VSLogToProgressSink(int msgType, const char *msg, void *userData) {
auto sink = reinterpret_cast<agi::ProgressSink *>(userData);
std::string msgStr(msg);
int commaPos = msgStr.find(',');
if (commaPos) {
std::string command = msgStr.substr(0, commaPos);
std::string tail = msgStr.substr(commaPos + 1, msgStr.length());
// We don't allow setting the title since that should stay as "Executing VapourSynth Script".
if (command == "__aegi_set_message") {
sink->SetMessage(tail);
} else if (command == "__aegi_set_progress") {
double percent;
if (!agi::util::try_parse(tail, &percent)) {
msgType = 2;
msgStr = agi::format("Warning: Invalid argument to __aegi_set_progress: %s\n", tail);
} else {
sink->SetProgress(percent, 100);
}
} else if (command == "__aegi_set_indeterminate") {
sink->SetIndeterminate();
}
}
int loglevel = 0; int loglevel = 0;
std::string loglevel_str = OPT_GET("Provider/Video/VapourSynth/Log Level")->GetString(); std::string loglevel_str = OPT_GET("Provider/Video/VapourSynth/Log Level")->GetString();
if (loglevel_str == "Quiet") if (loglevel_str == "Quiet")
@ -85,7 +111,7 @@ void VSLogToProgressSink(int msgType, const char *msg, void *userData) {
if (msgType < loglevel) if (msgType < loglevel)
return; return;
reinterpret_cast<agi::ProgressSink *>(userData)->Log(msg); sink->Log(msgStr);
} }
void VSCleanCache() { void VSCleanCache() {

View File

@ -123,7 +123,7 @@ VapourSynthVideoProvider::VapourSynthVideoProvider(agi::fs::path const& filename
VSCleanCache(); VSCleanCache();
int err1, err2; int err1, err2;
VSCore *core = vs.GetAPI()->createCore(0); VSCore *core = vs.GetAPI()->createCore(OPT_GET("Provider/VapourSynth/Autoload User Plugins")->GetBool() ? 0 : VSCoreCreationFlags::ccfDisableAutoLoading);
if (core == nullptr) { if (core == nullptr) {
throw VapourSynthError("Error creating core"); throw VapourSynthError("Error creating core");
} }