C++11 tidying.
[dcpomatic.git] / src / lib / hints.cc
1 /*
2     Copyright (C) 2016-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
22 #include "dcp_content_type.h"
23 #include "hints.h"
24 #include "types.h"
25 #include "film.h"
26 #include "content.h"
27 #include "video_content.h"
28 #include "text_content.h"
29 #include "audio_processor.h"
30 #include "font.h"
31 #include "font_data.h"
32 #include "ratio.h"
33 #include "audio_analysis.h"
34 #include "compose.hpp"
35 #include "util.h"
36 #include "cross.h"
37 #include "player.h"
38 #include "writer.h"
39 #include <dcp/cpl.h>
40 #include <dcp/raw_convert.h>
41 #include <dcp/reel.h>
42 #include <dcp/reel_closed_caption_asset.h>
43 #include <dcp/reel_subtitle_asset.h>
44 #include <boost/algorithm/string.hpp>
45 #include <iostream>
46
47 #include "i18n.h"
48
49
50 using std::cout;
51 using std::make_shared;
52 using std::max;
53 using std::min;
54 using std::pair;
55 using std::shared_ptr;
56 using std::string;
57 using std::vector;
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 (_("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."));
112         }
113 }
114
115
116 void
117 Hints::check_upmixers ()
118 {
119         auto ap = film()->audio_processor();
120         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
121                 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."));
122         }
123 }
124
125
126 void
127 Hints::check_incorrect_container ()
128 {
129         int narrower_than_scope = 0;
130         int scope = 0;
131         for (auto i: film()->content()) {
132                 if (i->video) {
133                         auto const r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio());
134                         if (r && r->id() == "239") {
135                                 ++scope;
136                         } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") {
137                                 ++narrower_than_scope;
138                         }
139                 }
140         }
141
142         string const film_container = film()->container()->id();
143
144         if (scope && !narrower_than_scope && film_container == "185") {
145                 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."));
146         }
147
148         if (!scope && narrower_than_scope && film_container == "239") {
149                 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."));
150         }
151 }
152
153
154 void
155 Hints::check_unusual_container ()
156 {
157         auto const film_container = film()->container()->id();
158         if (film_container != "185" && film_container != "239") {
159                 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"));
160         }
161 }
162
163
164 void
165 Hints::check_high_j2k_bandwidth ()
166 {
167         if (film()->j2k_bandwidth() >= 245000000) {
168                 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."));
169         }
170 }
171
172
173 void
174 Hints::check_frame_rate ()
175 {
176         auto f = film ();
177         switch (f->video_frame_rate()) {
178         case 24:
179                 /* Fine */
180                 break;
181         case 25:
182         {
183                 /* You might want to go to 24 */
184                 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);
185                 if (f->interop()) {
186                         base += "  ";
187                         base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
188                 }
189                 hint (base);
190                 break;
191         }
192         case 30:
193                 /* 30fps: we can't really offer any decent solutions */
194                 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."));
195                 break;
196         case 48:
197         case 50:
198         case 60:
199                 /* You almost certainly want to go to half frame rate */
200                 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.  You are advised to change the DCP frame rate to %2 fps."), f->video_frame_rate(), f->video_frame_rate() / 2));
201                 break;
202         }
203 }
204
205
206 void
207 Hints::check_4k_3d ()
208 {
209         auto f = film();
210         if (f->resolution() == Resolution::FOUR_K && f->three_d()) {
211                 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."));
212         }
213 }
214
215
216 void
217 Hints::check_speed_up ()
218 {
219         optional<double> lowest_speed_up;
220         optional<double> highest_speed_up;
221         for (auto i: film()->content()) {
222                 double spu = film()->active_frame_rate_change(i->position()).speed_up;
223                 if (!lowest_speed_up || spu < *lowest_speed_up) {
224                         lowest_speed_up = spu;
225                 }
226                 if (!highest_speed_up || spu > *highest_speed_up) {
227                         highest_speed_up = spu;
228                 }
229         }
230
231         double worst_speed_up = 1;
232         if (highest_speed_up) {
233                 worst_speed_up = *highest_speed_up;
234         }
235         if (lowest_speed_up) {
236                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
237         }
238
239         if (worst_speed_up > 25.5/24.0) {
240                 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.  You are advised to set your DCP frame rate to one closer to your content, provided that your target projection systems support your chosen DCP rate."));
241         }
242
243 }
244
245
246 void
247 Hints::check_interop ()
248 {
249         if (film()->interop()) {
250                 hint (_("In general it is now advisable to make SMPTE DCPs unless you have a particular reason to use Interop.  You are advised to set your DCP to use the SMPTE standard in the \"DCP\" tab."));
251         }
252 }
253
254
255 void
256 Hints::check_big_font_files ()
257 {
258         bool big_font_files = false;
259         if (film()->interop ()) {
260                 for (auto i: film()->content()) {
261                         for (auto j: i->text) {
262                                 for (auto k: j->fonts()) {
263                                         auto const p = k->file ();
264                                         if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
265                                                 big_font_files = true;
266                                         }
267                                 }
268                         }
269                 }
270         }
271
272         if (big_font_files) {
273                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
274         }
275 }
276
277
278 void
279 Hints::check_vob ()
280 {
281         int vob = 0;
282         for (auto i: film()->content()) {
283                 if (boost::algorithm::starts_with(i->path(0).filename().string(), "VTS_")) {
284                         ++vob;
285                 }
286         }
287
288         if (vob > 1) {
289                 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));
290         }
291 }
292
293
294 void
295 Hints::check_3d_in_2d ()
296 {
297         int three_d = 0;
298         for (auto i: film()->content()) {
299                 if (i->video && i->video->frame_type() != VideoFrameType::TWO_D) {
300                         ++three_d;
301                 }
302         }
303
304         if (three_d > 0 && !film()->three_d()) {
305                 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.)"));
306         }
307 }
308
309
310 /** @return true if the loudness could be checked, false if it could not because no analysis was available */
311 bool
312 Hints::check_loudness ()
313 {
314         auto path = film()->audio_analysis_path(film()->playlist());
315         if (!boost::filesystem::exists(path)) {
316                 return false;
317         }
318
319         try {
320                 auto an = make_shared<AudioAnalysis>(path);
321
322                 string ch;
323
324                 auto sample_peak = an->sample_peak ();
325                 auto true_peak = an->true_peak ();
326
327                 for (size_t i = 0; i < sample_peak.size(); ++i) {
328                         float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
329                         float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
330                         if (peak_dB > -3) {
331                                 ch += dcp::raw_convert<string>(short_audio_channel_name(i)) + ", ";
332                         }
333                 }
334
335                 ch = ch.substr (0, ch.length() - 2);
336
337                 if (!ch.empty()) {
338                         hint(String::compose(
339                                         _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
340                                         ch
341                                         )
342                              );
343                 }
344         } catch (OldFormatError& e) {
345                 /* The audio analysis is too old to load in */
346                 return false;
347         }
348
349         return true;
350 }
351
352
353 static
354 bool
355 subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
356 {
357         return asset && asset->file() && boost::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK);
358 }
359
360
361 void
362 Hints::check_out_of_range_markers ()
363 {
364         auto const length = film()->length();
365         for (auto const& i: film()->markers()) {
366                 if (i.second >= length) {
367                         hint (_("At least one marker comes after the end of the project and will be ignored."));
368                 }
369         }
370 }
371
372
373 void
374 Hints::thread ()
375 try
376 {
377         start_of_thread ("Hints");
378
379         auto film = _film.lock ();
380         if (!film) {
381                 return;
382         }
383
384         auto content = film->content ();
385
386         check_interop ();
387         check_big_font_files ();
388         check_few_audio_channels ();
389         check_upmixers ();
390         check_incorrect_container ();
391         check_unusual_container ();
392         check_high_j2k_bandwidth ();
393         check_frame_rate ();
394         check_4k_3d ();
395         check_speed_up ();
396         check_vob ();
397         check_3d_in_2d ();
398         auto const check_loudness_done = check_loudness ();
399         check_ffec_and_ffmc_in_smpte_feature ();
400         check_out_of_range_markers ();
401         check_text_languages ();
402
403         if (check_loudness_done) {
404                 emit (bind(boost::ref(Progress), _("Examining subtitles and closed captions")));
405         } else {
406                 emit (bind(boost::ref(Progress), _("Examining audio, subtitles and closed captions")));
407         }
408
409         auto player = make_shared<Player>(film);
410         player->set_ignore_video ();
411         if (check_loudness_done) {
412                 /* We don't need to analyse audio because we already loaded a suitable analysis */
413                 player->set_ignore_audio ();
414         }
415         player->Audio.connect (bind(&Hints::audio, this, _1, _2));
416         player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4));
417
418         struct timeval last_pulse;
419         gettimeofday (&last_pulse, 0);
420
421         while (!player->pass()) {
422
423                 struct timeval now;
424                 gettimeofday (&now, 0);
425                 if ((seconds(now) - seconds(last_pulse)) > 1) {
426                         if (_stop) {
427                                 return;
428                         }
429                         emit (bind (boost::ref(Pulse)));
430                         last_pulse = now;
431                 }
432         }
433
434         if (!check_loudness_done) {
435                 _analyser.finish ();
436                 _analyser.get().write(film->audio_analysis_path(film->playlist()));
437                 check_loudness ();
438         }
439
440         _writer->write (player->get_subtitle_fonts());
441
442         if (_long_subtitle && !_very_long_subtitle) {
443                 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."));
444         } else if (_very_long_subtitle) {
445                 hint (_("At least one of your subtitle lines has more than 79 characters.  You should make each line 79 characters at most in length."));
446         }
447
448         bool ccap_xml_too_big = false;
449         bool ccap_mxf_too_big = false;
450         bool subs_mxf_too_big = false;
451
452         auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
453         boost::filesystem::remove_all (dcp_dir);
454
455         _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
456
457         dcp::DCP dcp (dcp_dir);
458         dcp.read ();
459         DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
460         for (auto reel: dcp.cpls()[0]->reels()) {
461                 for (auto ccap: reel->closed_captions()) {
462                         if (ccap->asset() && ccap->asset()->xml_as_string().length() > static_cast<size_t>(MAX_CLOSED_CAPTION_XML_SIZE - SIZE_SLACK) && !ccap_xml_too_big) {
463                                 hint (_(
464                                                 "At least one of your closed caption files' XML part is larger than " MAX_CLOSED_CAPTION_XML_SIZE_TEXT
465                                                 ".  You should divide the DCP into shorter reels."
466                                        ));
467                                 ccap_xml_too_big = true;
468                         }
469                         if (subtitle_mxf_too_big(ccap->asset()) && !ccap_mxf_too_big) {
470                                 hint (_(
471                                                 "At least one of your closed caption files is larger than " MAX_TEXT_MXF_SIZE_TEXT
472                                                 " in total.  You should divide the DCP into shorter reels."
473                                        ));
474                                 ccap_mxf_too_big = true;
475                         }
476                 }
477                 if (reel->main_subtitle() && subtitle_mxf_too_big(reel->main_subtitle()->asset()) && !subs_mxf_too_big) {
478                         hint (_(
479                                         "At least one of your subtitle files is larger than " MAX_TEXT_MXF_SIZE_TEXT " in total.  "
480                                         "You should divide the DCP into shorter reels."
481                                ));
482                         subs_mxf_too_big = true;
483                 }
484         }
485         boost::filesystem::remove_all (dcp_dir);
486
487         emit (bind(boost::ref(Finished)));
488 }
489 catch (boost::thread_interrupted)
490 {
491         /* The Hints object is being destroyed before it has finished, so just give up */
492 }
493 catch (...)
494 {
495         store_current ();
496 }
497
498
499 void
500 Hints::hint (string h)
501 {
502         emit(bind(boost::ref(Hint), h));
503 }
504
505
506 void
507 Hints::audio (shared_ptr<AudioBuffers> audio, DCPTime time)
508 {
509         _analyser.analyse (audio, time);
510 }
511
512
513 void
514 Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
515 {
516         _writer->write (text, type, track, period);
517
518         switch (type) {
519         case TextType::CLOSED_CAPTION:
520                 closed_caption (text, period);
521                 break;
522         case TextType::OPEN_SUBTITLE:
523                 open_subtitle (text, period);
524                 break;
525         default:
526                 break;
527         }
528 }
529
530
531 void
532 Hints::closed_caption (PlayerText text, DCPTimePeriod period)
533 {
534         int lines = text.string.size();
535         for (auto i: text.string) {
536                 if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
537                         ++lines;
538                         if (!_long_ccap) {
539                                 _long_ccap = true;
540                                 hint (
541                                         String::compose(
542                                                 "At least one of your closed caption lines has more than %1 characters.  "
543                                                 "It is advisable to make each line %1 characters at most in length.",
544                                                 MAX_CLOSED_CAPTION_LENGTH,
545                                                 MAX_CLOSED_CAPTION_LENGTH)
546                                      );
547                         }
548                 }
549         }
550
551         if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
552                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
553                 _too_many_ccap_lines = true;
554         }
555
556         /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
557         if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
558                 _overlap_ccap = true;
559                 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs.  Change your DCP standard to SMPTE."));
560         }
561
562         _last_ccap = period;
563 }
564
565
566 void
567 Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
568 {
569         if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
570                 _early_subtitle = true;
571                 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."));
572         }
573
574         int const vfr = film()->video_frame_rate ();
575
576         if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
577                 _short_subtitle = true;
578                 hint (_("At least one of your subtitles lasts less than 15 frames.  It is advisable to make each subtitle at least 15 frames long."));
579         }
580
581         if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
582                 _subtitles_too_close = true;
583                 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."));
584         }
585
586         if (text.string.size() > 3 && !_too_many_subtitle_lines) {
587                 _too_many_subtitle_lines = true;
588                 hint (_("At least one of your subtitles has more than 3 lines.  It is advisable to use no more than 3 lines."));
589         }
590
591         size_t longest_line = 0;
592         for (auto const& i: text.string) {
593                 longest_line = max (longest_line, i.text().length());
594         }
595
596         if (longest_line > 52) {
597                 _long_subtitle = true;
598         }
599
600         if (longest_line > 79) {
601                 _very_long_subtitle = true;
602         }
603
604         _last_subtitle = period;
605 }
606
607
608 void
609 Hints::check_ffec_and_ffmc_in_smpte_feature ()
610 {
611         auto f = film();
612         if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::ContentKind::FEATURE && (!f->marker(dcp::Marker::FFEC) || !f->marker(dcp::Marker::FFMC))) {
613                 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."));
614         }
615 }
616
617
618 void
619 Hints::join ()
620 {
621         _thread.join ();
622 }
623
624
625 void
626 Hints::check_text_languages ()
627 {
628         for (auto i: film()->content()) {
629                 for (auto j: i->text) {
630                         if (j->use() && !j->language()) {
631                                 hint (_("At least one piece of subtitle or closed caption content has no specified language.  "
632                                         "It is advisable to set the language for each piece of subtitle or closed caption content "
633                                         "in the \"Content→Timed text\", \"Content→Open subtitles\" or \"Content→Closed captions\" tab."));
634                                 return;
635                         }
636                 }
637         }
638 }