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