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