mirror of https://github.com/odrling/Aegisub
vapoursynth: Show logged messages in progress window
This commit is contained in:
parent
097a0f45be
commit
1f2eaaf6e4
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."));
|
||||||
|
|
|
@ -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/"),
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
Loading…
Reference in New Issue