Missed update to private test repo version.
[dcpomatic.git] / src / lib / hints.cc
1 /*
2     Copyright (C) 2016-2022 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
22 #include "audio_analysis.h"
23 #include "audio_content.h"
24 #include "audio_processor.h"
25 #include "compose.hpp"
26 #include "config.h"
27 #include "constants.h"
28 #include "content.h"
29 #include "cross.h"
30 #include "dcp_content_type.h"
31 #include "film.h"
32 #include "font.h"
33 #include "hints.h"
34 #include "maths_util.h"
35 #include "player.h"
36 #include "ratio.h"
37 #include "text_content.h"
38 #include "video_content.h"
39 #include "writer.h"
40 #include <dcp/cpl.h>
41 #include <dcp/filesystem.h>
42 #include <dcp/raw_convert.h>
43 #include <dcp/reel.h>
44 #include <dcp/reel_closed_caption_asset.h>
45 #include <dcp/reel_subtitle_asset.h>
46 #include <boost/algorithm/string.hpp>
47 #include <iostream>
48
49 #include "i18n.h"
50
51
52 using std::cout;
53 using std::make_shared;
54 using std::max;
55 using std::shared_ptr;
56 using std::string;
57 using std::weak_ptr;
58 using boost::optional;
59 using boost::bind;
60 using namespace dcpomatic;
61 #if BOOST_VERSION >= 106100
62 using namespace boost::placeholders;
63 #endif
64
65
66 /* When checking to see if things are too big, we'll say they are if they
67  * are more than the target size minus this "slack."
68  */
69 #define SIZE_SLACK 4096
70
71
72 /* When writing hints:
73  * - put quotation marks around the name of a GUI tab that you are referring to (e.g. "DCP" or "DCP→Video" tab)
74  */
75
76
77 Hints::Hints (weak_ptr<const Film> weak_film)
78         : WeakConstFilm (weak_film)
79         , _writer (new Writer(weak_film, weak_ptr<Job>(), true))
80         , _analyser (film(), film()->playlist(), true, [](float) {})
81         , _stop (false)
82 {
83
84 }
85
86
87 void
88 Hints::start ()
89 {
90         _thread = boost::thread (bind(&Hints::thread, this));
91 }
92
93
94 Hints::~Hints ()
95 {
96         boost::this_thread::disable_interruption dis;
97
98         try {
99                 _stop = true;
100                 _thread.interrupt ();
101                 _thread.join ();
102         } catch (...) {}
103 }
104
105
106 void
107 Hints::check_few_audio_channels ()
108 {
109         if (film()->audio_channels() < 6) {
110                 hint (_("Your DCP has fewer than 6 audio channels.  This may cause problems on some projectors.  You may want to set the DCP to have 6 channels.  It does not matter if your content has fewer channels, as DCP-o-matic will fill the extras with silence."));
111         }
112 }
113
114
115 void
116 Hints::check_upmixers ()
117 {
118         auto ap = film()->audio_processor();
119         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
120                 hint (_("You are using DCP-o-matic's stereo-to-5.1 upmixer.  This is experimental and may result in poor-quality audio.  If you continue, you should listen to the resulting DCP in a cinema to make sure that it sounds good."));
121         }
122 }
123
124
125 void
126 Hints::check_incorrect_container ()
127 {
128         int narrower_than_scope = 0;
129         int scope = 0;
130         for (auto i: film()->content()) {
131                 if (i->video && i->video->size()) {
132                         auto const r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size())->ratio());
133                         if (r && r->id() == "239") {
134                                 ++scope;
135                         } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") {
136                                 ++narrower_than_scope;
137                         }
138                 }
139         }
140
141         string const film_container = film()->container()->id();
142
143         if (scope && !narrower_than_scope && film_container == "185") {
144                 hint (_("All of your content is in Scope (2.39:1) but your DCP's container is Flat (1.85:1).  This will letter-box your content inside a Flat (1.85:1) frame.  You may prefer to set your DCP's container to Scope (2.39:1) in the \"DCP\" tab."));
145         }
146
147         if (!scope && narrower_than_scope && film_container == "239") {
148                 hint (_("All of your content narrower than 1.90:1 but your DCP's container is Scope (2.39:1).  This will pillar-box your content.  You may prefer to set your DCP's container to have the same ratio as your content."));
149         }
150 }
151
152
153 void
154 Hints::check_unusual_container ()
155 {
156         auto const film_container = film()->container()->id();
157         if (film_container != "185" && film_container != "239") {
158                 hint (_("Your DCP uses an unusual container ratio.  This may cause problems on some projectors.  If possible, use Flat or Scope for the DCP container ratio."));
159         }
160 }
161
162
163 void
164 Hints::check_high_j2k_bandwidth ()
165 {
166         if (film()->j2k_bandwidth() >= 245000000) {
167                 hint (_("A few projectors have problems playing back very high bit-rate DCPs.  It is a good idea to drop the JPEG2000 bandwidth down to about 200Mbit/s; this is unlikely to have any visible effect on the image."));
168         }
169 }
170
171
172 void
173 Hints::check_frame_rate ()
174 {
175         auto f = film ();
176         switch (f->video_frame_rate()) {
177         case 24:
178                 /* Fine */
179                 break;
180         case 25:
181         {
182                 /* You might want to go to 24 */
183                 string base = String::compose(_("You are set up for a DCP at a frame rate of %1 fps.  This frame rate is not supported by all projectors.  You may want to consider changing your frame rate to %2 fps."), 25, 24);
184                 if (f->interop()) {
185                         base += "  ";
186                         base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
187                 }
188                 hint (base);
189                 break;
190         }
191         case 30:
192                 /* 30fps: we can't really offer any decent solutions */
193                 hint (_("You are set up for a DCP frame rate of 30fps, which is not supported by all projectors.  Be aware that you may have compatibility problems."));
194                 break;
195         case 48:
196         case 50:
197         case 60:
198                 /* You almost certainly want to go to half frame rate */
199                 hint (String::compose(_("You are set up for a DCP at a frame rate of %1 fps.  This frame rate is not supported by all projectors.  It is advisable to change the DCP frame rate to %2 fps."), f->video_frame_rate(), f->video_frame_rate() / 2));
200                 break;
201         }
202 }
203
204
205 void
206 Hints::check_4k_3d ()
207 {
208         auto f = film();
209         if (f->resolution() == Resolution::FOUR_K && f->three_d()) {
210                 hint (_("4K 3D is only supported by a very limited number of projectors.  Unless you know that you will play this DCP back on a capable projector, it is advisable to set the DCP to be 2K in the \"DCP→Video\" tab."));
211         }
212 }
213
214
215 void
216 Hints::check_speed_up ()
217 {
218         optional<double> lowest_speed_up;
219         optional<double> highest_speed_up;
220         for (auto i: film()->content()) {
221                 double spu = film()->active_frame_rate_change(i->position()).speed_up;
222                 if (!lowest_speed_up || spu < *lowest_speed_up) {
223                         lowest_speed_up = spu;
224                 }
225                 if (!highest_speed_up || spu > *highest_speed_up) {
226                         highest_speed_up = spu;
227                 }
228         }
229
230         double worst_speed_up = 1;
231         if (highest_speed_up) {
232                 worst_speed_up = *highest_speed_up;
233         }
234         if (lowest_speed_up) {
235                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
236         }
237
238         if (worst_speed_up > 25.5/24.0) {
239                 hint (_("There is a large difference between the frame rate of your DCP and that of some of your content.  This will cause your audio to play back at a much lower or higher pitch than it should.  It is advisable to set your DCP frame rate to one closer to your content, provided that your target projection systems support your chosen DCP rate."));
240         }
241
242 }
243
244
245 void
246 Hints::check_interop ()
247 {
248         if (film()->interop()) {
249                 hint (_("In general it is now advisable to make SMPTE DCPs unless you have a particular reason to use Interop.  It is advisable to set your DCP to use the SMPTE standard in the \"DCP\" tab."));
250         }
251 }
252
253
254 void
255 Hints::check_big_font_files ()
256 {
257         bool big_font_files = false;
258         if (film()->interop ()) {
259                 for (auto i: film()->content()) {
260                         for (auto j: i->text) {
261                                 for (auto k: j->fonts()) {
262                                         auto const p = k->file ();
263                                         if (p && dcp::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
264                                                 big_font_files = true;
265                                         }
266                                 }
267                         }
268                 }
269         }
270
271         if (big_font_files) {
272                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
273         }
274 }
275
276
277 void
278 Hints::check_vob ()
279 {
280         int vob = 0;
281         for (auto i: film()->content()) {
282                 if (boost::algorithm::starts_with(i->path(0).filename().string(), "VTS_")) {
283                         ++vob;
284                 }
285         }
286
287         if (vob > 1) {
288                 hint (String::compose (_("You have %1 files that look like they are VOB files from DVD. You should join them to ensure smooth joins between the files."), vob));
289         }
290 }
291
292
293 void
294 Hints::check_3d_in_2d ()
295 {
296         int three_d = 0;
297         for (auto i: film()->content()) {
298                 if (i->video && i->video->frame_type() != VideoFrameType::TWO_D) {
299                         ++three_d;
300                 }
301         }
302
303         if (three_d > 0 && !film()->three_d()) {
304                 hint (_("You are using 3D content but your DCP is set to 2D.  Set the DCP to 3D if you want to play it back on a 3D system (e.g. Real-D, MasterImage etc.)"));
305         }
306 }
307
308
309 /** @return true if the loudness could be checked, false if it could not because no analysis was available */
310 bool
311 Hints::check_loudness ()
312 {
313         auto path = film()->audio_analysis_path(film()->playlist());
314         if (!dcp::filesystem::exists(path)) {
315                 return false;
316         }
317
318         try {
319                 auto an = make_shared<AudioAnalysis>(path);
320
321                 string ch;
322
323                 auto sample_peak = an->sample_peak ();
324                 auto true_peak = an->true_peak ();
325
326                 for (size_t i = 0; i < sample_peak.size(); ++i) {
327                         float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
328                         float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
329                         if (peak_dB > -3) {
330                                 ch += dcp::raw_convert<string>(short_audio_channel_name(i)) + ", ";
331                         }
332                 }
333
334                 ch = ch.substr (0, ch.length() - 2);
335
336                 if (!ch.empty()) {
337                         hint(String::compose(
338                                         _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
339                                         ch
340                                         )
341                              );
342                 }
343         } catch (OldFormatError& e) {
344                 /* The audio analysis is too old to load in */
345                 return false;
346         }
347
348         return true;
349 }
350
351
352 static
353 bool
354 subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
355 {
356         return asset && asset->file() && dcp::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK);
357 }
358
359
360 void
361 Hints::check_out_of_range_markers ()
362 {
363         auto const length = film()->length();
364         for (auto const& i: film()->markers()) {
365                 if (i.second >= length) {
366                         hint (_("At least one marker comes after the end of the project and will be ignored."));
367                 }
368         }
369 }
370
371
372 void
373 Hints::scan_content(shared_ptr<const Film> film)
374 {
375         auto const check_loudness_done = check_loudness();
376
377         auto content = film->playlist()->content();
378         auto iter = std::find_if(content.begin(), content.end(), [](shared_ptr<const Content> content) {
379                 auto text_iter = std::find_if(content->text.begin(), content->text.end(), [](shared_ptr<const TextContent> text) {
380                         return text->use();
381                 });
382                 return text_iter != content->text.end();
383         });
384
385         auto const have_text = iter != content.end();
386
387         if (check_loudness_done && !have_text) {
388                 /* We don't need to check loudness, and we don't have any active text to check,
389                  * so a scan of the content is pointless.
390                  */
391                 return;
392         }
393
394         if (check_loudness_done && have_text) {
395                 emit (bind(boost::ref(Progress), _("Examining subtitles and closed captions")));
396         } else if (!check_loudness_done && !have_text) {
397                 emit (bind(boost::ref(Progress), _("Examining audio")));
398         } else {
399                 emit (bind(boost::ref(Progress), _("Examining audio, subtitles and closed captions")));
400         }
401
402         auto player = make_shared<Player>(film, Image::Alignment::COMPACT);
403         player->set_ignore_video();
404         if (check_loudness_done || _disable_audio_analysis) {
405                 /* We don't need to analyse audio because we already loaded a suitable analysis */
406                 player->set_ignore_audio();
407         } else {
408                 /* Send auto to the analyser to check loudness */
409                 player->Audio.connect(bind(&Hints::audio, this, _1, _2));
410         }
411         player->Text.connect(bind(&Hints::text, this, _1, _2, _3, _4));
412
413         struct timeval last_pulse;
414         gettimeofday(&last_pulse, 0);
415
416         _writer->write(player->get_subtitle_fonts());
417
418         while (!player->pass()) {
419
420                 struct timeval now;
421                 gettimeofday(&now, 0);
422                 if ((seconds(now) - seconds(last_pulse)) > 1) {
423                         if (_stop) {
424                                 return;
425                         }
426                         emit(bind(boost::ref(Pulse)));
427                         last_pulse = now;
428                 }
429         }
430
431         if (!check_loudness_done) {
432                 _analyser.finish();
433                 _analyser.get().write(film->audio_analysis_path(film->playlist()));
434                 check_loudness();
435         }
436 }
437
438
439 void
440 Hints::thread ()
441 try
442 {
443         start_of_thread ("Hints");
444
445         auto film = _film.lock ();
446         if (!film) {
447                 return;
448         }
449
450         auto content = film->content ();
451
452         check_certificates ();
453         check_interop ();
454         check_big_font_files ();
455         check_few_audio_channels ();
456         check_upmixers ();
457         check_incorrect_container ();
458         check_unusual_container ();
459         check_high_j2k_bandwidth ();
460         check_frame_rate ();
461         check_4k_3d ();
462         check_speed_up ();
463         check_vob ();
464         check_3d_in_2d ();
465         check_ffec_and_ffmc_in_smpte_feature ();
466         check_out_of_range_markers ();
467         check_subtitle_languages();
468         check_audio_language ();
469         check_8_or_16_audio_channels();
470
471         scan_content(film);
472
473         if (_long_subtitle && !_very_long_subtitle) {
474                 hint (_("At least one of your subtitle lines has more than 52 characters.  It is recommended to make each line 52 characters at most in length."));
475         } else if (_very_long_subtitle) {
476                 hint (_("At least one of your subtitle lines has more than 79 characters.  You should make each line 79 characters at most in length."));
477         }
478
479         bool ccap_xml_too_big = false;
480         bool ccap_mxf_too_big = false;
481         bool subs_mxf_too_big = false;
482
483         auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
484         dcp::filesystem::remove_all(dcp_dir);
485
486         _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
487
488         dcp::DCP dcp (dcp_dir);
489         dcp.read ();
490         DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
491         for (auto reel: dcp.cpls()[0]->reels()) {
492                 for (auto ccap: reel->closed_captions()) {
493                         if (ccap->asset() && ccap->asset()->xml_as_string().length() > static_cast<size_t>(MAX_CLOSED_CAPTION_XML_SIZE - SIZE_SLACK) && !ccap_xml_too_big) {
494                                 hint (_(
495                                                 "At least one of your closed caption files' XML part is larger than " MAX_CLOSED_CAPTION_XML_SIZE_TEXT
496                                                 ".  You should divide the DCP into shorter reels."
497                                        ));
498                                 ccap_xml_too_big = true;
499                         }
500                         if (subtitle_mxf_too_big(ccap->asset()) && !ccap_mxf_too_big) {
501                                 hint (_(
502                                                 "At least one of your closed caption files is larger than " MAX_TEXT_MXF_SIZE_TEXT
503                                                 " in total.  You should divide the DCP into shorter reels."
504                                        ));
505                                 ccap_mxf_too_big = true;
506                         }
507                 }
508                 if (reel->main_subtitle() && subtitle_mxf_too_big(reel->main_subtitle()->asset()) && !subs_mxf_too_big) {
509                         hint (_(
510                                         "At least one of your subtitle files is larger than " MAX_TEXT_MXF_SIZE_TEXT " in total.  "
511                                         "You should divide the DCP into shorter reels."
512                                ));
513                         subs_mxf_too_big = true;
514                 }
515         }
516         dcp::filesystem::remove_all(dcp_dir);
517
518         emit (bind(boost::ref(Finished)));
519 }
520 catch (boost::thread_interrupted)
521 {
522         /* The Hints object is being destroyed before it has finished, so just give up */
523 }
524 catch (...)
525 {
526         store_current ();
527 }
528
529
530 void
531 Hints::hint (string h)
532 {
533         emit(bind(boost::ref(Hint), h));
534 }
535
536
537 void
538 Hints::audio (shared_ptr<AudioBuffers> audio, DCPTime time)
539 {
540         _analyser.analyse (audio, time);
541 }
542
543
544 void
545 Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
546 {
547         _writer->write (text, type, track, period);
548
549         switch (type) {
550         case TextType::CLOSED_CAPTION:
551                 closed_caption (text, period);
552                 break;
553         case TextType::OPEN_SUBTITLE:
554                 open_subtitle (text, period);
555                 break;
556         default:
557                 break;
558         }
559 }
560
561
562 void
563 Hints::closed_caption (PlayerText text, DCPTimePeriod period)
564 {
565         int lines = text.string.size();
566         for (auto i: text.string) {
567                 if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
568                         ++lines;
569                         if (!_long_ccap) {
570                                 _long_ccap = true;
571                                 hint (
572                                         String::compose(
573                                                 "At least one of your closed caption lines has more than %1 characters.  "
574                                                 "It is advisable to make each line %1 characters at most in length.",
575                                                 MAX_CLOSED_CAPTION_LENGTH,
576                                                 MAX_CLOSED_CAPTION_LENGTH)
577                                      );
578                         }
579                 }
580         }
581
582         if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
583                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
584                 _too_many_ccap_lines = true;
585         }
586
587         /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
588         if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
589                 _overlap_ccap = true;
590                 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs.  Change your DCP standard to SMPTE."));
591         }
592
593         _last_ccap = period;
594 }
595
596
597 void
598 Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
599 {
600         if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
601                 _early_subtitle = true;
602                 hint (_("It is advisable to put your first subtitle at least 4 seconds after the start of the DCP to make sure it is seen."));
603         }
604
605         int const vfr = film()->video_frame_rate ();
606
607         if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
608                 _short_subtitle = true;
609                 hint (_("At least one of your subtitles lasts less than 15 frames.  It is advisable to make each subtitle at least 15 frames long."));
610         }
611
612         if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
613                 _subtitles_too_close = true;
614                 hint (_("At least one of your subtitles starts less than 2 frames after the previous one.  It is advisable to make the gap between subtitles at least 2 frames."));
615         }
616
617         struct VPos
618         {
619         public:
620                 dcp::VAlign align;
621                 float position;
622
623                 bool operator<(VPos const& other) const {
624                         if (static_cast<int>(align) != static_cast<int>(other.align)) {
625                                 return static_cast<int>(align) < static_cast<int>(other.align);
626                         }
627                         return position < other.position;
628                 }
629         };
630
631         /* This is rather an approximate way to count distinct lines, but I guess it will do;
632          * to make it better we need to take into account font metrics, and the SMPTE alignment
633          * debacle, and so on.
634          */
635         std::set<VPos> lines;
636         for (auto const& line: text.string) {
637                 lines.insert({ line.v_align(), line.v_position() });
638         }
639
640         if (lines.size() > 3 && !_too_many_subtitle_lines) {
641                 _too_many_subtitle_lines = true;
642                 hint (_("At least one of your subtitles has more than 3 lines.  It is advisable to use no more than 3 lines."));
643         }
644
645         size_t longest_line = 0;
646         for (auto const& i: text.string) {
647                 longest_line = max (longest_line, i.text().length());
648         }
649
650         if (longest_line > 52) {
651                 _long_subtitle = true;
652         }
653
654         if (longest_line > 79) {
655                 _very_long_subtitle = true;
656         }
657
658         _last_subtitle = period;
659 }
660
661
662 void
663 Hints::check_ffec_and_ffmc_in_smpte_feature ()
664 {
665         auto f = film();
666         if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::ContentKind::FEATURE && (!f->marker(dcp::Marker::FFEC) || !f->marker(dcp::Marker::FFMC))) {
667                 hint (_("SMPTE DCPs with the type FTR (feature) should have markers for the first frame of end credits (FFEC) and the first frame of moving credits (FFMC).  You should add these markers using the 'Markers' button in the \"DCP\" tab."));
668         }
669 }
670
671
672 void
673 Hints::join ()
674 {
675         _thread.join ();
676 }
677
678
679 void
680 Hints::check_subtitle_languages()
681 {
682         for (auto i: film()->content()) {
683                 for (auto j: i->text) {
684                         if (j->use() && j->type() == TextType::OPEN_SUBTITLE && !j->language()) {
685                                 hint (_("At least one piece of subtitle content has no specified language.  "
686                                         "It is advisable to set the language for each piece of subtitle content "
687                                         "in the \"Content→Timed text\" or \"Content→Open subtitles\" tab."));
688                                 return;
689                         }
690                 }
691         }
692 }
693
694
695 void
696 Hints::check_audio_language ()
697 {
698         auto content = film()->content();
699         auto mapped_audio =
700                 std::find_if(content.begin(), content.end(), [](shared_ptr<const Content> c) {
701                         return c->audio && !c->audio->mapping().mapped_output_channels().empty();
702                 });
703
704         if (mapped_audio != content.end() && !film()->audio_language()) {
705                 hint (_("Some of your content has audio but you have not set the audio language.  It is advisable to set the audio language "
706                         "in the \"DCP\" tab unless your audio has no spoken parts."));
707         }
708 }
709
710
711 void
712 Hints::check_certificates ()
713 {
714         auto bad = Config::instance()->check_certificates();
715         if (!bad) {
716                 return;
717         }
718
719         switch (*bad) {
720         case Config::BAD_SIGNER_UTF8_STRINGS:
721                 hint(_("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs contains a small error "
722                        "which will prevent DCPs from being validated correctly on some systems.  It is advisable to "
723                        "re-create the signing certificate chain by clicking the \"Re-make certificates and key...\" "
724                        "button in the Keys page of Preferences."));
725                 break;
726         case Config::BAD_SIGNER_VALIDITY_TOO_LONG:
727                 hint(_("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs has a validity period "
728                        "that is too long.  This will cause problems playing back DCPs on some systems. "
729                        "It is advisable to re-create the signing certificate chain by clicking the "
730                        "\"Re-make certificates and key...\" button in the Keys page of Preferences."));
731                 break;
732         default:
733                 /* Some bad situations can't happen here as DCP-o-matic would have refused to start until they are fixed */
734                 break;
735         }
736 }
737
738
739 void
740 Hints::check_8_or_16_audio_channels()
741 {
742         auto const channels = film()->audio_channels();
743         if (channels != 8 && channels != 16) {
744                 hint(String::compose(_("Your DCP has %1 audio channels, rather than 8 or 16.  This may cause some distributors to raise QC errors when they check your DCP.  To avoid this, set the DCP audio channels to 8 or 16."), channels));
745         }
746 }
747