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