summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2022-12-17 20:19:32 -0800
committeryum <yum.food.vr@gmail.com>2022-12-17 20:19:32 -0800
commit03fbf0e8ca409fe4c26e246286a975724ad0994b (patch)
tree298ac903b8706acc51ebd7fbf726848d2cb052c8
parentee8213d1d2c2008d2d996929500c9e87dac325a3 (diff)
GUI: Add ability to start & stop transcription engine
-rw-r--r--GUI/GUI/GUI/Frame.cpp83
-rw-r--r--GUI/GUI/GUI/Frame.h8
-rw-r--r--GUI/GUI/GUI/PythonWrapper.cpp38
-rw-r--r--GUI/GUI/GUI/PythonWrapper.h11
4 files changed, 137 insertions, 3 deletions
diff --git a/GUI/GUI/GUI/Frame.cpp b/GUI/GUI/GUI/Frame.cpp
index 74683ca..db2bdd7 100644
--- a/GUI/GUI/GUI/Frame.cpp
+++ b/GUI/GUI/GUI/Frame.cpp
@@ -10,6 +10,8 @@ namespace {
ID_PY_PANEL,
ID_PY_VERSION_BUTTON,
ID_PY_SETUP_BUTTON,
+ ID_PY_APP_START_BUTTON,
+ ID_PY_APP_STOP_BUTTON,
ID_PY_OUT,
};
};
@@ -20,11 +22,16 @@ Frame::Frame()
py_panel_sizer_(wxVERTICAL),
py_version_button_(&py_panel_, ID_PY_VERSION_BUTTON, "Check embedded Python version"),
py_setup_button_(&py_panel_, ID_PY_SETUP_BUTTON, "Set up Python virtual environment"),
+ py_app_start_button_(&py_panel_, ID_PY_APP_START_BUTTON, "Begin transcribing"),
+ py_app_stop_button_(&py_panel_, ID_PY_APP_STOP_BUTTON, "Stop transcribing"),
py_out_(&py_panel_, ID_PY_OUT, wxEmptyString, wxDefaultPosition,
- wxSize(/*x_px=*/480, /*y_px=*/160), wxTE_MULTILINE)
+ wxSize(/*x_px=*/480, /*y_px=*/160), wxTE_MULTILINE),
+ py_app_(nullptr)
{
Bind(wxEVT_MENU, &Frame::OnExit, this, wxID_EXIT);
Bind(wxEVT_BUTTON, &Frame::OnGetPythonVersion, this, ID_PY_VERSION_BUTTON);
+ Bind(wxEVT_BUTTON, &Frame::OnAppStart, this, ID_PY_APP_START_BUTTON);
+ Bind(wxEVT_BUTTON, &Frame::OnAppStop, this, ID_PY_APP_STOP_BUTTON);
Bind(wxEVT_BUTTON, &Frame::OnSetupPython, this, ID_PY_SETUP_BUTTON);
// wx needs this to be able to load PNGs.
@@ -38,6 +45,8 @@ Frame::Frame()
py_panel_.SetSizer(&py_panel_sizer_);
py_panel_sizer_.Add(&py_version_button_);
py_panel_sizer_.Add(&py_setup_button_);
+ py_panel_sizer_.Add(&py_app_start_button_);
+ py_panel_sizer_.Add(&py_app_stop_button_);
py_panel_sizer_.Add(&py_out_);
}
@@ -60,8 +69,8 @@ void Frame::OnSetupPython(wxCommandEvent& event)
py_out_.AppendText("Setting up Python virtual environment\n");
py_out_.AppendText("This could take several minutes, please be patient!\n");
py_out_.AppendText("This will download ~5GB of dependencies.\n");
- py_out_.AppendText("Dependencies are installed in the executable folder, "
- "so deleting the folder is all that's needed to undo this.");
+ py_out_.AppendText("Dependencies are installed in the GUI's folder, "
+ "so deleting the folder is all that's needed to uninstall.\n");
{
std::string py_out;
@@ -107,6 +116,74 @@ void Frame::OnSetupPython(wxCommandEvent& event)
py_out_.AppendText("Python virtual environment successfully set up!\n");
}
+void Frame::OnAppStart(wxCommandEvent& event) {
+ if (py_app_) {
+ if (wxProcess::Exists(py_app_->GetPid())) {
+ py_out_.AppendText("Transcription engine already running\n");
+ return;
+ }
+ delete py_app_;
+ py_app_ = nullptr;
+ }
+
+ py_out_.AppendText("Launching transcription engine\n");
+
+ PythonWrapper py;
+ auto cb = [&](wxProcess* proc, int ret) -> void {
+ std::ostringstream py_out_oss;
+ py_out_oss << "Transcription engine exited with code " << ret << std::endl;
+
+ py_out_.AppendText(py_out_oss.str());
+ return;
+ };
+
+ wxProcess* p = py.StartApp(std::move(cb));
+ if (!p) {
+ py_out_.AppendText("Failed to launch transcription engine\n");
+ return;
+ }
+
+ py_app_ = p;
+}
+
+void Frame::OnAppStop(wxCommandEvent& event) {
+ if (py_app_) {
+ const long pid = py_app_->GetPid();
+
+ // Try to kill it politely.
+ wxProcess::Kill(pid);
+ for (int i = 0; i < 10; i++) {
+ if (!wxProcess::Exists(pid)) {
+ break;
+ }
+ wxMilliSleep(10);
+ }
+
+ // If it doesn't accept its fate, murder it with an axe.
+ bool first = true;
+ int loop_cnt = 0;
+ while (wxProcess::Exists(pid)) {
+ if (first) {
+ first = false;
+ py_out_.AppendText("Timed out trying to stop transcription engine "
+ "cleanly, sending SIGKILL\n");
+ }
+ else if (++loop_cnt % 100 == 0) {
+ py_out_.AppendText("Waiting for transcription engine to exit");
+ }
+ wxProcess::Kill(pid, wxSIGKILL);
+ wxMilliSleep(10);
+ }
+
+ // Since we don't process the termination event, py_app_ deletes itself!
+ py_app_ = nullptr;
+ py_out_.AppendText("Stopped transcription engine\n");
+ }
+ else {
+ py_out_.AppendText("Transcription engine already stopped\n");
+ }
+}
+
void Frame::LoadAndSetIcon(const std::string& icon_path) {
if (!std::filesystem::exists(icon_path)) {
wxLogFatalError("Logo is missing from %s", icon_path.c_str());
diff --git a/GUI/GUI/GUI/Frame.h b/GUI/GUI/GUI/Frame.h
index 62e9169..414d2b3 100644
--- a/GUI/GUI/GUI/Frame.h
+++ b/GUI/GUI/GUI/Frame.h
@@ -6,6 +6,8 @@
#include <wx/wx.h>
#endif
+#include <memory>
+
class Frame : public wxFrame
{
public:
@@ -17,11 +19,17 @@ private:
wxBoxSizer py_panel_sizer_;
wxButton py_version_button_;
wxButton py_setup_button_;
+ wxButton py_app_start_button_;
+ wxButton py_app_stop_button_;
wxTextCtrl py_out_;
+ wxProcess* py_app_;
+
void OnExit(wxCommandEvent& event);
void OnGetPythonVersion(wxCommandEvent& event);
void OnSetupPython(wxCommandEvent& event);
+ void OnAppStart(wxCommandEvent& event);
+ void OnAppStop(wxCommandEvent& event);
void LoadAndSetIcon(const std::string& icon_path);
};
diff --git a/GUI/GUI/GUI/PythonWrapper.cpp b/GUI/GUI/GUI/PythonWrapper.cpp
index 27d12fd..7270ab5 100644
--- a/GUI/GUI/GUI/PythonWrapper.cpp
+++ b/GUI/GUI/GUI/PythonWrapper.cpp
@@ -4,6 +4,38 @@
#include <sstream>
+class PythonProcess : public wxProcess {
+public:
+ PythonProcess(std::function<void(wxProcess* proc, int ret)>&& exit_callback) : exit_cb_(exit_callback) {}
+
+ virtual void OnTerminate(int pid, int status) wxOVERRIDE {
+ exit_cb_(this, status);
+ }
+
+private:
+ const std::function<void(wxProcess* proc, int ret)> exit_cb_;
+};
+
+wxProcess* PythonWrapper::InvokeAsyncWithArgs(std::vector<std::string>&& args,
+ std::function<void(wxProcess* proc, int ret)>&& exit_callback) {
+ std::ostringstream cmd_oss;
+ cmd_oss << "Resources/Python/python.exe";
+ for (const auto& arg : args) {
+ cmd_oss << " " << arg;
+ }
+
+ auto *p = new PythonProcess(std::move(exit_callback));
+ // TODO(yum) we should hide the console & stream output to a friendlier interface
+ int pid = wxExecute(cmd_oss.str(), wxEXEC_ASYNC, p);
+
+ if (!pid) {
+ delete p;
+ p = nullptr;
+ }
+
+ return p;
+}
+
bool PythonWrapper::InvokeWithArgs(std::vector<std::string>&& args, std::string* out) {
std::ostringstream cmd_oss;
cmd_oss << "Resources/Python/python.exe";
@@ -52,3 +84,9 @@ bool PythonWrapper::InstallPip(std::string* out) {
std::string pip_path = "Resources/Python/get-pip.py";
return InvokeWithArgs({ pip_path }, out);
}
+
+wxProcess* PythonWrapper::StartApp(std::function<void(wxProcess* proc, int ret)>&& exit_callback) {
+ return InvokeAsyncWithArgs({ "Resources/Scripts/transcribe.py" },
+ std::move(exit_callback));
+}
+
diff --git a/GUI/GUI/GUI/PythonWrapper.h b/GUI/GUI/GUI/PythonWrapper.h
index 607507d..4407b5e 100644
--- a/GUI/GUI/GUI/PythonWrapper.h
+++ b/GUI/GUI/GUI/PythonWrapper.h
@@ -6,6 +6,8 @@
#include <wx/wx.h>
#endif
+#include <wx/process.h>
+
#include <string>
#include <vector>
@@ -15,6 +17,13 @@
class PythonWrapper
{
public:
+
+ // Invoke the interpreter asynchronously with the given arguments.
+ // When the process exits, `exit_callback` runs.
+ // The caller is responsible for deleting wxProcess.
+ wxProcess* InvokeAsyncWithArgs(std::vector<std::string>&& args,
+ std::function<void(wxProcess* proc, int ret)>&& exit_callback);
+
// Invoke the interpreter with arguments.
// On error, sets `out` to an error message and returns false.
bool InvokeWithArgs(std::vector<std::string>&& args, std::string* out);
@@ -24,5 +33,7 @@ public:
// Execute get-pip.py.
bool InstallPip(std::string* out);
+
+ wxProcess* StartApp(std::function<void(wxProcess* proc, int ret)>&& exit_callback);
};