From b7b2b112a106138d99dda3f259620b350c896f1a Mon Sep 17 00:00:00 2001 From: yum Date: Sun, 18 Dec 2022 19:11:47 -0800 Subject: Add ability to select model * icon now works when pinned to taskbar * add model selection * add script to dump mic devices * whisper models now download into the virtual environment --- GUI/GUI/GUI/.gitignore | 3 + GUI/GUI/GUI/App.cpp | 1 + GUI/GUI/GUI/Frame.cpp | 221 +++++++++++++++++++++++++++------------- GUI/GUI/GUI/Frame.h | 16 ++- GUI/GUI/GUI/GUI.rc | Bin 0 -> 2798 bytes GUI/GUI/GUI/GUI.vcxproj | 11 ++ GUI/GUI/GUI/GUI.vcxproj.filters | 25 +++++ GUI/GUI/GUI/PythonWrapper.cpp | 13 ++- GUI/GUI/GUI/PythonWrapper.h | 9 +- GUI/GUI/GUI/Resources/logo.ico | Bin 0 -> 4314 bytes GUI/package.ps1 | 4 +- 11 files changed, 215 insertions(+), 88 deletions(-) create mode 100644 GUI/GUI/GUI/GUI.rc create mode 100644 GUI/GUI/GUI/Resources/logo.ico (limited to 'GUI') diff --git a/GUI/GUI/GUI/.gitignore b/GUI/GUI/GUI/.gitignore index 86c78ae..92618b8 100644 --- a/GUI/GUI/GUI/.gitignore +++ b/GUI/GUI/GUI/.gitignore @@ -1,3 +1,6 @@ # Don't check in build artifacts x64 x86 +# No .rc generated files +GUI.APS +resource.h diff --git a/GUI/GUI/GUI/App.cpp b/GUI/GUI/GUI/App.cpp index 8456447..94a01a4 100644 --- a/GUI/GUI/GUI/App.cpp +++ b/GUI/GUI/GUI/App.cpp @@ -4,6 +4,7 @@ bool MyApp::OnInit() { Frame* frame = new Frame(); + frame->Show(true); return true; diff --git a/GUI/GUI/GUI/Frame.cpp b/GUI/GUI/GUI/Frame.cpp index 55112db..4f23beb 100644 --- a/GUI/GUI/GUI/Frame.cpp +++ b/GUI/GUI/GUI/Frame.cpp @@ -8,13 +8,19 @@ namespace { enum FrameIds { ID_PY_PANEL, - ID_PY_VERSION_BUTTON, + ID_PY_CONFIG_PANEL, + ID_PY_CONFIG_DROPDOWN_PANEL, ID_PY_SETUP_BUTTON, + ID_PY_DUMP_MICS_BUTTON, ID_PY_APP_START_BUTTON, ID_PY_APP_STOP_BUTTON, ID_PY_OUT, ID_PY_APP_MIC, + ID_PY_APP_MIC_PANEL, ID_PY_APP_LANG, + ID_PY_APP_LANG_PANEL, + ID_PY_APP_MODEL, + ID_PY_APP_MODEL_PANEL, }; const wxString kMicChoices[] = { @@ -33,6 +39,7 @@ namespace { "9", }; const size_t kNumMicChoices = sizeof(kMicChoices) / sizeof(kMicChoices[0]); + constexpr int kMicDefault = 0; // index // lifted from whisper/tokenizer.py const wxString kLangChoices[] = { @@ -137,79 +144,134 @@ namespace { "sundanese" }; const size_t kNumLangChoices = sizeof(kLangChoices) / sizeof(kLangChoices[0]); + constexpr int kLangDefault = 0; // english + + // lifted from whisper/__init__.py + const wxString kModelChoices[] = { + "tiny.en", + "tiny", + "base.en", + "base", + "small.en", + "small", + "medium.en", + "medium", + }; + const size_t kNumModelChoices = sizeof(kModelChoices) / sizeof(kModelChoices[0]); + constexpr int kModelDefault = 2; // base.en } // namespace Frame::Frame() : wxFrame(nullptr, wxID_ANY, "TaSTT"), - py_panel_(this, ID_PY_PANEL), - 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), - py_app_(nullptr), - py_app_mic_(&py_panel_, ID_PY_APP_MIC, wxDefaultPosition, wxDefaultSize, kNumMicChoices, kMicChoices), - py_app_lang_(&py_panel_, ID_PY_APP_LANG, wxDefaultPosition, wxDefaultSize, kNumLangChoices, kLangChoices) + py_app_(nullptr) { + auto* py_panel = new wxPanel(this, ID_PY_PANEL); + { + const auto py_out_sz = wxSize(/*x_px=*/320, /*y_px=*/160); + auto* py_out = new wxTextCtrl(py_panel, ID_PY_OUT, + wxEmptyString, + wxDefaultPosition, + py_out_sz, wxTE_MULTILINE | wxTE_READONLY); + py_out->SetMinSize(py_out_sz); + py_out_ = py_out; + + py_out_->AppendText(PythonWrapper::GetVersion() + "\n"); + + auto* py_config_panel = new wxPanel(py_panel, ID_PY_CONFIG_PANEL); + { + auto* py_setup_button = new wxButton(py_config_panel, ID_PY_SETUP_BUTTON, "Set up Python virtual environment"); + auto* py_dump_mics_button = new wxButton(py_config_panel, ID_PY_DUMP_MICS_BUTTON, "List input devices"); + + auto* py_config_dropdown_panel = new wxPanel(py_config_panel, ID_PY_CONFIG_DROPDOWN_PANEL); + { + auto* py_app_mic = new wxChoice(py_config_dropdown_panel, ID_PY_APP_MIC, wxDefaultPosition, + wxDefaultSize, kNumMicChoices, kMicChoices); + py_app_mic->SetSelection(kMicDefault); + py_app_mic_ = py_app_mic; + + auto* py_app_lang = new wxChoice(py_config_dropdown_panel, ID_PY_APP_LANG, wxDefaultPosition, + wxDefaultSize, kNumLangChoices, kLangChoices); + py_app_lang->SetSelection(kLangDefault); + py_app_lang_ = py_app_lang; + + auto* py_app_model = new wxChoice(py_config_dropdown_panel, ID_PY_APP_MODEL, wxDefaultPosition, + wxDefaultSize, kNumModelChoices, kModelChoices); + py_app_model->SetSelection(kModelDefault); + py_app_model_ = py_app_model; + + auto* sizer = new wxGridSizer(/*cols=*/2); + py_config_dropdown_panel->SetSizer(sizer); + + sizer->Add(new wxStaticText(py_config_dropdown_panel, wxID_ANY, /*label=*/"Microphone:")); + sizer->Add(py_app_mic); + + sizer->Add(new wxStaticText(py_config_dropdown_panel, wxID_ANY, /*label=*/"Language:")); + sizer->Add(py_app_lang); + + sizer->Add(new wxStaticText(py_config_dropdown_panel, wxID_ANY, /*label=*/"Model:")); + sizer->Add(py_app_model); + } + + auto* py_app_start_button = new wxButton(py_config_panel, ID_PY_APP_START_BUTTON, "Begin transcribing"); + auto* py_app_stop_button = new wxButton(py_config_panel, ID_PY_APP_STOP_BUTTON, "Stop transcribing"); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + py_config_panel->SetSizer(sizer); + sizer->Add(py_setup_button); + sizer->Add(py_dump_mics_button); + sizer->Add(py_config_dropdown_panel); + sizer->Add(py_app_start_button); + sizer->Add(py_app_stop_button); + } + + auto* sizer = new wxBoxSizer(wxHORIZONTAL); + py_panel->SetSizer(sizer); + sizer->Add(py_config_panel); + sizer->Add(py_out); + } + 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); + Bind(wxEVT_BUTTON, &Frame::OnDumpMics, this, ID_PY_DUMP_MICS_BUTTON); // wx needs this to be able to load PNGs. wxImage::AddHandler(&png_handler_); - const std::string icon_path = "Resources/logo.png"; - LoadAndSetIcon(icon_path); - - wxSize py_out_size(/*x=*/80, /*y=*/20); - py_out_.SetSize(py_out_size); - py_app_mic_.SetSelection(0); - py_app_lang_.SetSelection(0); - - 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_mic_); - py_panel_sizer_.Add(&py_app_lang_); - py_panel_sizer_.Add(&py_app_start_button_); - py_panel_sizer_.Add(&py_app_stop_button_); - py_panel_sizer_.Add(&py_out_); + LoadAndSetIcons(); + + { + auto frame_sz = GetBestSize(); + auto panel_sz = py_panel->GetBestSize(); + + auto ideal_sz = panel_sz; + ideal_sz.y += frame_sz.y; + + this->SetSize(ideal_sz); + } } void Frame::OnExit(wxCommandEvent& event) { + OnAppStop(event); Close(true); } -void Frame::OnGetPythonVersion(wxCommandEvent& event) -{ - PythonWrapper py; - std::string py_version = py.GetVersion(); - py_out_.AppendText(py_version + "\n"); -} - void Frame::OnSetupPython(wxCommandEvent& event) { - PythonWrapper py; - - 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 GUI's folder, " - "so deleting the folder is all that's needed to uninstall.\n"); + 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"); { std::string py_out; std::ostringstream py_out_oss; py_out_oss << " Installing pip" << std::endl; - py_out_.AppendText(py_out_oss.str()); - if (!py.InstallPip(&py_out)) { + py_out_->AppendText(py_out_oss.str()); + if (!PythonWrapper::InstallPip(&py_out)) { std::ostringstream py_out_oss; py_out_oss << "Failed to install pip: " << py_out; - py_out_.AppendText(py_out_oss.str()); + py_out_->AppendText(py_out_oss.str()); } } @@ -228,56 +290,65 @@ void Frame::OnSetupPython(wxCommandEvent& event) { std::ostringstream py_out_oss; py_out_oss << " Installing " << pip_dep << std::endl; - py_out_.AppendText(py_out_oss.str()); + py_out_->AppendText(py_out_oss.str()); } std::string py_out; - bool res = py.InvokeWithArgs({ "-m", "pip", "install", pip_dep }, &py_out); + bool res = PythonWrapper::InvokeWithArgs({ "-m", "pip", "install", pip_dep }, &py_out); if (!res) { std::ostringstream py_out_oss; py_out_oss << "Failed to install " << pip_dep << ": " << py_out << std::endl; - py_out_.AppendText(py_out_oss.str()); + py_out_->AppendText(py_out_oss.str()); return; } } - py_out_.AppendText("Python virtual environment successfully set up!\n"); + py_out_->AppendText("Python virtual environment successfully set up!\n"); +} + +void Frame::OnDumpMics(wxCommandEvent& event) +{ + py_out_->AppendText(PythonWrapper::DumpMics()); } void Frame::OnAppStart(wxCommandEvent& event) { if (py_app_) { if (wxProcess::Exists(py_app_->GetPid())) { - py_out_.AppendText("Transcription engine already running\n"); + py_out_->AppendText("Transcription engine already running\n"); return; } delete py_app_; py_app_ = nullptr; } - py_out_.AppendText("Launching transcription engine\n"); + 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()); + py_out_->AppendText(py_out_oss.str()); return; }; - int which_mic = py_app_mic_.GetSelection(); + int which_mic = py_app_mic_->GetSelection(); if (which_mic == wxNOT_FOUND) { - which_mic = 0; + which_mic = kMicDefault; } - int which_lang = py_app_lang_.GetSelection(); + int which_lang = py_app_lang_->GetSelection(); if (which_lang == wxNOT_FOUND) { - which_lang = 0; + which_lang = kLangDefault; + } + int which_model = py_app_model_->GetSelection(); + if (which_model == wxNOT_FOUND) { + which_model = kModelDefault; } - wxProcess* p = py.StartApp(std::move(cb), + wxProcess* p = PythonWrapper::StartApp(std::move(cb), kMicChoices[which_mic].ToStdString(), - kLangChoices[which_lang].ToStdString()); + kLangChoices[which_lang].ToStdString(), + kModelChoices[which_model].ToStdString()); if (!p) { - py_out_.AppendText("Failed to launch transcription engine\n"); + py_out_->AppendText("Failed to launch transcription engine\n"); return; } @@ -303,11 +374,11 @@ void Frame::OnAppStop(wxCommandEvent& event) { while (wxProcess::Exists(pid)) { if (first) { first = false; - py_out_.AppendText("Timed out trying to stop transcription engine " + 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"); + py_out_->AppendText("Waiting for transcription engine to exit"); } wxProcess::Kill(pid, wxSIGKILL); wxMilliSleep(10); @@ -315,20 +386,26 @@ void Frame::OnAppStop(wxCommandEvent& event) { // Since we don't process the termination event, py_app_ deletes itself! py_app_ = nullptr; - py_out_.AppendText("Stopped transcription engine\n"); + py_out_->AppendText("Stopped transcription engine\n"); } else { - py_out_.AppendText("Transcription engine already stopped\n"); + 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()); +void Frame::LoadAndSetIcons() { + const char* icons[] = { + "Resources/Images/logo.png", + "Resources/Images/logo_16x16.png", + "Resources/Images/logo_32x32.png", + }; + wxIconBundle icon_bundle; + for (const auto& icon_path : icons) { + if (!std::filesystem::exists(icon_path)) { + wxLogFatalError("Logo is missing from %s", icon_path); + } + icon_bundle.AddIcon(icon_path, wxBITMAP_TYPE_PNG); } - wxBitmap icon_img(icon_path, wxBITMAP_TYPE_PNG); - wxIcon icon; - icon.CopyFromBitmap(icon_img); - SetIcon(icon); + SetIcons(icon_bundle); } diff --git a/GUI/GUI/GUI/Frame.h b/GUI/GUI/GUI/Frame.h index 8132cce..e5b3ae3 100644 --- a/GUI/GUI/GUI/Frame.h +++ b/GUI/GUI/GUI/Frame.h @@ -15,23 +15,19 @@ public: private: wxPNGHandler png_handler_; - wxPanel py_panel_; - 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_; - wxChoice py_app_mic_; - wxChoice py_app_lang_; + wxTextCtrl* py_out_; + wxChoice* py_app_mic_; + wxChoice* py_app_lang_; + wxChoice* py_app_model_; wxProcess* py_app_; void OnExit(wxCommandEvent& event); void OnGetPythonVersion(wxCommandEvent& event); void OnSetupPython(wxCommandEvent& event); + void OnDumpMics(wxCommandEvent& event); void OnAppStart(wxCommandEvent& event); void OnAppStop(wxCommandEvent& event); - void LoadAndSetIcon(const std::string& icon_path); + void LoadAndSetIcons(); }; diff --git a/GUI/GUI/GUI/GUI.rc b/GUI/GUI/GUI/GUI.rc new file mode 100644 index 0000000..01c922a Binary files /dev/null and b/GUI/GUI/GUI/GUI.rc differ diff --git a/GUI/GUI/GUI/GUI.vcxproj b/GUI/GUI/GUI/GUI.vcxproj index 223f47e..cd0e5f0 100644 --- a/GUI/GUI/GUI/GUI.vcxproj +++ b/GUI/GUI/GUI/GUI.vcxproj @@ -144,8 +144,19 @@ + + + + + + + + + + + diff --git a/GUI/GUI/GUI/GUI.vcxproj.filters b/GUI/GUI/GUI/GUI.vcxproj.filters index 74e4659..5118c26 100644 --- a/GUI/GUI/GUI/GUI.vcxproj.filters +++ b/GUI/GUI/GUI/GUI.vcxproj.filters @@ -41,5 +41,30 @@ Header Files + + Header Files + + + + + Resource Files + + + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + \ No newline at end of file diff --git a/GUI/GUI/GUI/PythonWrapper.cpp b/GUI/GUI/GUI/PythonWrapper.cpp index 53fcc06..6e9e0f1 100644 --- a/GUI/GUI/GUI/PythonWrapper.cpp +++ b/GUI/GUI/GUI/PythonWrapper.cpp @@ -78,6 +78,16 @@ std::string PythonWrapper::GetVersion() { return result; } +std::string PythonWrapper::DumpMics() { + std::string result; + const std::string dump_mics_path = "Resources/Scripts/dump_mic_devices.py"; + bool ok = InvokeWithArgs({ dump_mics_path }, &result); + if (!ok) { + wxLogFatalError("Failed to dump mic devices: %s", result.c_str()); + } + return result; +} + bool PythonWrapper::InstallPip(std::string* out) { std::string result; @@ -87,11 +97,12 @@ bool PythonWrapper::InstallPip(std::string* out) { wxProcess* PythonWrapper::StartApp( std::function&& exit_callback, - const std::string& mic, const std::string& lang) { + const std::string& mic, const std::string& lang, const std::string& model) { return InvokeAsyncWithArgs({ "Resources/Scripts/transcribe.py", "--mic", mic, "--lang", lang, + "--model", model, }, std::move(exit_callback)); } diff --git a/GUI/GUI/GUI/PythonWrapper.h b/GUI/GUI/GUI/PythonWrapper.h index 0fa3c94..f6a739e 100644 --- a/GUI/GUI/GUI/PythonWrapper.h +++ b/GUI/GUI/GUI/PythonWrapper.h @@ -14,10 +14,8 @@ /* * This class wraps interactions with the embedded Python interpreter. */ -class PythonWrapper +namespace PythonWrapper { -public: - // Invoke the interpreter asynchronously with the given arguments. // When the process exits, `exit_callback` runs. // The caller is responsible for deleting wxProcess. @@ -31,11 +29,14 @@ public: // Execute python --version. std::string GetVersion(); + // Executes dump_mic_devices.py. + std::string DumpMics(); + // Execute get-pip.py. bool InstallPip(std::string* out); wxProcess* StartApp( std::function&& exit_callback, - const std::string& mic, const std::string& lang); + const std::string& mic, const std::string& lang, const std::string& model); }; diff --git a/GUI/GUI/GUI/Resources/logo.ico b/GUI/GUI/GUI/Resources/logo.ico new file mode 100644 index 0000000..aca1b5a Binary files /dev/null and b/GUI/GUI/GUI/Resources/logo.ico differ diff --git a/GUI/package.ps1 b/GUI/package.ps1 index 0346e67..36049b2 100644 --- a/GUI/package.ps1 +++ b/GUI/package.ps1 @@ -6,9 +6,11 @@ if (Test-Path $install_dir) { mkdir $install_dir > $null mkdir $install_dir/Resources > $null -cp ../Images/logo.png TaSTT/Resources +cp -Recurse ../Images TaSTT/Resources/Images cp -Recurse ../Python TaSTT/Resources/Python cp -Recurse ../Scripts TaSTT/Resources/Scripts cp -Recurse ../Sounds TaSTT/Resources/Sounds cp GUI/x64/Release/GUI.exe TaSTT/TaSTT.exe +#Compress-Archive -Path "$install_dir" -DestinationPath "$install_dir.zip" -Force + -- cgit v1.2.3