2 Copyright (C) 2018-2021 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/>.
21 #include "verify_dcp_dialog.h"
23 #include "lib/verify_dcp_job.h"
24 #include "lib/warnings.h"
25 #include <dcp/verify.h>
26 #include <dcp/raw_convert.h>
27 DCPOMATIC_DISABLE_WARNINGS
28 #include <wx/richtext/richtextctrl.h>
29 #include <wx/notebook.h>
30 DCPOMATIC_ENABLE_WARNINGS
34 using std::shared_ptr;
37 VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job)
38 : wxDialog (parent, wxID_ANY, _("DCP verification"), wxDefaultPosition, {600, 400})
40 auto sizer = new wxBoxSizer (wxVERTICAL);
41 auto notebook = new wxNotebook (this, wxID_ANY);
42 sizer->Add (notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
44 map<dcp::VerificationNote::Type, wxRichTextCtrl*> pages;
45 pages[dcp::VerificationNote::VERIFY_ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
46 notebook->AddPage (pages[dcp::VerificationNote::VERIFY_ERROR], _("Errors"));
47 pages[dcp::VerificationNote::VERIFY_BV21_ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
48 notebook->AddPage (pages[dcp::VerificationNote::VERIFY_BV21_ERROR], _("SMPTE Bv2.1 errors"));
49 pages[dcp::VerificationNote::VERIFY_WARNING] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
50 notebook->AddPage (pages[dcp::VerificationNote::VERIFY_WARNING], _("Warnings"));
52 auto summary = new wxStaticText (this, wxID_ANY, wxT(""));
53 sizer->Add (summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
55 auto buttons = CreateStdDialogButtonSizer (0);
56 sizer->Add (CreateSeparatedSizer(buttons), wxSizerFlags().Expand().DoubleBorder());
57 buttons->SetAffirmativeButton (new wxButton (this, wxID_OK));
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::VERIFY_WARNING] = 0;
75 counts[dcp::VerificationNote::VERIFY_BV21_ERROR] = 0;
76 counts[dcp::VerificationNote::VERIFY_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) {
87 message.Replace("%n", std_to_wx(note.note().get()));
90 message.Replace("%f", std_to_wx(note.file()->filename().string()));
93 message.Replace("%l", std_to_wx(dcp::raw_convert<string>(note.line().get())));
95 add_bullet (note.type(), message);
96 counts[note.type()]++;
99 if (job->finished_in_error() && job->error_summary() != "") {
100 /* We have an error that did not come from dcp::verify */
101 add_bullet (dcp::VerificationNote::VERIFY_ERROR, std_to_wx(job->error_summary()));
104 for (auto i: job->notes()) {
106 case dcp::VerificationNote::FAILED_READ:
107 add (i, std_to_wx(*i.note()));
109 case dcp::VerificationNote::MISMATCHED_CPL_HASHES:
110 add(i, _("The hash of the CPL %n in the PKL does not agree with the CPL file. This probably means that the CPL file is corrupt."));
112 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
113 add(i, _("The picture in a reel has a frame rate of %n, which is not valid."));
115 case dcp::VerificationNote::INCORRECT_PICTURE_HASH:
116 add(i, _("The hash of the picture asset %f does not agree with the PKL file. This probably means that the asset file is corrupt."));
118 case dcp::VerificationNote::MISMATCHED_PICTURE_HASHES:
119 add(i, _("The PKL and CPL hashes disagree for picture asset %f."));
121 case dcp::VerificationNote::INCORRECT_SOUND_HASH:
122 add(i, _("The hash of the sound asset %f does not agree with the PKL file. This probably means that the asset file is corrupt."));
124 case dcp::VerificationNote::MISMATCHED_SOUND_HASHES:
125 add(i, _("The PKL and CPL hashes disagree for sound asset %f."));
127 case dcp::VerificationNote::EMPTY_ASSET_PATH:
128 add(i, _("An asset has an empty path in the ASSETMAP."));
130 case dcp::VerificationNote::MISSING_ASSET:
131 add(i, _("The asset %f is missing."));
133 case dcp::VerificationNote::MISMATCHED_STANDARD:
134 add(i, _("Parts of the DCP are written according to the Interop standard and parts according to SMPTE."));
136 case dcp::VerificationNote::INVALID_XML:
138 add(i, _("The XML in %f is malformed on line %l (%n)."));
140 add(i, _("The XML in %f is malformed (%n)."));
143 case dcp::VerificationNote::MISSING_ASSETMAP:
144 add(i, _("No ASSETMAP or ASSETMAP.xml file was found."));
146 case dcp::VerificationNote::INVALID_INTRINSIC_DURATION:
147 add(i, _("The asset %n has an instrinsic duration of less than 1 second, which is invalid."));
149 case dcp::VerificationNote::INVALID_DURATION:
150 add(i, _("The asset %n has a duration of less than 1 second, which is invalid."));
152 case dcp::VerificationNote::INVALID_PICTURE_FRAME_SIZE_IN_BYTES:
153 add(i, _("At least one frame of the video asset %f is over the limit of 250Mbit/s."));
155 case dcp::VerificationNote::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES:
156 add(i, _("At least one frame of the video asset %f is close to the limit of 250MBit/s."));
158 case dcp::VerificationNote::EXTERNAL_ASSET:
159 add(i, _("This DCP refers to at the asset %n in another DCP (and perhaps others), so it is a \"version file\" (VF)"));
161 case dcp::VerificationNote::INVALID_STANDARD:
162 add(i, _("This DCP uses the Interop standard, but it should be made with SMPTE."));
164 case dcp::VerificationNote::INVALID_LANGUAGE:
165 add(i, _("The invalid language tag %n is used."));
167 case dcp::VerificationNote::INVALID_PICTURE_SIZE_IN_PIXELS:
168 add(i, _("The video asset %f uses the invalid image size %n."));
170 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_2K:
171 add(i, _("The video asset %f uses the invalid frame rate %n."));
173 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_4K:
174 add(i, _("The video asset %f uses the frame rate %n which is invalid for 4K video."));
176 case dcp::VerificationNote::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D:
177 add(i, _("The video asset %f uses the frame rate %n which is invalid for 3D video."));
179 case dcp::VerificationNote::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES:
180 add(i, _("The XML in the closed caption asset %f takes up %n bytes which is over the 256KB limit."));
182 case dcp::VerificationNote::INVALID_TIMED_TEXT_SIZE_IN_BYTES:
183 add(i, _("The timed text asset %f takes up %n bytes which is over the 115MB limit."));
185 case dcp::VerificationNote::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES:
186 add(i, _("The fonts in the timed text asset %f take up %n bytes which is over the 10MB limit."));
188 case dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE:
189 add(i, _("The subtitle asset %f contains no <Language> tag."));
191 case dcp::VerificationNote::MISMATCHED_SUBTITLE_LANGUAGES:
192 add(i, _("Not all subtitle assets specify the same <Language> tag."));
194 case dcp::VerificationNote::MISSING_SUBTITLE_START_TIME:
195 add(i, _("The subtitle asset %f contains no <StartTime> tag."));
197 case dcp::VerificationNote::INVALID_SUBTITLE_START_TIME:
198 add(i, _("The subtitle asset %f has a <StartTime> which is not zero."));
200 case dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME:
201 add(i, _("The first subtitle or closed caption happens before 4s into the first reel."));
203 case dcp::VerificationNote::INVALID_SUBTITLE_DURATION:
204 add(i, _("At least one subtitle lasts less than 15 frames."));
206 case dcp::VerificationNote::INVALID_SUBTITLE_SPACING:
207 add(i, _("At least one pair of subtitles is separated by less than 2 frames."));
209 case dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT:
210 add(i, _("There are more than 3 subtitle lines in at least one place."));
212 case dcp::VerificationNote::NEARLY_INVALID_SUBTITLE_LINE_LENGTH:
213 add(i, _("There are more than 52 characters in at least one subtitle line."));
215 case dcp::VerificationNote::INVALID_SUBTITLE_LINE_LENGTH:
216 add(i, _("There are more than 79 characters in at least one subtitle line."));
218 case dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT:
219 add(i, _("There are more than 3 closed caption lines in at least one place."));
221 case dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_LENGTH:
222 add(i, _("There are more than 32 characters in at least one closed caption line."));
224 case dcp::VerificationNote::INVALID_SOUND_FRAME_RATE:
225 add(i, _("The sound asset %f has an invalid frame rate of %n."));
227 case dcp::VerificationNote::MISSING_CPL_ANNOTATION_TEXT:
228 add(i, _("The CPL %n has no <AnnotationText> tag."));
230 case dcp::VerificationNote::MISMATCHED_CPL_ANNOTATION_TEXT:
231 add(i, _("The CPL %n has an <AnnotationText> which is not the same as its <ContentTitleText>."));
233 case dcp::VerificationNote::MISMATCHED_ASSET_DURATION:
234 add(i, _("At least one asset in a reel does not have the same duration as the others."));
236 case dcp::VerificationNote::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS:
237 add(i, _("The DCP has subtitles but at least one reel has no subtitle asset."));
239 case dcp::VerificationNote::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS:
240 add(i, _("The DCP has closed captions but not every reel has the same number of closed caption assets."));
242 case dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT:
243 add(i, _("The subtitle asset %n has no <EntryPoint> tag."));
245 case dcp::VerificationNote::INCORRECT_SUBTITLE_ENTRY_POINT:
246 add(i, _("Subtitle asset %n has a non-zero <EntryPoint>."));
248 case dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT:
249 add(i, _("The closed caption asset %n has no <EntryPoint> tag."));
251 case dcp::VerificationNote::INCORRECT_CLOSED_CAPTION_ENTRY_POINT:
252 add(i, _("Closed caption asset %n has a non-zero <EntryPoint>."));
254 case dcp::VerificationNote::MISSING_HASH:
255 add(i, _("The asset %n has no <Hash> in the CPL."));
257 case dcp::VerificationNote::MISSING_FFEC_IN_FEATURE:
258 add(i, _("The DCP is a feature but has no FFEC (first frame of end credits) marker."));
260 case dcp::VerificationNote::MISSING_FFMC_IN_FEATURE:
261 add(i, _("The DCP is a feature but has no FFMC (first frame of moving credits) marker."));
263 case dcp::VerificationNote::MISSING_FFOC:
264 add(i, _("The DCP has no FFOC (first frame of content) marker."));
266 case dcp::VerificationNote::MISSING_LFOC:
267 add(i, _("The DCP has no LFOC (last frame of content) marker."));
269 case dcp::VerificationNote::INCORRECT_FFOC:
270 add(i, _("The DCP has a FFOC of %n instead of 1."));
272 case dcp::VerificationNote::INCORRECT_LFOC:
273 add(i, _("The DCP has a LFOC of %n instead of the reel duration minus one."));
275 case dcp::VerificationNote::MISSING_CPL_METADATA:
276 add(i, _("The CPL %n has no CPL metadata tag."));
278 case dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER:
279 add(i, _("The CPL %n has no CPL metadata version number tag."));
281 case dcp::VerificationNote::MISSING_EXTENSION_METADATA:
282 add(i, _("The CPL %n has no CPL extension metadata tag."));
284 case dcp::VerificationNote::INVALID_EXTENSION_METADATA:
285 add(i, _("The CPL %f has an invalid CPL extension metadata tag (%n)"));
287 case dcp::VerificationNote::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT:
288 add(i, _("The CPL %n has encrypted content but is not signed."));
290 case dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT:
291 add(i, _("The PKL %n has encrypted content but is not signed."));
293 case dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL:
294 add(i, _("The PKL %n has an <AnnotationText> which does not match its CPL's <ContentTitleText>."));
296 case dcp::VerificationNote::PARTIALLY_ENCRYPTED:
297 add(i, _("The DCP has encrypted content, but not all its assets are encrypted."));
302 wxString summary_text;
304 if (counts[dcp::VerificationNote::VERIFY_ERROR] == 1) {
305 /// TRANSLATORS: this will be used at the start of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
306 summary_text = _("1 error, ");
308 /// TRANSLATORS: this will be used at the start of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
309 summary_text = wxString::Format("%d errors, ", counts[dcp::VerificationNote::VERIFY_ERROR]);
312 if (counts[dcp::VerificationNote::VERIFY_BV21_ERROR] == 1) {
313 /// TRANSLATORS: this will be used in the middle of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
314 summary_text += _("1 Bv2.1 error, ");
316 /// TRANSLATORS: this will be used in the middle of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
317 summary_text += wxString::Format("%d Bv2.1 errors, ", counts[dcp::VerificationNote::VERIFY_BV21_ERROR]);
320 if (counts[dcp::VerificationNote::VERIFY_WARNING] == 1) {
321 /// TRANSLATORS: this will be used at the end of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
322 summary_text += _("and 1 warning.");
324 /// TRANSLATORS: this will be used at the end of a string like "1 error, 2 Bv2.1 errors and 3 warnings."
325 summary_text += wxString::Format("and %d warnings.", counts[dcp::VerificationNote::VERIFY_WARNING]);
328 summary->SetLabel(summary_text);
330 if (counts[dcp::VerificationNote::VERIFY_ERROR] == 0) {
331 add_bullet (dcp::VerificationNote::VERIFY_ERROR, _("No errors found."));
334 if (counts[dcp::VerificationNote::VERIFY_BV21_ERROR] == 0) {
335 add_bullet (dcp::VerificationNote::VERIFY_BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
338 if (counts[dcp::VerificationNote::VERIFY_WARNING] == 0) {
339 add_bullet (dcp::VerificationNote::VERIFY_WARNING, _("No warnings found."));