2 Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
22 #include "verify_dcp_result_panel.h"
24 #include "lib/verify_dcp_job.h"
25 #include <dcp/raw_convert.h>
26 #include <dcp/verify.h>
27 #include <dcp/warnings.h>
28 LIBDCP_DISABLE_WARNINGS
29 #include <wx/richtext/richtextctrl.h>
30 #include <wx/notebook.h>
31 LIBDCP_ENABLE_WARNINGS
32 #include <boost/algorithm/string.hpp>
37 using std::shared_ptr;
42 VerifyDCPResultPanel::VerifyDCPResultPanel(wxWindow* parent, shared_ptr<VerifyDCPJob> job)
43 : wxPanel(parent, wxID_ANY)
45 auto sizer = new wxBoxSizer(wxVERTICAL);
46 auto notebook = new wxNotebook(this, wxID_ANY);
47 sizer->Add(notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
49 map<dcp::VerificationNote::Type, wxRichTextCtrl*> pages;
50 pages[dcp::VerificationNote::Type::ERROR] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
51 notebook->AddPage(pages[dcp::VerificationNote::Type::ERROR], _("Errors"));
52 pages[dcp::VerificationNote::Type::BV21_ERROR] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
53 notebook->AddPage(pages[dcp::VerificationNote::Type::BV21_ERROR], _("SMPTE Bv2.1 errors"));
54 pages[dcp::VerificationNote::Type::WARNING] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
55 notebook->AddPage(pages[dcp::VerificationNote::Type::WARNING], _("Warnings"));
57 auto summary = new wxStaticText(this, wxID_ANY, wxT(""));
58 sizer->Add(summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
62 sizer->SetSizeHints(this);
64 for (auto const& i: pages) {
65 i.second->GetCaret()->Hide();
68 if (job->finished_ok() && job->notes().empty()) {
69 summary->SetLabel(_("DCP validates OK."));
73 map<dcp::VerificationNote::Type, int> counts;
74 counts[dcp::VerificationNote::Type::WARNING] = 0;
75 counts[dcp::VerificationNote::Type::BV21_ERROR] = 0;
76 counts[dcp::VerificationNote::Type::ERROR] = 0;
78 auto add_bullet = [&pages](dcp::VerificationNote::Type type, wxString message) {
79 pages[type]->BeginStandardBullet(N_("standard/diamond"), 1, 50);
80 pages[type]->WriteText(message);
81 pages[type]->Newline();
82 pages[type]->EndStandardBullet();
85 auto add = [&counts, &add_bullet](dcp::VerificationNote note, wxString message) {
86 if (note.reference_hash()) {
87 message.Replace("%reference_hash", std_to_wx(note.reference_hash().get()));
89 if (note.calculated_hash()) {
90 message.Replace("%calculated_hash", std_to_wx(note.calculated_hash().get()));
93 message.Replace("%frame", std_to_wx(dcp::raw_convert<string>(note.frame().get())));
97 dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE)
101 message.Replace("%n", std_to_wx(note.note().get()));
104 message.Replace("%f", std_to_wx(note.file()->filename().string()));
107 message.Replace("%l", std_to_wx(dcp::raw_convert<string>(note.line().get())));
109 if (note.component()) {
110 message.Replace("%component", std_to_wx(dcp::raw_convert<string>(note.component().get())));
113 message.Replace("%size", std_to_wx(dcp::raw_convert<string>(note.size().get())));
116 message.Replace("%id", std_to_wx(note.id().get()));
118 if (note.other_id()) {
119 message.Replace("%other_id", std_to_wx(note.other_id().get()));
121 add_bullet(note.type(), message);
122 counts[note.type()]++;
125 if (job->finished_in_error() && job->error_summary() != "") {
126 /* We have an error that did not come from dcp::verify */
127 add_bullet(dcp::VerificationNote::Type::ERROR, std_to_wx(job->error_summary()));
128 ++counts[dcp::VerificationNote::Type::ERROR];
131 for (auto i: job->notes()) {
133 case dcp::VerificationNote::Code::FAILED_READ:
134 add(i, _("Could not read DCP (%n)"));
136 case dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES:
137 add(i, _("The hash (%reference_hash) of the CPL %n in the PKL does not agree with the CPL file (%calculated_hash). This probably means that the CPL file is corrupt."));
139 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE:
140 add(i, _("The picture in a reel has a frame rate of %n, which is not valid."));
142 case dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH:
143 add(i, _("The hash (%calculated_hash) of the picture asset %f does not agree with the PKL file (%reference_hash). This probably means that the asset file is corrupt."));
145 case dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES:
146 add(i, _("The PKL and CPL hashes disagree for picture asset %f."));
148 case dcp::VerificationNote::Code::INCORRECT_SOUND_HASH:
149 add(i, _("The hash (%calculated_hash) of the sound asset %f does not agree with the PKL file (%reference_hash). This probably means that the asset file is corrupt."));
151 case dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES:
152 add(i, _("The PKL and CPL hashes disagree for sound asset %f."));
154 case dcp::VerificationNote::Code::EMPTY_ASSET_PATH:
155 add(i, _("An asset has an empty path in the ASSETMAP."));
157 case dcp::VerificationNote::Code::MISSING_ASSET:
158 add(i, _("The asset %f is missing."));
160 case dcp::VerificationNote::Code::MISMATCHED_STANDARD:
161 add(i, _("Parts of the DCP are written according to the Interop standard and parts according to SMPTE."));
163 case dcp::VerificationNote::Code::INVALID_XML:
165 add(i, _("The XML in %f is malformed on line %l (%n)."));
167 add(i, _("The XML in %f is malformed (%n)."));
170 case dcp::VerificationNote::Code::MISSING_ASSETMAP:
171 add(i, _("No ASSETMAP or ASSETMAP.xml file was found."));
173 case dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION:
174 add(i, _("The asset %n has an intrinsic duration of less than 1 second, which is invalid."));
176 case dcp::VerificationNote::Code::INVALID_DURATION:
177 add(i, _("The asset %n has a duration of less than 1 second, which is invalid."));
179 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES:
180 add(i, _("At least one frame of the video asset %f is over the limit of 250Mbit/s."));
182 case dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES:
183 add(i, _("Frame %frame (timecode %timecode) in asset %f has an instantaneous bit rate that is close to the limit of 250Mbit/s."));
185 case dcp::VerificationNote::Code::EXTERNAL_ASSET:
186 add(i, _("This DCP refers to at the asset %n in another DCP (and perhaps others), so it is a \"version file\" (VF)"));
188 case dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD:
189 add(i, _("The asset %f is 3D but its MXF is marked as 2D."));
191 case dcp::VerificationNote::Code::INVALID_STANDARD:
192 add(i, _("This DCP uses the Interop standard, but it should be made with SMPTE."));
194 case dcp::VerificationNote::Code::INVALID_LANGUAGE:
195 add(i, _("The invalid language tag %n is used."));
197 case dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS:
198 add(i, _("The video asset %f uses the invalid image size %n."));
200 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K:
201 add(i, _("The video asset %f uses the invalid frame rate %n."));
203 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K:
204 add(i, _("The video asset %f uses the frame rate %n which is invalid for 4K video."));
206 case dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D:
207 add(i, _("The video asset %f uses the frame rate %n which is invalid for 3D video."));
209 case dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES:
210 add(i, _("The XML in the closed caption asset %f takes up %n bytes which is over the 256KB limit."));
212 case dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES:
213 add(i, _("The timed text asset %f takes up %n bytes which is over the 115MB limit."));
215 case dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES:
216 add(i, _("The fonts in the timed text asset %f take up %n bytes which is over the 10MB limit."));
218 case dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE:
219 add(i, _("The subtitle asset %f contains no <Language> tag."));
221 case dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES:
222 add(i, _("Not all subtitle assets specify the same <Language> tag."));
224 case dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME:
225 add(i, _("The subtitle asset %f contains no <StartTime> tag."));
227 case dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME:
228 add(i, _("The subtitle asset %f has a <StartTime> which is not zero."));
230 case dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME:
231 add(i, _("The first subtitle or closed caption happens before 4s into the first reel."));
233 case dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION:
234 add(i, _("At least one subtitle lasts less than 15 frames."));
236 case dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING:
237 add(i, _("At least one pair of subtitles is separated by less than 2 frames."));
239 case dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT:
240 add(i, _("There are more than 3 subtitle lines in at least one place."));
242 case dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH:
243 add(i, _("There are more than 52 characters in at least one subtitle line."));
245 case dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH:
246 add(i, _("There are more than 79 characters in at least one subtitle line."));
248 case dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT:
249 add(i, _("There are more than 3 closed caption lines in at least one place."));
251 case dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH:
252 add(i, _("There are more than 32 characters in at least one closed caption line."));
254 case dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE:
255 add(i, _("The sound asset %f has an invalid frame rate of %n."));
257 case dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT:
258 add(i, _("The CPL %n has no <AnnotationText> tag."));
260 case dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT:
261 add(i, _("The CPL %n has an <AnnotationText> which is not the same as its <ContentTitleText>."));
263 case dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION:
264 add(i, _("At least one asset in a reel does not have the same duration as the others."));
266 case dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS:
267 add(i, _("The DCP has subtitles but at least one reel has no subtitle asset."));
269 case dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS:
270 add(i, _("The DCP has closed captions but not every reel has the same number of closed caption assets."));
272 case dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT:
273 add(i, _("The subtitle asset %n has no <EntryPoint> tag."));
275 case dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT:
276 add(i, _("Subtitle asset %n has a non-zero <EntryPoint>."));
278 case dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT:
279 add(i, _("The closed caption asset %n has no <EntryPoint> tag."));
281 case dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT:
282 add(i, _("Closed caption asset %n has a non-zero <EntryPoint>."));
284 case dcp::VerificationNote::Code::MISSING_HASH:
285 add(i, _("The asset %n has no <Hash> in the CPL."));
287 case dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE:
288 add(i, _("The DCP is a feature but has no FFEC (first frame of end credits) marker."));
290 case dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE:
291 add(i, _("The DCP is a feature but has no FFMC (first frame of moving credits) marker."));
293 case dcp::VerificationNote::Code::MISSING_FFOC:
294 add(i, _("The DCP has no FFOC (first frame of content) marker."));
296 case dcp::VerificationNote::Code::MISSING_LFOC:
297 add(i, _("The DCP has no LFOC (last frame of content) marker."));
299 case dcp::VerificationNote::Code::INCORRECT_FFOC:
300 add(i, _("The DCP has a FFOC of %n instead of 1."));
302 case dcp::VerificationNote::Code::INCORRECT_LFOC:
303 add(i, _("The DCP has a LFOC of %n instead of the reel duration minus one."));
305 case dcp::VerificationNote::Code::MISSING_CPL_METADATA:
306 add(i, _("The CPL %n has no CPL metadata tag."));
308 case dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER:
309 add(i, _("The CPL %n has no CPL metadata version number tag."));
311 case dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA:
312 add(i, _("The CPL %n has no CPL extension metadata tag."));
314 case dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA:
315 add(i, _("The CPL %f has an invalid CPL extension metadata tag (%n)"));
317 case dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT:
318 add(i, _("The CPL %n has encrypted content but is not signed."));
320 case dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT:
321 add(i, _("The PKL %n has encrypted content but is not signed."));
323 case dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL:
324 add(i, _("The PKL %n has an <AnnotationText> which does not match its CPL's <ContentTitleText>."));
326 case dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED:
327 add(i, _("The DCP has encrypted content, but not all its assets are encrypted."));
329 case dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM:
330 add(i, _("A picture frame has an invalid JPEG2000 codestream (%n)"));
332 case dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K:
333 add(i, _("A 2K JPEG2000 frame has %n guard bits instead of 1."));
335 case dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K:
336 add(i, _("A 4K JPEG2000 frame has %n guard bits instead of 2."));
338 case dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE:
339 add(i, _("A JPEG2000 tile size does not match the image size."));
341 case dcp::VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH:
342 add(i, _("A JPEG2000 frame has a code-block width of %n instead of 32."));
344 case dcp::VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT:
345 add(i, _("A JPEG2000 frame has a code-block height of %n instead of 32."));
347 case dcp::VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K:
348 add(i, _("A 2K JPEG2000 frame has %n POC marker(s) instead of 0."));
350 case dcp::VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K:
351 add(i, _("A 4K JPEG2000 frame has %n POC marker(s) instead of 1."));
353 case dcp::VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER:
354 add(i, _("A JPEG2000 frame contains an invalid POC marker (%n)."));
356 case dcp::VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION:
357 add(i, _("A JPEG2000 frame contains POC marker in an invalid location."));
359 case dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K:
360 add(i, _("A 2K JPEG2000 frame contains %n tile parts instead of 3."));
362 case dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K:
363 add(i, _("A 4K JPEG2000 frame contains %n tile parts instead of 6."));
365 case dcp::VerificationNote::Code::MISSING_JPEG200_TLM_MARKER:
366 add(i, _("A JPEG2000 frame has no TLM marker."));
368 case dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY:
369 add(i, _("A subtitle lasts longer than the reel it is in."));
371 case dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID:
372 add(i, _("The Resource ID in a timed text MXF did not match the ID of the contained XML."));
374 case dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID:
375 add(i, _("The Asset ID in a timed text MXF is the same as the Resource ID or that of the contained XML."));
377 case dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION:
379 vector<string> parts;
380 boost::split(parts, i.note().get(), boost::is_any_of(" "));
381 add(i, wxString::Format(_("The reel duration (%s) of some timed text is not the same as the ContainerDuration (%s) of its MXF."), std_to_wx(parts[0]), std_to_wx(parts[1])));
384 case dcp::VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED:
385 add(i, _("Part of the DCP could not be checked because no KDM was available."));
387 case dcp::VerificationNote::Code::EMPTY_TEXT:
388 add(i, _("At least one <Text> node in a subtitle or closed caption is empty."));
390 case dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN:
391 add(i, _("Some closed <Text> or <Image> nodes have different vertical alignments within a <Subtitle>."));
393 case dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING:
394 add(i, _("Some closed captions are not listed in the order of their vertical position."));
396 case dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT:
397 add(i, _("There is a <EntryPoint> tag inside a <MainMarkers>."));
399 case dcp::VerificationNote::Code::UNEXPECTED_DURATION:
400 add(i, _("There is a <Duration> tag inside a <MainMarkers>."));
402 case dcp::VerificationNote::Code::INVALID_CONTENT_KIND:
403 add(i, _("An invalid <ContentKind> %n has been used."));
405 case dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA:
406 add(i, _("The <MainPictureActiveArea> is either not a multiple of 2, or is bigger than an asset."));
408 case dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL:
409 add(i, _("The PKL %n has more than one asset with the same ID."));
411 case dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP:
412 add(i, _("The ASSETMAP %n has more than one asset with the same ID."));
414 case dcp::VerificationNote::Code::MISSING_SUBTITLE:
415 add(i, _("The subtitle asset %n contains no subtitles."));
417 case dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE:
418 add(i, _("<IssueDate> has an invalid value %n"));
420 case dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS:
421 add(i, _("Sound assets do not all have the same channel count."));
423 case dcp::VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION:
424 add(i, _("<MainSoundConfiguration> describes incorrect number of channels (%n)"));
426 case dcp::VerificationNote::Code::MISSING_FONT:
427 add(i, _("The font file for font ID \"%n\" was not found, or was not referred to in the ASSETMAP."));
429 case dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE:
430 add(i, _("Frame %frame has an image component that is too large (component %component is %size bytes in size)."));
432 case dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT:
433 add(i, _("The XML in the subtitle asset %n has more than one namespace declaration."));
435 case dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT:
436 add(i, _("A subtitle or closed caption refers to a font with ID %id that does not have a corresponding <LoadFont> node."));
438 case dcp::VerificationNote::Code::MISSING_LOAD_FONT:
439 add(i, _("The SMPTE subtitle asset %id has <Text> nodes but no <LoadFont> node"));
441 case dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID:
442 add(i, _("The asset with ID %id in the asset map actually has an id of %other_id"));
444 case dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT:
445 add(i, _("The <LabelText> in a <ContentVersion> in CPL %id is empty"));
450 wxString summary_text;
452 if (counts[dcp::VerificationNote::Type::ERROR] == 1) {
453 /// TRANSLATORS: this will be used at the start of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
454 summary_text = _("1 error, ");
456 /// TRANSLATORS: this will be used at the start of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
457 summary_text = wxString::Format("%d errors, ", counts[dcp::VerificationNote::Type::ERROR]);
460 if (counts[dcp::VerificationNote::Type::BV21_ERROR] == 1) {
461 /// TRANSLATORS: this will be used in the middle of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
462 summary_text += _("1 Bv2.1 error, ");
464 /// TRANSLATORS: this will be used in the middle of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
465 summary_text += wxString::Format("%d Bv2.1 errors, ", counts[dcp::VerificationNote::Type::BV21_ERROR]);
468 if (counts[dcp::VerificationNote::Type::WARNING] == 1) {
469 /// TRANSLATORS: this will be used at the end of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
470 summary_text += _("and 1 warning.");
472 /// TRANSLATORS: this will be used at the end of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
473 summary_text += wxString::Format("and %d warnings.", counts[dcp::VerificationNote::Type::WARNING]);
476 summary->SetLabel(summary_text);
478 if (counts[dcp::VerificationNote::Type::ERROR] == 0) {
479 add_bullet(dcp::VerificationNote::Type::ERROR, _("No errors found."));
482 if (counts[dcp::VerificationNote::Type::BV21_ERROR] == 0) {
483 add_bullet(dcp::VerificationNote::Type::BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
486 if (counts[dcp::VerificationNote::Type::WARNING] == 0) {
487 add_bullet(dcp::VerificationNote::Type::WARNING, _("No warnings found."));