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