Merge branches 'vapoursynth' and 'fixes' into feature
This commit is contained in:
commit
4a939d1954
12 changed files with 107 additions and 15 deletions
|
@ -1,4 +1,5 @@
|
|||
subdir('include')
|
||||
subdir('vapoursynth')
|
||||
|
||||
automation_dir = dataroot / 'automation'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
8
automation/vapoursynth/meson.build
Normal file
8
automation/vapoursynth/meson.build
Normal 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
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -373,6 +373,7 @@
|
|||
}
|
||||
},
|
||||
"VapourSynth" : {
|
||||
"Autoload User Plugins": true,
|
||||
"Cache" : {
|
||||
"Files" : 500,
|
||||
"Size" : 1000
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
"Audio" : {
|
||||
"Player" : "DirectSound"
|
||||
},
|
||||
"Subtitle" : {
|
||||
"Provider" : "CSRI/xy-vsfilter_aegisub"
|
||||
"Provider" : {
|
||||
"VapourSynth" : {
|
||||
"Autoload User Plugins": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -373,6 +373,7 @@
|
|||
}
|
||||
},
|
||||
"VapourSynth" : {
|
||||
"Autoload User Plugins": true,
|
||||
"Cache" : {
|
||||
"Files" : 500,
|
||||
"Size" : 1000
|
||||
|
|
|
@ -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"));
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue