From: Carl Hetherington Date: Tue, 9 Apr 2024 00:02:28 +0000 (+0200) Subject: Separate GUI verifier with basic reporting (#1823). X-Git-Tag: v2.17.16~10 X-Git-Url: https://git.carlh.net/gitweb/?a=commitdiff_plain;h=af20e21e2363f7c4d5f7031c444984f383c26914;hp=39960bc88eee794ade1a73b00523e749945b9eab;p=dcpomatic.git Separate GUI verifier with basic reporting (#1823). --- diff --git a/cscript b/cscript index 4bef0f3d7..5d931f471 100644 --- a/cscript +++ b/cscript @@ -431,6 +431,7 @@ def make_spec(filename, version, target, options, requires=None): print('%{_bindir}/dcpomatic2_openssl', file=f) print('%{_bindir}/dcpomatic2_combiner', file=f) print('%{_bindir}/dcpomatic2_verify_cli', file=f) + print('%{_bindir}/dcpomatic2_verifier', file=f) print('%{_bindir}/dcpomatic2_kdm_inspect', file=f) print('%{_bindir}/dcpomatic2_map', file=f) if can_build_disk(target): @@ -439,6 +440,7 @@ def make_spec(filename, version, target, options, requires=None): print('%{_datadir}/applications/dcpomatic2.desktop', file=f) print('%{_datadir}/applications/dcpomatic2_batch.desktop', file=f) print('%{_datadir}/applications/dcpomatic2_editor.desktop', file=f) + print('%{_datadir}/applications/dcpomatic2_verifier.desktop', file=f) print('%{_datadir}/applications/dcpomatic2_server.desktop', file=f) print('%{_datadir}/applications/dcpomatic2_kdm.desktop', file=f) print('%{_datadir}/applications/dcpomatic2_player.desktop', file=f) @@ -472,6 +474,7 @@ def make_spec(filename, version, target, options, requires=None): print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2.png' % r, file=f) print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_batch.png' % r, file=f) print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_editor.png' % r, file=f) + print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_verifier.png' % r, file=f) print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_kdm.png' % r, file=f) print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_server.png' % r, file=f) print('%%{_datadir}/icons/hicolor/%s/apps/dcpomatic2_player.png' % r, file=f) @@ -535,7 +538,7 @@ def dependencies(target, options): # Use distro-provided FFmpeg on Arch deps = [] - deps.append(('libdcp', '56b3586247d76b2d07911018ccba63d4b05f4995', {'c++17': target.platform == 'osx'})) + deps.append(('libdcp', 'v1.9.5', {'c++17': target.platform == 'osx'})) deps.append(('libsub', 'v1.6.47')) deps.append(('leqm-nrt', '30dcaea1373ac62fba050e02ce5b0c1085797a23')) deps.append(('rtaudio', 'f619b76')) @@ -874,6 +877,7 @@ def package(target, version, options): out.append(make_appimage(target, 'DCP-o-matic Encode Server', 'dcpomatic2_server', version)) out.append(make_appimage(target, 'DCP-o-matic Combiner', 'dcpomatic2_combiner', version)) out.append(make_appimage(target, 'DCP-o-matic Editor', 'dcpomatic2_editor', version)) + out.append(make_appimage(target, 'DCP-o-matic Verifier', 'dcpomatic2_verifier', version)) return out else: if target.bits == 32: diff --git a/graphics/linux/128/dcpomatic2_verifier.png b/graphics/linux/128/dcpomatic2_verifier.png new file mode 100644 index 000000000..d28ceb27b Binary files /dev/null and b/graphics/linux/128/dcpomatic2_verifier.png differ diff --git a/graphics/linux/16/dcpomatic2_verifier.png b/graphics/linux/16/dcpomatic2_verifier.png new file mode 100644 index 000000000..9ad2929dc Binary files /dev/null and b/graphics/linux/16/dcpomatic2_verifier.png differ diff --git a/graphics/linux/22/dcpomatic2_verifier.png b/graphics/linux/22/dcpomatic2_verifier.png new file mode 100644 index 000000000..dbe6cbd4d Binary files /dev/null and b/graphics/linux/22/dcpomatic2_verifier.png differ diff --git a/graphics/linux/256/dcpomatic2_verifier.png b/graphics/linux/256/dcpomatic2_verifier.png new file mode 100644 index 000000000..f5198a733 Binary files /dev/null and b/graphics/linux/256/dcpomatic2_verifier.png differ diff --git a/graphics/linux/32/dcpomatic2_verifier.png b/graphics/linux/32/dcpomatic2_verifier.png new file mode 100644 index 000000000..61afb5f9f Binary files /dev/null and b/graphics/linux/32/dcpomatic2_verifier.png differ diff --git a/graphics/linux/48/dcpomatic2_verifier.png b/graphics/linux/48/dcpomatic2_verifier.png new file mode 100644 index 000000000..5f5188ea3 Binary files /dev/null and b/graphics/linux/48/dcpomatic2_verifier.png differ diff --git a/graphics/linux/512/dcpomatic2_verifier.png b/graphics/linux/512/dcpomatic2_verifier.png new file mode 100644 index 000000000..b8417ed85 Binary files /dev/null and b/graphics/linux/512/dcpomatic2_verifier.png differ diff --git a/graphics/linux/64/dcpomatic2_verifier.png b/graphics/linux/64/dcpomatic2_verifier.png new file mode 100644 index 000000000..b17249b55 Binary files /dev/null and b/graphics/linux/64/dcpomatic2_verifier.png differ diff --git a/graphics/osx/dcpomatic2_verifier.icns b/graphics/osx/dcpomatic2_verifier.icns new file mode 100644 index 000000000..ffd23360c Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.icns differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.png new file mode 100644 index 000000000..d28ceb27b Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.png new file mode 100644 index 000000000..d28ceb27b Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_128x128@2x.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.png new file mode 100644 index 000000000..9ad2929dc Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.png new file mode 100644 index 000000000..9ad2929dc Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_16x16@2x.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.png new file mode 100644 index 000000000..f5198a733 Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.png new file mode 100644 index 000000000..f5198a733 Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_256x256@2x.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.png new file mode 100644 index 000000000..61afb5f9f Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.png new file mode 100644 index 000000000..61afb5f9f Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_32x32@2x.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.png new file mode 100644 index 000000000..b8417ed85 Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512.png differ diff --git a/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.png new file mode 100644 index 000000000..b8417ed85 Binary files /dev/null and b/graphics/osx/dcpomatic2_verifier.iconset/icon_512x512@2x.png differ diff --git a/graphics/src/dcpomatic2_verifier.svg b/graphics/src/dcpomatic2_verifier.svg new file mode 100644 index 000000000..5572f0a61 --- /dev/null +++ b/graphics/src/dcpomatic2_verifier.svg @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/graphics/update b/graphics/update index 46964470b..f04ce3fea 100755 --- a/graphics/update +++ b/graphics/update @@ -25,7 +25,7 @@ function required_font() fi } -svg_apps="dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist dcpomatic2_disk dcpomatic2_combiner dcpomatic2_editor" +svg_apps="dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist dcpomatic2_disk dcpomatic2_combiner dcpomatic2_editor dcpomatic2_verifier" if [ `uname -s` == "Darwin" ]; then diff --git a/graphics/windows/dcpomatic2_verifier.ico b/graphics/windows/dcpomatic2_verifier.ico new file mode 100644 index 000000000..2a85de433 Binary files /dev/null and b/graphics/windows/dcpomatic2_verifier.ico differ diff --git a/graphics/wscript b/graphics/wscript index 663e28287..287a6dec4 100644 --- a/graphics/wscript +++ b/graphics/wscript @@ -32,7 +32,8 @@ def build(bld): 'dcpomatic2_playlist', 'dcpomatic2_disk', 'dcpomatic2_combiner', - 'dcpomatic2_editor']: + 'dcpomatic2_editor', + 'dcpomatic2_verifier']: bld.install_files('${PREFIX}/share/icons/hicolor/%dx%d/apps' % (r, r), 'linux/%d/%s.png' % (r, p)) # Install stuff for POSIX systems diff --git a/platform/linux/dcpomatic_verifier.desktop.in b/platform/linux/dcpomatic_verifier.desktop.in new file mode 100644 index 000000000..b52a6fc3d --- /dev/null +++ b/platform/linux/dcpomatic_verifier.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=1.0 +Type=Application +Terminal=false +Exec=@INSTALL_PREFIX@/bin/dcpomatic2_verifier +Name=DCP-o-matic 2 Verifier +Icon=dcpomatic2_verifier +Comment=DCP generator +Categories=AudioVideo;Video diff --git a/platform/linux/wscript b/platform/linux/wscript index 98aff905d..4a3516fd0 100644 --- a/platform/linux/wscript +++ b/platform/linux/wscript @@ -16,6 +16,7 @@ def build(bld): desktop(bld, '_playlist'), desktop(bld, '_combiner'), desktop(bld, '_editor'), + desktop(bld, '_verifier'), ] if bld.env.ENABLE_DISK: diff --git a/platform/osx/dcpomatic2_verifier.Info.plist.in b/platform/osx/dcpomatic2_verifier.Info.plist.in new file mode 100644 index 000000000..d88a8ce50 --- /dev/null +++ b/platform/osx/dcpomatic2_verifier.Info.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + dcpomatic2_verifier + CFBundleGetInfoString + DCP-o-matic 2 Verifier + CFBundleIconFile + dcpomatic2_verifier.icns + CFBundleIdentifier + com.dcpomatic.verifier + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DCP-o-matic 2 Verifier + CFBundlePackageType + APPL + CFBundleShortVersions + @VERSION@ + CFBundleSignature + DOMC + CFBundleVersion + @VERSION@ + CFBundleAllowMixedLocalizations + + LSUIElement + 0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh index be4394649..a223556af 100644 --- a/platform/osx/make_dmg.sh +++ b/platform/osx/make_dmg.sh @@ -7,7 +7,7 @@ SYNTAX="make_dmg.sh -e -r -i -p %HOMEPATH%/Documents/dcpomatic_debug_log.txt diff --git a/platform/windows/dcpomatic_verifier.rc b/platform/windows/dcpomatic_verifier.rc new file mode 100644 index 000000000..8f1ba073f --- /dev/null +++ b/platform/windows/dcpomatic_verifier.rc @@ -0,0 +1,2 @@ +id ICON "../../graphics/windows/dcpomatic2_verifier.ico" +#include "wx-3.1/wx/msw/wx.rc" diff --git a/platform/windows/wscript b/platform/windows/wscript index 13927f170..c6718db41 100644 --- a/platform/windows/wscript +++ b/platform/windows/wscript @@ -22,6 +22,7 @@ def write_installer(bits, dcpomatic_version, debug, disk): ('combiner', 'Combiner'), ('editor', 'Editor'), ('map', 'Map'), + ('verifier', 'Verifier'), ] if disk: diff --git a/run/dcpomatic_verifier b/run/dcpomatic_verifier new file mode 100755 index 000000000..a87c3d4dc --- /dev/null +++ b/run/dcpomatic_verifier @@ -0,0 +1,24 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source $DIR/environment +binary=$build/src/tools/dcpomatic2_verifier + +if [[ "$(uname -m)" == arm64 ]]; then + env=arm64/11.0 +else + env=x86_64/10.10 +fi + +export DYLD_LIBRARY_PATH=/Users/cah/osx-environment/$env/lib:/usr/local/lib + +if [ "$1" == "--debug" ]; then + shift + if [[ "$(uname)" == Darwin ]]; then + /Applications/Xcode.app/Contents/Developer/usr/bin/lldb $binary $* + else + gdb --args $binary $* + fi +else + $binary $* 2> >(grep -v Gtk-CRITICAL | grep -v Gtk-WARNING) +fi 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 notes () const { - return _notes; + dcp::VerificationResult const& result() const { + return _result; } private: @@ -45,5 +45,5 @@ private: std::vector _directories; std::vector _kdms; - std::vector _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 + + 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 . + +*/ + + +/** @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 +LIBDCP_DISABLE_WARNINGS +#include +#include +LIBDCP_ENABLE_WARNINGS +#ifdef __WXGTK__ +#include +#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(std::vector{dcp}, std::vector()); + 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_ptrLayout (); 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 #include +#include #include LIBDCP_DISABLE_WARNINGS #include @@ -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 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 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 job) case dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT: add(i, _("The in a 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 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 +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(this, wxT("Text files (*.txt)|*.txt"), _job->result()); + } +} + + +void +VerifyDCPResultPanel::save_html_report() +{ + if (_job) { + save(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 +class Button; class VerifyDCPJob; class wxRichTextCtrl; @@ -37,6 +38,13 @@ public: void fill(std::shared_ptr job); private: + void save_text_report(); + void save_html_report(); + wxStaticText* _summary; std::map _pages; + Button* _save_text_report; + Button* _save_html_report; + + std::shared_ptr _job; }; diff --git a/test/reels_test.cc b/test/reels_test.cc index d4a783f91..df4bbbbe6 100644 --- a/test/reels_test.cc +++ b/test/reels_test.cc @@ -54,6 +54,14 @@ using std::vector; using namespace dcpomatic; +static +void +filter_ok(std::vector& notes) +{ + notes.erase(std::remove_if(notes.begin(), notes.end(), [](dcp::VerificationNote const& note) { return note.type() == dcp::VerificationNote::Type::OK; }), notes.end()); +} + + /** Test Film::reels() */ BOOST_AUTO_TEST_CASE (reels_test1) { @@ -508,9 +516,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short1) make_and_verify_dcp (film); vector dirs = { film->dir(film->dcp_name(false)) }; - auto notes = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); - dump_notes (notes); - BOOST_REQUIRE (notes.empty()); + auto result = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); + filter_ok(result.notes); + dump_notes(result.notes); + BOOST_REQUIRE(result.notes.empty()); } @@ -533,9 +542,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short2) make_and_verify_dcp (film); vector dirs = { film->dir(film->dcp_name(false)) }; - auto const notes = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); - dump_notes (notes); - BOOST_REQUIRE (notes.empty()); + auto result = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); + filter_ok(result.notes); + dump_notes(result.notes); + BOOST_REQUIRE(result.notes.empty()); } @@ -554,9 +564,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short3) make_and_verify_dcp (film); - auto const notes = dcp::verify({}, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); - dump_notes (notes); - BOOST_REQUIRE (notes.empty()); + auto result = dcp::verify({}, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); + filter_ok(result.notes); + dump_notes(result.notes); + BOOST_REQUIRE(result.notes.empty()); } @@ -584,9 +595,10 @@ BOOST_AUTO_TEST_CASE (reels_should_not_be_short4) BOOST_REQUIRE (!wait_for_jobs()); vector dirs = { film->dir(film->dcp_name(false)) }; - auto const notes = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); - dump_notes (notes); - BOOST_REQUIRE (notes.empty()); + auto result = dcp::verify(dirs, {}, boost::bind(&no_op), boost::bind(&no_op), {}, TestPaths::xsd()); + filter_ok(result.notes); + dump_notes(result.notes); + BOOST_REQUIRE(result.notes.empty()); } diff --git a/test/test.cc b/test/test.cc index ff4a3c9c0..4064f9b0e 100644 --- a/test/test.cc +++ b/test/test.cc @@ -965,10 +965,10 @@ void progress (float) {} void verify_dcp(boost::filesystem::path dir, vector ignore) { - auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, TestPaths::xsd()); + auto result = dcp::verify({dir}, {}, &stage, &progress, {}, TestPaths::xsd()); bool ok = true; - for (auto i: notes) { - if (find(ignore.begin(), ignore.end(), i.code()) == ignore.end()) { + for (auto i: result.notes) { + if (i.type() != dcp::VerificationNote::Type::OK && find(ignore.begin(), ignore.end(), i.code()) == ignore.end()) { std::cout << "\t" << dcp::note_to_string(i) << "\n"; ok = false; }