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