Adapt for libdcp use of enum class.
[dcpomatic.git] / src / wx / verify_dcp_dialog.cc
1 /*
2     Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21 #include "verify_dcp_dialog.h"
22 #include "wx_util.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
31
32 using std::list;
33 using std::map;
34 using std::shared_ptr;
35 using std::string;
36
37 VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job)
38         : wxDialog (parent, wxID_ANY, _("DCP verification"), wxDefaultPosition, {600, 400})
39 {
40         auto sizer = new wxBoxSizer (wxVERTICAL);
41         auto notebook = new wxNotebook (this, wxID_ANY);
42         sizer->Add (notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
43
44         map<dcp::VerificationNote::Type, wxRichTextCtrl*> pages;
45         pages[dcp::VerificationNote::Type::ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
46         notebook->AddPage (pages[dcp::VerificationNote::Type::ERROR], _("Errors"));
47         pages[dcp::VerificationNote::Type::BV21_ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
48         notebook->AddPage (pages[dcp::VerificationNote::Type::BV21_ERROR], _("SMPTE Bv2.1 errors"));
49         pages[dcp::VerificationNote::Type::WARNING] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
50         notebook->AddPage (pages[dcp::VerificationNote::Type::WARNING], _("Warnings"));
51
52         auto summary = new wxStaticText (this, wxID_ANY, wxT(""));
53         sizer->Add (summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
54
55         auto buttons = CreateStdDialogButtonSizer (0);
56         sizer->Add (CreateSeparatedSizer(buttons), wxSizerFlags().Expand().DoubleBorder());
57         buttons->SetAffirmativeButton (new wxButton (this, wxID_OK));
58         buttons->Realize ();
59
60         SetSizer (sizer);
61         sizer->Layout ();
62         sizer->SetSizeHints (this);
63
64         for (auto const& i: pages) {
65                 i.second->GetCaret()->Hide();
66         }
67
68         if (job->finished_ok() && job->notes().empty()) {
69                 summary->SetLabel (_("DCP validates OK."));
70                 return;
71         }
72
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;
77
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 ();
83         };
84
85         auto add = [&counts, &add_bullet](dcp::VerificationNote note, wxString message) {
86                 if (note.note()) {
87                         message.Replace("%n", std_to_wx(note.note().get()));
88                 }
89                 if (note.file()) {
90                         message.Replace("%f", std_to_wx(note.file()->filename().string()));
91                 }
92                 if (note.line()) {
93                         message.Replace("%l", std_to_wx(dcp::raw_convert<string>(note.line().get())));
94                 }
95                 add_bullet (note.type(), message);
96                 counts[note.type()]++;
97         };
98
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::Type::ERROR, std_to_wx(job->error_summary()));
102         }
103
104         for (auto i: job->notes()) {
105                 switch (i.code()) {
106                 case dcp::VerificationNote::Code::FAILED_READ:
107                         add (i, std_to_wx(*i.note()));
108                         break;
109                 case dcp::VerificationNote::Code::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."));
111                         break;
112                 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE:
113                         add(i, _("The picture in a reel has a frame rate of %n, which is not valid."));
114                         break;
115                 case dcp::VerificationNote::Code::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."));
117                         break;
118                 case dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES:
119                         add(i, _("The PKL and CPL hashes disagree for picture asset %f."));
120                         break;
121                 case dcp::VerificationNote::Code::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."));
123                         break;
124                 case dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES:
125                         add(i, _("The PKL and CPL hashes disagree for sound asset %f."));
126                         break;
127                 case dcp::VerificationNote::Code::EMPTY_ASSET_PATH:
128                         add(i, _("An asset has an empty path in the ASSETMAP."));
129                         break;
130                 case dcp::VerificationNote::Code::MISSING_ASSET:
131                         add(i, _("The asset %f is missing."));
132                         break;
133                 case dcp::VerificationNote::Code::MISMATCHED_STANDARD:
134                         add(i, _("Parts of the DCP are written according to the Interop standard and parts according to SMPTE."));
135                         break;
136                 case dcp::VerificationNote::Code::INVALID_XML:
137                         if (i.line()) {
138                                 add(i, _("The XML in %f is malformed on line %l (%n)."));
139                         } else {
140                                 add(i, _("The XML in %f is malformed (%n)."));
141                         }
142                         break;
143                 case dcp::VerificationNote::Code::MISSING_ASSETMAP:
144                         add(i, _("No ASSETMAP or ASSETMAP.xml file was found."));
145                         break;
146                 case dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION:
147                         add(i, _("The asset %n has an instrinsic duration of less than 1 second, which is invalid."));
148                         break;
149                 case dcp::VerificationNote::Code::INVALID_DURATION:
150                         add(i, _("The asset %n has a duration of less than 1 second, which is invalid."));
151                         break;
152                 case dcp::VerificationNote::Code::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."));
154                         break;
155                 case dcp::VerificationNote::Code::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."));
157                         break;
158                 case dcp::VerificationNote::Code::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)"));
160                         break;
161                 case dcp::VerificationNote::Code::INVALID_STANDARD:
162                         add(i, _("This DCP uses the Interop standard, but it should be made with SMPTE."));
163                         break;
164                 case dcp::VerificationNote::Code::INVALID_LANGUAGE:
165                         add(i, _("The invalid language tag %n is used."));
166                         break;
167                 case dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS:
168                         add(i, _("The video asset %f uses the invalid image size %n."));
169                         break;
170                 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K:
171                         add(i, _("The video asset %f uses the invalid frame rate %n."));
172                         break;
173                 case dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K:
174                         add(i, _("The video asset %f uses the frame rate %n which is invalid for 4K video."));
175                         break;
176                 case dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D:
177                         add(i, _("The video asset %f uses the frame rate %n which is invalid for 3D video."));
178                         break;
179                 case dcp::VerificationNote::Code::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."));
181                         break;
182                 case dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES:
183                         add(i, _("The timed text asset %f takes up %n bytes which is over the 115MB limit."));
184                         break;
185                 case dcp::VerificationNote::Code::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."));
187                         break;
188                 case dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE:
189                         add(i, _("The subtitle asset %f contains no <Language> tag."));
190                         break;
191                 case dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES:
192                         add(i, _("Not all subtitle assets specify the same <Language> tag."));
193                         break;
194                 case dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME:
195                         add(i, _("The subtitle asset %f contains no <StartTime> tag."));
196                         break;
197                 case dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME:
198                         add(i, _("The subtitle asset %f has a <StartTime> which is not zero."));
199                         break;
200                 case dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME:
201                         add(i, _("The first subtitle or closed caption happens before 4s into the first reel."));
202                         break;
203                 case dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION:
204                         add(i, _("At least one subtitle lasts less than 15 frames."));
205                         break;
206                 case dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING:
207                         add(i, _("At least one pair of subtitles is separated by less than 2 frames."));
208                         break;
209                 case dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT:
210                         add(i, _("There are more than 3 subtitle lines in at least one place."));
211                         break;
212                 case dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH:
213                         add(i, _("There are more than 52 characters in at least one subtitle line."));
214                         break;
215                 case dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH:
216                         add(i, _("There are more than 79 characters in at least one subtitle line."));
217                         break;
218                 case dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT:
219                         add(i, _("There are more than 3 closed caption lines in at least one place."));
220                         break;
221                 case dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH:
222                         add(i, _("There are more than 32 characters in at least one closed caption line."));
223                         break;
224                 case dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE:
225                         add(i, _("The sound asset %f has an invalid frame rate of %n."));
226                         break;
227                 case dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT:
228                         add(i, _("The CPL %n has no <AnnotationText> tag."));
229                         break;
230                 case dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT:
231                         add(i, _("The CPL %n has an <AnnotationText> which is not the same as its <ContentTitleText>."));
232                         break;
233                 case dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION:
234                         add(i, _("At least one asset in a reel does not have the same duration as the others."));
235                         break;
236                 case dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS:
237                         add(i, _("The DCP has subtitles but at least one reel has no subtitle asset."));
238                         break;
239                 case dcp::VerificationNote::Code::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."));
241                         break;
242                 case dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT:
243                         add(i, _("The subtitle asset %n has no <EntryPoint> tag."));
244                         break;
245                 case dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT:
246                         add(i, _("Subtitle asset %n has a non-zero <EntryPoint>."));
247                         break;
248                 case dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT:
249                         add(i, _("The closed caption asset %n has no <EntryPoint> tag."));
250                         break;
251                 case dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT:
252                         add(i, _("Closed caption asset %n has a non-zero <EntryPoint>."));
253                         break;
254                 case dcp::VerificationNote::Code::MISSING_HASH:
255                         add(i, _("The asset %n has no <Hash> in the CPL."));
256                         break;
257                 case dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE:
258                         add(i, _("The DCP is a feature but has no FFEC (first frame of end credits) marker."));
259                         break;
260                 case dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE:
261                         add(i, _("The DCP is a feature but has no FFMC (first frame of moving credits) marker."));
262                         break;
263                 case dcp::VerificationNote::Code::MISSING_FFOC:
264                         add(i, _("The DCP has no FFOC (first frame of content) marker."));
265                         break;
266                 case dcp::VerificationNote::Code::MISSING_LFOC:
267                         add(i, _("The DCP has no LFOC (last frame of content) marker."));
268                         break;
269                 case dcp::VerificationNote::Code::INCORRECT_FFOC:
270                         add(i, _("The DCP has a FFOC of %n instead of 1."));
271                         break;
272                 case dcp::VerificationNote::Code::INCORRECT_LFOC:
273                         add(i, _("The DCP has a LFOC of %n instead of the reel duration minus one."));
274                         break;
275                 case dcp::VerificationNote::Code::MISSING_CPL_METADATA:
276                         add(i, _("The CPL %n has no CPL metadata tag."));
277                         break;
278                 case dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER:
279                         add(i, _("The CPL %n has no CPL metadata version number tag."));
280                         break;
281                 case dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA:
282                         add(i, _("The CPL %n has no CPL extension metadata tag."));
283                         break;
284                 case dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA:
285                         add(i, _("The CPL %f has an invalid CPL extension metadata tag (%n)"));
286                         break;
287                 case dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT:
288                         add(i, _("The CPL %n has encrypted content but is not signed."));
289                         break;
290                 case dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT:
291                         add(i, _("The PKL %n has encrypted content but is not signed."));
292                         break;
293                 case dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL:
294                         add(i, _("The PKL %n has an <AnnotationText> which does not match its CPL's <ContentTitleText>."));
295                         break;
296                 case dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED:
297                         add(i, _("The DCP has encrypted content, but not all its assets are encrypted."));
298                         break;
299                 }
300         }
301
302         wxString summary_text;
303
304         if (counts[dcp::VerificationNote::Type::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, ");
307         } else {
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::Type::ERROR]);
310         }
311
312         if (counts[dcp::VerificationNote::Type::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, ");
315         } else {
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::Type::BV21_ERROR]);
318         }
319
320         if (counts[dcp::VerificationNote::Type::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.");
323         } else {
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::Type::WARNING]);
326         }
327
328         summary->SetLabel(summary_text);
329
330         if (counts[dcp::VerificationNote::Type::ERROR] == 0) {
331                 add_bullet (dcp::VerificationNote::Type::ERROR, _("No errors found."));
332         }
333
334         if (counts[dcp::VerificationNote::Type::BV21_ERROR] == 0) {
335                 add_bullet (dcp::VerificationNote::Type::BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
336         }
337
338         if (counts[dcp::VerificationNote::Type::WARNING] == 0) {
339                 add_bullet (dcp::VerificationNote::Type::WARNING, _("No warnings found."));
340         }
341 }