diff options
| author | Carl Hetherington <cth@carlh.net> | 2024-04-11 10:50:51 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2024-04-15 10:59:34 +0200 |
| commit | 3ab56573dbfb395fea493096182dc14c09fb961f (patch) | |
| tree | 5c3480e6a96156b62a88335ffe5de9e8f6993739 | |
| parent | 0873610bb24b997545bd6d3c99a8f653ce74f805 (diff) | |
Add basic verification report.v1.9.5
| -rw-r--r-- | src/verify_report.cc | 144 | ||||
| -rw-r--r-- | src/verify_report.h | 237 | ||||
| -rw-r--r-- | src/wscript | 2 | ||||
| -rw-r--r-- | test/data/text_formatter.txt | 8 | ||||
| -rw-r--r-- | test/data/text_formatter_windows.txt | 8 | ||||
| -rw-r--r-- | test/verify_report_test.cc | 67 | ||||
| -rw-r--r-- | test/wscript | 1 | ||||
| -rw-r--r-- | tools/dcpverify.cc | 19 |
8 files changed, 482 insertions, 4 deletions
diff --git a/src/verify_report.cc b/src/verify_report.cc new file mode 100644 index 00000000..2201b8fa --- /dev/null +++ b/src/verify_report.cc @@ -0,0 +1,144 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + + This file is part of libdcp. + + libdcp 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. + + libdcp 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 libdcp. If not, see <http://www.gnu.org/licenses/>. + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#include "compose.hpp" +#include "cpl.h" +#include "dcp.h" +#include "file.h" +#include "reel.h" +#include "reel_picture_asset.h" +#include "reel_sound_asset.h" +#include "reel_subtitle_asset.h" +#include "verify.h" +#include "verify_report.h" + + +using std::shared_ptr; +using std::string; +using std::vector; +using boost::optional; +using namespace dcp; + + +void write_line(File& file, string format) +{ + file.puts(string(format + "\n").c_str()); +} + + +template <typename... Args> +void write_line(File& file, string format, Args... args) +{ + file.puts(String::compose(format + "\n", std::forward<Args>(args)...).c_str()); +} + + +void +dcp::verify_report(dcp::VerificationResult const& result, Formatter& formatter) +{ + auto document = formatter.document(); + auto body = formatter.body(); + + formatter.heading("DCP verification report"); + + if (result.dcps.size() > 1) { + formatter.subheading("DCPs"); + } else { + formatter.subheading("DCP"); + } + + auto reel_asset_details = [&formatter](shared_ptr<dcp::ReelAsset> asset) { + formatter.list_item(String::compose("UUID: %1", asset->id())); + formatter.list_item(String::compose("Intrinsic duration: %1", asset->intrinsic_duration())); + formatter.list_item(String::compose("Entry point: %1", asset->entry_point().get_value_or(0))); + formatter.list_item(String::compose("Duration: %1", asset->duration().get_value_or(0))); + if (asset->annotation_text()) { + formatter.list_item(String::compose("Annotation text: %1", *asset->annotation_text())); + } + }; + + auto write_notes = [&formatter](dcp::VerificationResult const& result, optional<string> cpl_id) { + for (auto note: result.notes) { + if (note.cpl_id() == cpl_id) { + auto const note_as_string = dcp::note_to_string(note, formatter.process_string(), formatter.process_filename()); + if (note.type() == dcp::VerificationNote::Type::OK) { + formatter.list_item(note_as_string, string("ok")); + } else if (note.type() == dcp::VerificationNote::Type::WARNING) { + formatter.list_item(note_as_string, string("warning")); + } else if (note.type() == dcp::VerificationNote::Type::ERROR) { + formatter.list_item(note_as_string, string("error")); + } + } + } + }; + + for (auto dcp: result.dcps) { + auto ul = formatter.unordered_list(); + for (auto cpl: dcp->cpls()) { + formatter.list_item(String::compose("CPL ID: %1", cpl->id())); + int reel_index = 1; + for (auto reel: cpl->reels()) { + formatter.list_item(String::compose("Reel: %1", reel_index++)); + auto ul2 = formatter.unordered_list(); + if (auto pic = reel->main_picture()) { + formatter.list_item("Main picture"); + auto ul3 = formatter.unordered_list(); + reel_asset_details(pic); + formatter.list_item(String::compose("Frame rate: %1", pic->frame_rate().numerator)); + formatter.list_item(String::compose("Screen aspect ratio: %1x%2", pic->screen_aspect_ratio().numerator, pic->screen_aspect_ratio().denominator)); + } + if (auto sound = reel->main_sound()) { + formatter.list_item("Main sound"); + auto ul3 = formatter.unordered_list(); + reel_asset_details(sound); + } + if (auto sub = reel->main_subtitle()) { + formatter.list_item("Main subtitle"); + auto ul3 = formatter.unordered_list(); + reel_asset_details(sub); + if (sub->language()) { + formatter.list_item(String::compose("Language: %1", *sub->language())); + } + } + } + write_notes(result, cpl->id()); + } + } + + if (std::count_if(result.notes.begin(), result.notes.end(), [](VerificationNote const& note) { return !note.cpl_id(); }) > 0) { + formatter.subheading("Report"); + write_notes(result, {}); + } +} + diff --git a/src/verify_report.h b/src/verify_report.h new file mode 100644 index 00000000..b8ca5516 --- /dev/null +++ b/src/verify_report.h @@ -0,0 +1,237 @@ +/* + Copyright (C) 2022 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/>. + +*/ + + +#include "compose.hpp" +#include "file.h" +#include "verify.h" +#include <boost/filesystem.hpp> +#include <vector> + + +namespace dcp { + + +class Formatter +{ +public: + Formatter(boost::filesystem::path file) + : _file(file, "w") + {} + + class Wrap + { + public: + Wrap() = default; + + Wrap(Formatter* formatter, std::string const& close) + : _formatter(formatter) + , _close(close) + {} + + Wrap(Formatter* formatter, std::string const& close, std::function<void ()> closer) + : _formatter(formatter) + , _close(close) + , _closer(closer) + {} + + Wrap(Wrap&& other) + { + std::swap(_formatter, other._formatter); + std::swap(_close, other._close); + std::swap(_closer, other._closer); + } + + ~Wrap() + { + if (_formatter) { + _formatter->file().puts(_close.c_str()); + } + if (_closer) { + _closer(); + } + } + + private: + Formatter* _formatter = nullptr; + std::string _close; + std::function<void ()> _closer = nullptr; + }; + + virtual Wrap document() { return {}; } + + virtual void heading(std::string const& text) = 0; + virtual void subheading(std::string const& text) = 0; + virtual Wrap body() { return {}; } + + virtual Wrap unordered_list() = 0; + virtual void list_item(std::string const& text, boost::optional<std::string> type = {}) = 0; + + virtual std::function<std::string (std::string)> process_string() = 0; + virtual std::function<std::string (std::string)> process_filename() = 0; + + dcp::File& file() { + return _file; + } + +protected: + dcp::File _file; +}; + + +class TextFormatter : public Formatter +{ +public: + TextFormatter(boost::filesystem::path file) + : Formatter(file) + {} + + void heading(std::string const& text) override { + print(text); + } + + void subheading(std::string const& text) override { + print(""); + print(text); + } + + Wrap unordered_list() override { + _indent++; + return Wrap(this, "", [this]() { _indent--; }); + } + + void list_item(std::string const& text, boost::optional<std::string> type = {}) override { + LIBDCP_UNUSED(type); + for (int i = 0; i < _indent * 2; ++i) { + _file.puts(" "); + } + _file.puts("* "); + print(text); + } + + std::function<std::string (std::string)> process_string() override { + return [](std::string s) { + return s; + }; + } + + std::function<std::string (std::string)> process_filename() override { + return [](std::string s) { + return s; + }; + } + +private: + void print(std::string const& text) { + _file.puts(text.c_str()); + _file.puts("\n"); + } + + int _indent = 0; +}; + + +class HTMLFormatter : public Formatter +{ +public: + HTMLFormatter(boost::filesystem::path file) + : Formatter(file) + {} + + void heading(std::string const& text) override { + tagged("h1", text); + } + + void subheading(std::string const& text) override { + tagged("h2", text); + } + + Wrap document() override { + auto html = wrapped("html"); + auto head = wrapped("head"); + auto style = wrapped("style"); + _file.puts("li {\n" + " margin: 2px;\n" + " padding: 2px 2px 2px 1em;\n" + "}\n" + ); + _file.puts("li.ok {\n" + " background-color: #00ff00;\n" + "}\n" + "li.warning {\n" + " background-color: #ffa500;\n" + "}\n" + "li.error {\n" + " background-color: #ff0000;\n" + "}\n" + "ul {\n" + " list-style: none;\n" + "}\n" + ); + return html; + } + + Wrap body() override { + return wrapped("body"); + } + + Wrap unordered_list() override { + return wrapped("ul"); + } + + void list_item(std::string const& text, boost::optional<std::string> type = {}) override { + if (type) { + _file.puts(dcp::String::compose("<li class=\"%1\">%2", *type, text).c_str()); + } else { + _file.puts(dcp::String::compose("<li>%1", text).c_str()); + } + } + + std::function<std::string (std::string)> process_string() override { + return [](std::string s) { + boost::replace_all(s, "<", "<"); + boost::replace_all(s, ">", ">"); + return s; + }; + } + + std::function<std::string (std::string)> process_filename() override { + return [](std::string s) { + return String::compose("<code>%1</code>", s); + }; + } + +private: + void tagged(std::string tag, std::string content) { + _file.puts(String::compose("<%1>%2</%3>\n", tag, content, tag).c_str()); + }; + + Wrap wrapped(std::string const& tag) { + _file.puts(String::compose("<%1>", tag).c_str()); + return Wrap(this, String::compose("</%1>", tag)); + }; +}; + + +extern void verify_report(dcp::VerificationResult const& result, Formatter& formatter); + + +} + diff --git a/src/wscript b/src/wscript index 29eb37ab..adaa57bb 100644 --- a/src/wscript +++ b/src/wscript @@ -125,6 +125,7 @@ def build(bld): v_align.cc verify.cc verify_j2k.cc + verify_report.cc version.cc """ @@ -232,6 +233,7 @@ def build(bld): v_align.h verify.h verify_j2k.h + verify_report.h version.h warnings.h """ diff --git a/test/data/text_formatter.txt b/test/data/text_formatter.txt new file mode 100644 index 00000000..8861db99 --- /dev/null +++ b/test/data/text_formatter.txt @@ -0,0 +1,8 @@ +Heading + +Subheading + * Foo + * Bar + * Fred + * Jim + * Sheila diff --git a/test/data/text_formatter_windows.txt b/test/data/text_formatter_windows.txt new file mode 100644 index 00000000..16d78d84 --- /dev/null +++ b/test/data/text_formatter_windows.txt @@ -0,0 +1,8 @@ +Heading
+
+Subheading
+ * Foo
+ * Bar
+ * Fred
+ * Jim
+ * Sheila
diff --git a/test/verify_report_test.cc b/test/verify_report_test.cc new file mode 100644 index 00000000..6b288891 --- /dev/null +++ b/test/verify_report_test.cc @@ -0,0 +1,67 @@ +/* + Copyright (C) 2022 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/>. + +*/ + + +#include "verify.h" +#include "verify_report.h" +#include <boost/test/unit_test.hpp> +#include "test.h" + + +BOOST_AUTO_TEST_CASE(verify_report_basically_ok) +{ + dcp::HTMLFormatter formatter("build/test/verify_report_basically_ok.html"); + dcp::verify_report( + dcp::verify( + { private_test / "TONEPLATES-SMPTE-PLAINTEXT_TST_F_XX-XX_ITL-TD_51-XX_2K_WOE_20111001_WOE_OV" }, + {}, + [](std::string, boost::optional<boost::filesystem::path>) {}, + [](float) {}, + {}, + xsd_test + ), + formatter + ); +} + + +BOOST_AUTO_TEST_CASE(text_formatter) +{ + { + dcp::TextFormatter fmt("build/test/text_formatter.txt"); + + fmt.heading("Heading"); + fmt.subheading("Subheading"); + auto A = fmt.unordered_list(); + fmt.list_item("Foo"); + fmt.list_item("Bar"); + auto B = fmt.unordered_list(); + fmt.list_item("Fred"); + fmt.list_item("Jim"); + fmt.list_item("Sheila"); + } + +#ifdef LIBDCP_WINDOWS + check_file("test/data/text_formatter_windows.txt", "build/test/text_formatter.txt"); +#else + check_file("test/data/text_formatter.txt", "build/test/text_formatter.txt"); +#endif +} + diff --git a/test/wscript b/test/wscript index e401f720..92c83598 100644 --- a/test/wscript +++ b/test/wscript @@ -117,6 +117,7 @@ def build(bld): utf8_test.cc v_align_test.cc verify_test.cc + verify_report_test.cc """ obj.target = 'tests' obj.install_path = '' diff --git a/tools/dcpverify.cc b/tools/dcpverify.cc index 1f747097..88f0d705 100644 --- a/tools/dcpverify.cc +++ b/tools/dcpverify.cc @@ -37,6 +37,7 @@ #include "filesystem.h" #include "raw_convert.h" #include "verify.h" +#include "verify_report.h" #include "version.h" #include <boost/bind/bind.hpp> #include <boost/filesystem.hpp> @@ -68,6 +69,7 @@ help (string n) << " --ignore-bv21-smpte don't give the SMPTE Bv2.1 error about a DCP not being SMPTE\n" << " --no-asset-hash-check don't check asset hashes\n" << " --asset-hash-check-maximum-size <size-in-MB> only check hashes for assets smaller than this size (in MB)\n" + << " -o <filename> write HTML report to filename\n" << " -q, --quiet don't report progress\n"; } @@ -80,6 +82,7 @@ main (int argc, char* argv[]) bool ignore_missing_assets = false; bool ignore_bv21_smpte = false; bool quiet = false; + boost::optional<boost::filesystem::path> report_filename; dcp::VerificationOptions verification_options; @@ -96,7 +99,7 @@ main (int argc, char* argv[]) { 0, 0, 0, 0 } }; - int c = getopt_long (argc, argv, "VhABCD:q", long_options, &option_index); + int c = getopt_long (argc, argv, "VhABCD:qo:", long_options, &option_index); if (c == -1) { break; @@ -126,6 +129,9 @@ main (int argc, char* argv[]) case 'q': quiet = true; break; + case 'o': + report_filename = optarg; + break; } } @@ -173,8 +179,8 @@ main (int argc, char* argv[]) vector<boost::filesystem::path> directories; directories.push_back (argv[optind]); - auto notes = dcp::verify(directories, {}, stage, progress, verification_options).notes; - dcp::filter_notes (notes, ignore_missing_assets); + auto result = dcp::verify(directories, {}, stage, progress, verification_options); + dcp::filter_notes(result.notes, ignore_missing_assets); if (!quiet) { cout << "\n"; @@ -183,7 +189,7 @@ main (int argc, char* argv[]) bool failed = false; bool bv21_failed = false; bool warned = false; - for (auto i: notes) { + for (auto i: result.notes) { if (ignore_bv21_smpte && i.code() == dcp::VerificationNote::Code::INVALID_STANDARD) { continue; } @@ -217,5 +223,10 @@ main (int argc, char* argv[]) } } + if (report_filename) { + dcp::HTMLFormatter formatter(*report_filename); + dcp::verify_report(result, formatter); + } + exit (failed ? EXIT_FAILURE : EXIT_SUCCESS); } |
