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