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('vapoursynth')
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
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
other data.
"""
@ -40,6 +43,28 @@ aegi_vsplugins: str = ""
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):
"""
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
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")
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)
progress_set_message("Getting timecodes and keyframes from the index file")
return clip, info_from_lwindex(cachefile)
@ -172,7 +201,6 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
**kwargs: Any) -> List[int]:
"""
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 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.
"""
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)
if use_scxvid:
@ -197,12 +228,12 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
nonlocal done
keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore
done += 1
if done % (clip.num_frames // 25) == 0:
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Detecting keyframes... {}% done.\n".format(100 * done // clip.num_frames))
if done % (clip.num_frames // 200) == 0:
progress_set_progress(100 * done / clip.num_frames)
return f
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]]
@ -225,8 +256,12 @@ class GenKeyframesMode(Enum):
def ask_gen_keyframes(_: str) -> bool:
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")
progress_set_message("")
return result
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
Additional keyword arguments are passed on to make_keyframes.
"""
progress_set_message("Looking for keyframes")
progress_set_indeterminate()
kffilename = make_keyframes_filename(filename)
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):
return fallback
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "No keyframes file found, detecting keyframes...\n")
keyframes = make_keyframes(clip, **kwargs)
save_keyframes(kffilename, keyframes)
@ -267,6 +304,8 @@ def check_audio(filename: str, **kwargs: Any) -> bool:
won't crash if it's not installed.
Additional keyword arguments are passed on to BestAudioSource.
"""
progress_set_message("Checking if the file has an audio track")
progress_set_indeterminate()
try:
ensure_plugin("bas", "BestAudioSource", "")
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();
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) {
throw VapourSynthError("Error creating script API");
}

View File

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

View File

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

View File

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

View File

@ -19,9 +19,15 @@ resrc = [
output: ['bitmap.cpp', 'bitmap.h'])
]
conf_platform_json = configure_file(input: 'default_config_platform.json.in',
output: '@BASENAME@',
configuration: conf_platform)
if host_machine.system() == 'windows'
conf_platform_json = configure_file(input: 'default_config_win.json',
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'
resmanifest = 'manifest_osx.respack'

View File

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

View File

@ -502,7 +502,9 @@ void VapourSynth(wxTreebook *book, Preferences *parent) {
const wxString log_levels[] = { "Quiet", "Fatal", "Critical", "Warning", "Information", "Debug" };
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"));

View File

@ -21,8 +21,10 @@
#include "options.h"
#include "utils.h"
#include <libaegisub/background_runner.h>
#include <libaegisub/format.h>
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <libaegisub/util.h>
#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) {
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;
std::string loglevel_str = OPT_GET("Provider/Video/VapourSynth/Log Level")->GetString();
if (loglevel_str == "Quiet")
@ -85,7 +111,7 @@ void VSLogToProgressSink(int msgType, const char *msg, void *userData) {
if (msgType < loglevel)
return;
reinterpret_cast<agi::ProgressSink *>(userData)->Log(msg);
sink->Log(msgStr);
}
void VSCleanCache() {

View File

@ -123,7 +123,7 @@ VapourSynthVideoProvider::VapourSynthVideoProvider(agi::fs::path const& filename
VSCleanCache();
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) {
throw VapourSynthError("Error creating core");
}