vapoursynth: Show logged messages in progress window

This commit is contained in:
arch1t3cht 2023-02-24 01:41:56 +01:00
parent 097a0f45be
commit 1f2eaaf6e4
10 changed files with 71 additions and 4 deletions

View File

@ -150,11 +150,17 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
.format("scxvid" if use_scxvid else "wwxd")) .format("scxvid" if use_scxvid else "wwxd"))
keyframes = {} keyframes = {}
done = 0
def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame: def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
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
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))
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")
return [n for n in range(clip.num_frames) if keyframes[n]] return [n for n in range(clip.num_frames) if keyframes[n]]
@ -178,6 +184,7 @@ def get_keyframes(filename: str, clip: vs.VideoNode, **kwargs: Any) -> str:
kffilename = make_keyframes_filename(filename) kffilename = make_keyframes_filename(filename)
if not os.path.exists(kffilename): if not os.path.exists(kffilename):
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)

View File

@ -46,10 +46,15 @@ namespace agi {
/// @brief Log a message /// @brief Log a message
/// ///
/// If any messages are logged then the dialog will not automatically close /// If any messages are logged and StayOpen is set (set by default)
/// when the task finishes so that the user has the chance to read them. /// then the dialog will not automatically close when the task finishes
/// so that the user has the chance to read them.
virtual void Log(std::string const& str)=0; virtual void Log(std::string const& str)=0;
/// Set whether the dialog should stay open after the task finishes.
/// Defaults to true.
virtual void SetStayOpen(bool stayopen)=0;
/// Has the user asked the task to cancel? /// Has the user asked the task to cancel?
virtual bool IsCancelled()=0; virtual bool IsCancelled()=0;
}; };

View File

@ -127,6 +127,7 @@ namespace Automation4 {
void SetMessage(std::string const& msg) override { impl->SetMessage(msg); } void SetMessage(std::string const& msg) override { impl->SetMessage(msg); }
void SetProgress(int64_t cur, int64_t max) override { impl->SetProgress(cur, max); } void SetProgress(int64_t cur, int64_t max) override { impl->SetProgress(cur, max); }
void Log(std::string const& str) override { impl->Log(str); } void Log(std::string const& str) override { impl->Log(str); }
void SetStayOpen(bool stayopen) override { impl->SetStayOpen(stayopen); }
bool IsCancelled() override { return impl->IsCancelled(); } bool IsCancelled() override { return impl->IsCancelled(); }
/// Show the passed dialog on the GUI thread, blocking the calling /// Show the passed dialog on the GUI thread, blocking the calling

View File

@ -73,6 +73,7 @@ namespace {
class DialogProgressSink final : public agi::ProgressSink { class DialogProgressSink final : public agi::ProgressSink {
DialogProgress *dialog; DialogProgress *dialog;
std::atomic<bool> cancelled{false}; std::atomic<bool> cancelled{false};
std::atomic<bool> stayopen{true};
int progress = 0; int progress = 0;
public: public:
@ -98,6 +99,14 @@ public:
Main().Async([=]{ dialog->pending_log += to_wx(str); }); Main().Async([=]{ dialog->pending_log += to_wx(str); });
} }
void SetStayOpen(bool b) override {
stayopen = b;
}
bool GetStayOpen() {
return stayopen;
}
bool IsCancelled() override { bool IsCancelled() override {
return cancelled; return cancelled;
} }
@ -169,7 +178,7 @@ void DialogProgress::Run(std::function<void(agi::ProgressSink*)> task) {
// so the user can read the debug output and switch the cancel button to a // so the user can read the debug output and switch the cancel button to a
// close button // close button
bool cancelled = this->ps->IsCancelled(); bool cancelled = this->ps->IsCancelled();
if (cancelled || (log_output->IsEmpty() && !pending_log)) if (cancelled || !this->ps->GetStayOpen() || (log_output->IsEmpty() && !pending_log))
EndModal(!cancelled); EndModal(!cancelled);
else { else {
if (!pending_log.empty()) { if (!pending_log.empty()) {

View File

@ -373,6 +373,7 @@
"Seek Preroll" : 12 "Seek Preroll" : 12
}, },
"VapourSynth" : { "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 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"
} }
} }

View File

@ -373,6 +373,7 @@
"Seek Preroll" : 12 "Seek Preroll" : 12
}, },
"VapourSynth" : { "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 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"
} }
} }

View File

