diff options
| author | Carl Hetherington <cth@carlh.net> | 2024-04-09 02:02:28 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2024-04-17 09:36:45 +0200 |
| commit | af20e21e2363f7c4d5f7031c444984f383c26914 (patch) | |
| tree | 072277c1a9c48d81367384d0c0f4a3ae356ce54e /src | |
| parent | 39960bc88eee794ade1a73b00523e749945b9eab (diff) | |
Separate GUI verifier with basic reporting (#1823).
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/config.cc | 1 | ||||
| -rw-r--r-- | src/lib/verify_dcp_job.cc | 4 | ||||
| -rw-r--r-- | src/lib/verify_dcp_job.h | 6 | ||||
| -rw-r--r-- | src/tools/dcpomatic_verifier.cc | 247 | ||||
| -rw-r--r-- | src/tools/wscript | 10 | ||||
| -rw-r--r-- | src/wx/verify_dcp_result_dialog.cc | 1 | ||||
| -rw-r--r-- | src/wx/verify_dcp_result_panel.cc | 67 | ||||
| -rw-r--r-- | src/wx/verify_dcp_result_panel.h | 8 |
8 files changed, 335 insertions, 9 deletions
diff --git a/src/lib/config.cc b/src/lib/config.cc index c80ef224e..21192ad30 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -200,6 +200,7 @@ Config::set_defaults () _initial_paths["CinemaDatabasePath"] = boost::none; _initial_paths["ConfigFilePath"] = boost::none; _initial_paths["Preferences"] = boost::none; + _initial_paths["SaveVerificationReport"] = boost::none; _use_isdcf_name_by_default = true; _write_kdms_to_disk = true; _email_kdms = false; diff --git a/src/lib/verify_dcp_job.cc b/src/lib/verify_dcp_job.cc index 5e50ec51d..668b1eab4 100644 --- a/src/lib/verify_dcp_job.cc +++ b/src/lib/verify_dcp_job.cc @@ -86,7 +86,7 @@ VerifyDCPJob::run () } } - _notes = dcp::verify( + _result = dcp::verify( _directories, decrypted_kdms, bind(&VerifyDCPJob::update_stage, this, _1, _2), @@ -96,7 +96,7 @@ VerifyDCPJob::run () ); bool failed = false; - for (auto i: _notes) { + for (auto i: _result.notes) { if (i.type() == dcp::VerificationNote::Type::ERROR) { failed = true; } diff --git a/src/lib/verify_dcp_job.h b/src/lib/verify_dcp_job.h index 61a347507..d7ac21d41 100644 --- a/src/lib/verify_dcp_job.h +++ b/src/lib/verify_dcp_job.h @@ -36,8 +36,8 @@ public: std::string json_name () const override; void run () override; - std::vector<dcp::VerificationNote> notes () const { - return _notes; + dcp::VerificationResult const& result() const { + return _result; } private: @@ -45,5 +45,5 @@ private: std::vector<boost::filesystem::path> _directories; std::vector<boost::filesystem::path> _kdms; - std::vector<dcp::VerificationNote> _notes; + dcp::VerificationResult _result; }; diff --git a/src/tools/dcpomatic_verifier.cc b/src/tools/dcpomatic_verifier.cc new file mode 100644 index 000000000..382516acc --- /dev/null +++ b/src/tools/dcpomatic_verifier.cc @@ -0,0 +1,247 @@ +/* + Copyright (C) 2024 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +/** @file src/tools/dcpomatic_verify.cc + * @brief A DCP verify GUI. + */ + + +#include "wx/check_box.h" +#include "wx/dcpomatic_button.h" +#include "wx/dir_picker_ctrl.h" +#include "wx/verify_dcp_progress_panel.h" +#include "wx/verify_dcp_result_panel.h" +#include "wx/wx_util.h" +#include "lib/constants.h" +#include "lib/cross.h" +#include "lib/job_manager.h" +#include "lib/verify_dcp_job.h" +#include "lib/util.h" +#include <dcp/verify_report.h> +LIBDCP_DISABLE_WARNINGS +#include <wx/evtloop.h> +#include <wx/wx.h> +LIBDCP_ENABLE_WARNINGS +#ifdef __WXGTK__ +#include <X11/Xlib.h> +#endif + + +using std::exception; +using std::make_shared; + + +class DOMFrame : public wxFrame +{ +public: + explicit DOMFrame(wxString const& title) + : wxFrame(nullptr, -1, title) + { +#ifdef DCPOMATIC_WINDOWS + SetIcon(wxIcon(std_to_wx("id"))); +#endif + auto overall_sizer = new wxBoxSizer(wxVERTICAL); + + auto dcp_sizer = new wxBoxSizer(wxHORIZONTAL); + add_label_to_sizer(dcp_sizer, this, _("DCP"), true, 0, wxALIGN_CENTER_VERTICAL); + _dcp = new DirPickerCtrl(this, true); + dcp_sizer->Add(_dcp, 1, wxEXPAND); + overall_sizer->Add(dcp_sizer, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER); + + auto options_sizer = new wxBoxSizer(wxVERTICAL); + _write_log = new CheckBox(this, _("Write log to DCP folder")); + options_sizer->Add(_write_log, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP); + overall_sizer->Add(options_sizer, 0, wxLEFT, DCPOMATIC_DIALOG_BORDER); + + _verify = new Button(this, _("Verify")); + overall_sizer->Add(_verify, 0, wxEXPAND | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER); + + _progress_panel = new VerifyDCPProgressPanel(this); + overall_sizer->Add(_progress_panel, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER); + + _result_panel = new VerifyDCPResultPanel(this); + overall_sizer->Add(_result_panel, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER); + + SetSizerAndFit(overall_sizer); + + _dcp->Changed.connect(boost::bind(&DOMFrame::setup_sensitivity, this)); + _verify->bind(&DOMFrame::verify_clicked, this); + + setup_sensitivity(); + } + +private: + void setup_sensitivity() + { + _verify->Enable(!_dcp->GetPath().IsEmpty()); + } + + void verify_clicked() + { + auto dcp = boost::filesystem::path(wx_to_std(_dcp->GetPath())); + if (dcp.empty()) { + return; + } + + auto job_manager = JobManager::instance(); + auto job = make_shared<VerifyDCPJob>(std::vector<boost::filesystem::path>{dcp}, std::vector<boost::filesystem::path>()); + job_manager->add(job); + + while (job_manager->work_to_do()) { + wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT); + dcpomatic_sleep_seconds(1); + + _progress_panel->update(job); + } + + _result_panel->fill(job); + if (_write_log->get()) { + dcp::TextFormatter formatter(dcp / "REPORT.txt"); + dcp::verify_report(job->result(), formatter); + } + } + + DirPickerCtrl* _dcp; + CheckBox* _write_log; + Button* _verify; + VerifyDCPProgressPanel* _progress_panel; + VerifyDCPResultPanel* _result_panel; +}; + + +/** @class App + * @brief The magic App class for wxWidgets. + */ +class App : public wxApp +{ +public: + App() + : wxApp() + { + dcpomatic_setup_path_encoding(); +#ifdef DCPOMATIC_LINUX + XInitThreads(); +#endif + } + +private: + bool OnInit() override + { + try { + SetAppName(_("DCP-o-matic Verifier")); + + if (!wxApp::OnInit()) { + return false; + } + +#ifdef DCPOMATIC_LINUX + unsetenv("UBUNTU_MENUPROXY"); +#endif + +#ifdef DCPOMATIC_OSX + dcpomatic_sleep_seconds(1); + make_foreground_application(); +#endif + + /* Enable i18n; this will create a Config object + to look for a force-configured language. This Config + object will be wrong, however, because dcpomatic_setup + hasn't yet been called and there aren't any filters etc. + set up yet. + */ + dcpomatic_setup_i18n(); + + /* Set things up, including filters etc. + which will now be internationalised correctly. + */ + dcpomatic_setup(); + + /* Force the configuration to be re-loaded correctly next + time it is needed. + */ + Config::drop(); + + _frame = new DOMFrame(_("DCP-o-matic Verifier")); + SetTopWindow(_frame); + _frame->SetSize({480, 640}); + _frame->Show(); + } + catch (exception& e) + { + error_dialog(nullptr, wxString::Format("DCP-o-matic Verifier could not start."), std_to_wx(e.what())); + } + + return true; + } + + void report_exception() + { + try { + throw; + } catch (FileError& e) { + error_dialog( + nullptr, + wxString::Format( + _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM, + std_to_wx(e.what()), + std_to_wx(e.file().string().c_str()) + ) + ); + } catch (boost::filesystem::filesystem_error& e) { + error_dialog( + nullptr, + wxString::Format( + _("An exception occurred: %s (%s) (%s)\n\n") + REPORT_PROBLEM, + std_to_wx(e.what()), + std_to_wx(e.path1().string()), + std_to_wx(e.path2().string()) + ) + ); + } catch (exception& e) { + error_dialog( + nullptr, + wxString::Format( + _("An exception occurred: %s.\n\n") + REPORT_PROBLEM, + std_to_wx(e.what()) + ) + ); + } catch (...) { + error_dialog(nullptr, _("An unknown exception occurred.") + " " + REPORT_PROBLEM); + } + } + + /* An unhandled exception has occurred inside the main event loop */ + bool OnExceptionInMainLoop() override + { + report_exception(); + return false; + } + + void OnUnhandledException() override + { + report_exception(); + } + + DOMFrame* _frame = nullptr; +}; + + +IMPLEMENT_APP(App) diff --git a/src/tools/wscript b/src/tools/wscript index c3b2b5fe0..65e4d0e4c 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -62,7 +62,15 @@ def build(bld): gui_tools = [] if not bld.env.DISABLE_GUI: - gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist', 'dcpomatic_combiner', 'dcpomatic_editor'] + gui_tools = ['dcpomatic', + 'dcpomatic_batch', + 'dcpomatic_server', + 'dcpomatic_kdm', + 'dcpomatic_player', + 'dcpomatic_playlist', + 'dcpomatic_combiner', + 'dcpomatic_editor', + 'dcpomatic_verifier'] if bld.env.ENABLE_DISK: gui_tools.append('dcpomatic_disk') diff --git a/src/wx/verify_dcp_result_dialog.cc b/src/wx/verify_dcp_result_dialog.cc index 9617878c5..806eac85a 100644 --- a/src/wx/verify_dcp_result_dialog.cc +++ b/src/wx/verify_dcp_result_dialog.cc @@ -44,5 +44,4 @@ VerifyDCPResultDialog::VerifyDCPResultDialog(wxWindow* parent, shared_ptr<Verify SetSizer (sizer); sizer->Layout (); sizer->SetSizeHints (this); - } diff --git a/src/wx/verify_dcp_result_panel.cc b/src/wx/verify_dcp_result_panel.cc index 4920ab350..bfeb14640 100644 --- a/src/wx/verify_dcp_result_panel.cc +++ b/src/wx/verify_dcp_result_panel.cc @@ -19,11 +19,14 @@ */ +#include "dcpomatic_button.h" +#include "file_dialog.h" #include "verify_dcp_result_panel.h" #include "wx_util.h" #include "lib/verify_dcp_job.h" #include <dcp/raw_convert.h> #include <dcp/verify.h> +#include <dcp/verify_report.h> #include <dcp/warnings.h> LIBDCP_DISABLE_WARNINGS #include <wx/richtext/richtextctrl.h> @@ -56,6 +59,13 @@ VerifyDCPResultPanel::VerifyDCPResultPanel(wxWindow* parent) _summary = new wxStaticText(this, wxID_ANY, wxT("")); sizer->Add(_summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER); + auto save_sizer = new wxBoxSizer(wxHORIZONTAL); + _save_text_report = new Button(this, _("Save report as text...")); + save_sizer->Add(_save_text_report, 0, wxALL, DCPOMATIC_SIZER_GAP); + _save_html_report = new Button(this, _("Save report as HTML...")); + save_sizer->Add(_save_html_report, 0, wxALL, DCPOMATIC_SIZER_GAP); + sizer->Add(save_sizer); + SetSizer(sizer); sizer->Layout(); sizer->SetSizeHints(this); @@ -63,13 +73,19 @@ VerifyDCPResultPanel::VerifyDCPResultPanel(wxWindow* parent) for (auto const& i: _pages) { i.second->GetCaret()->Hide(); } + + _save_text_report->bind(&VerifyDCPResultPanel::save_text_report, this); + _save_html_report->bind(&VerifyDCPResultPanel::save_html_report, this); + + _save_text_report->Enable(false); + _save_html_report->Enable(false); } void VerifyDCPResultPanel::fill(shared_ptr<VerifyDCPJob> job) { - if (job->finished_ok() && job->notes().empty()) { + if (job->finished_ok() && job->result().notes.empty()) { _summary->SetLabel(_("DCP validates OK.")); return; } @@ -132,7 +148,7 @@ VerifyDCPResultPanel::fill(shared_ptr<VerifyDCPJob> job) ++counts[dcp::VerificationNote::Type::ERROR]; } - for (auto i: job->notes()) { + for (auto i: job->result().notes) { switch (i.code()) { case dcp::VerificationNote::Code::FAILED_READ: add(i, _("Could not read DCP (%n)")); @@ -448,6 +464,19 @@ VerifyDCPResultPanel::fill(shared_ptr<VerifyDCPJob> job) case dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT: add(i, _("The <LabelText> in a <ContentVersion> in CPL %id is empty")); break; + case dcp::VerificationNote::Code::MATCHING_CPL_HASHES: + case dcp::VerificationNote::Code::CORRECT_PICTURE_HASH: + case dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES: + case dcp::VerificationNote::Code::VALID_RELEASE_TERRITORY: + case dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT: + case dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL: + case dcp::VerificationNote::Code::ALL_ENCRYPTED: + case dcp::VerificationNote::Code::NONE_ENCRYPTED: + case dcp::VerificationNote::Code::VALID_CONTENT_KIND: + case dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA: + case dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT: + /* These are all "OK" messages which we don't report here */ + break; } } @@ -490,5 +519,39 @@ VerifyDCPResultPanel::fill(shared_ptr<VerifyDCPJob> job) if (counts[dcp::VerificationNote::Type::WARNING] == 0) { add_bullet(dcp::VerificationNote::Type::WARNING, _("No warnings found.")); } + + _job = job; + _save_text_report->Enable(true); + _save_html_report->Enable(true); +} + + +template <class T> +void save(wxWindow* parent, wxString filter, dcp::VerificationResult const& result) +{ + FileDialog dialog(parent, _("Verification report"), filter, wxFD_SAVE | wxFD_OVERWRITE_PROMPT, "SaveVerificationReport"); + if (!dialog.show()) { + return; + } + + T formatter(dialog.path()); + dcp::verify_report(result, formatter); } + +void +VerifyDCPResultPanel::save_text_report() +{ + if (_job) { + save<dcp::TextFormatter>(this, wxT("Text files (*.txt)|*.txt"), _job->result()); + } +} + + +void +VerifyDCPResultPanel::save_html_report() +{ + if (_job) { + save<dcp::HTMLFormatter>(this, wxT("HTML files (*.htm;*html)|*.htm;*.html"), _job->result()); + } +} diff --git a/src/wx/verify_dcp_result_panel.h b/src/wx/verify_dcp_result_panel.h index f0a502064..8cf92118b 100644 --- a/src/wx/verify_dcp_result_panel.h +++ b/src/wx/verify_dcp_result_panel.h @@ -25,6 +25,7 @@ #include <memory> +class Button; class VerifyDCPJob; class wxRichTextCtrl; @@ -37,6 +38,13 @@ public: void fill(std::shared_ptr<VerifyDCPJob> job); private: + void save_text_report(); + void save_html_report(); + wxStaticText* _summary; std::map<dcp::VerificationNote::Type, wxRichTextCtrl*> _pages; + Button* _save_text_report; + Button* _save_html_report; + + std::shared_ptr<VerifyDCPJob> _job; }; |