@ -472,6 +472,12 @@ void Advanced_Video(wxTreebook *book, Preferences *parent) {
void VapourSynth(wxTreebook *book, Preferences *parent) { void VapourSynth(wxTreebook *book, Preferences *parent) {
#ifdef WITH_VAPOURSYNTH #ifdef WITH_VAPOURSYNTH
auto p = new OptionPage(book, parent, _("VapourSynth"), OptionPage::PAGE_SUB); auto p = new OptionPage(book, parent, _("VapourSynth"), OptionPage::PAGE_SUB);
auto general = p->PageSizer(_("General"));
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");
auto video = p->PageSizer(_("Default Video Script")); auto video = p->PageSizer(_("Default Video Script"));
auto vhint = new wxStaticText(p, wxID_ANY, _("This script will be executed to load video files that aren't\nVapourSynth scripts (i.e. end in .py or .vpy).\nThe filename variable stores the path to the file.")); auto vhint = new wxStaticText(p, wxID_ANY, _("This script will be executed to load video files that aren't\nVapourSynth scripts (i.e. end in .py or .vpy).\nThe filename variable stores the path to the file."));

View File

@ -20,6 +20,7 @@
#include "vapoursynth_wrap.h" #include "vapoursynth_wrap.h"
#include "options.h" #include "options.h"
#include "utils.h" #include "utils.h"
#include <libaegisub/background_runner.h>
#include <libaegisub/fs.h> #include <libaegisub/fs.h>
#include <libaegisub/path.h> #include <libaegisub/path.h>
@ -60,6 +61,28 @@ int OpenScriptOrVideo(const VSAPI *api, const VSSCRIPTAPI *sapi, VSScript *scrip
return result; return result;
} }
void VSLogToProgressSink(int msgType, const char *msg, void *userData) {
int loglevel = 0;
std::string loglevel_str = OPT_GET("Provider/Video/VapourSynth/Log Level")->GetString();
if (loglevel_str == "Quiet")
loglevel = 5;
else if (loglevel_str == "Fatal")
loglevel = 4;
else if (loglevel_str == "Critical")
loglevel = 3;
else if (loglevel_str == "Warning")
loglevel = 2;
else if (loglevel_str == "Information")
loglevel = 1;
else if (loglevel_str == "Debug")
loglevel = 0;
if (msgType < loglevel)
return;
reinterpret_cast<agi::ProgressSink *>(userData)->Log(msg);
}
void VSCleanCache() { void VSCleanCache() {
CleanCache(config::path->Decode("?local/vscache/"), CleanCache(config::path->Decode("?local/vscache/"),
"", "",

View File

@ -21,5 +21,6 @@
int OpenScriptOrVideo(const VSAPI *api, const VSSCRIPTAPI *sapi, VSScript *script, agi::fs::path const& filename, std::string default_script); int OpenScriptOrVideo(const VSAPI *api, const VSSCRIPTAPI *sapi, VSScript *script, agi::fs::path const& filename, std::string default_script);
void VSCleanCache(); void VSCleanCache();
void VSLogToProgressSink(int msgType, const char *msg, void *userData);
#endif // WITH_VAPOURSYNTH #endif // WITH_VAPOURSYNTH

View File

@ -123,15 +123,28 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename
VSCleanCache(); VSCleanCache();
int err1, err2; int err1, err2;
script = vs.GetScriptAPI()->createScript(nullptr); VSCore *core = vs.GetAPI()->createCore(0);
if (core == nullptr) {
throw VapoursynthError("Error creating core");
}
script = vs.GetScriptAPI()->createScript(core);
if (script == nullptr) { if (script == nullptr) {
vs.GetAPI()->freeCore(core);
throw VapoursynthError("Error creating script API"); throw VapoursynthError("Error creating script API");
} }
vs.GetScriptAPI()->evalSetWorkingDir(script, 1); vs.GetScriptAPI()->evalSetWorkingDir(script, 1);
br->Run([&](agi::ProgressSink *ps) { br->Run([&](agi::ProgressSink *ps) {
ps->SetTitle(from_wx(_("Executing Vapoursynth Script"))); ps->SetTitle(from_wx(_("Executing Vapoursynth Script")));
ps->SetMessage("");
ps->SetIndeterminate(); ps->SetIndeterminate();
VSLogHandle *logger = vs.GetAPI()->addLogHandler(VSLogToProgressSink, nullptr, ps, core);
err1 = OpenScriptOrVideo(vs.GetAPI(), vs.GetScriptAPI(), script, filename, OPT_GET("Provider/Video/VapourSynth/Default Script")->GetString()); err1 = OpenScriptOrVideo(vs.GetAPI(), vs.GetScriptAPI(), script, filename, OPT_GET("Provider/Video/VapourSynth/Default Script")->GetString());
vs.GetAPI()->removeLogHandler(logger, core);
ps->SetStayOpen(bool(err1));
if (err1)
ps->SetMessage(from_wx(_("Failed to execute script! Press \"Close\" to continue.")));
}); });
if (err1) { if (err1) {
std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script)); std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script));