Hint on making Interop DCPs (#1949).
[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 Hints::Hints (weak_ptr<const Film> film)
72         : WeakConstFilm (film)
73         , _writer (new Writer(film, weak_ptr<Job>(), true))
74         , _long_ccap (false)
75         , _overlap_ccap (false)
76         , _too_many_ccap_lines (false)
77         , _early_subtitle (false)
78         , _short_subtitle (false)
79         , _subtitles_too_close (false)
80         , _too_many_subtitle_lines (false)
81         , _long_subtitle (false)
82         , _stop (false)
83 {
84
85 }
86
87 void
88 Hints::start ()
89 {
90         _thread = boost::thread (bind(&Hints::thread, this));
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                         Ratio 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" && film_container != "190") {
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_speed_up ()
206 {
207         optional<double> lowest_speed_up;
208         optional<double> highest_speed_up;
209         for (auto i: film()->content()) {
210                 double spu = film()->active_frame_rate_change(i->position()).speed_up;
211                 if (!lowest_speed_up || spu < *lowest_speed_up) {
212                         lowest_speed_up = spu;
213                 }
214                 if (!highest_speed_up || spu > *highest_speed_up) {
215                         highest_speed_up = spu;
216                 }
217         }
218
219         double worst_speed_up = 1;
220         if (highest_speed_up) {
221                 worst_speed_up = *highest_speed_up;
222         }
223         if (lowest_speed_up) {
224                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
225         }
226
227         if (worst_speed_up > 25.5/24.0) {
228                 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."));
229         }
230
231 }
232
233
234 void
235 Hints::check_interop ()
236 {
237         if (film()->interop()) {
238                 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."));
239         }
240 }
241
242
243 void
244 Hints::check_big_font_files ()
245 {
246         bool big_font_files = false;
247         if (film()->interop ()) {
248                 for (auto i: film()->content()) {
249                         for (auto j: i->text) {
250                                 for (auto k: j->fonts()) {
251                                         optional<boost::filesystem::path> const p = k->file ();
252                                         if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
253                                                 big_font_files = true;
254                                         }
255                                 }
256                         }
257                 }
258         }
259
260         if (big_font_files) {
261                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
262         }
263 }
264
265
266 void
267 Hints::check_vob ()
268 {
269         int vob = 0;
270         for (auto i: film()->content()) {
271                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
272                         ++vob;
273                 }
274         }
275
276         if (vob > 1) {
277                 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));
278         }
279 }
280
281
282 void
283 Hints::check_3d_in_2d ()
284 {
285         int three_d = 0;
286         for (auto i: film()->content()) {
287                 if (i->video && i->video->frame_type() != VideoFrameType::TWO_D) {
288                         ++three_d;
289                 }
290         }
291
292         if (three_d > 0 && !film()->three_d()) {
293                 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.)"));
294         }
295 }
296
297
298 void
299 Hints::check_loudness ()
300 {
301         auto path = film()->audio_analysis_path(film()->playlist());
302         if (boost::filesystem::exists (path)) {
303                 try {
304                         auto an = make_shared<AudioAnalysis>(path);
305
306                         string ch;
307
308                         auto sample_peak = an->sample_peak ();
309                         auto true_peak = an->true_peak ();
310
311                         for (size_t i = 0; i < sample_peak.size(); ++i) {
312                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
313                                 float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
314                                 if (peak_dB > -3) {
315                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
316                                 }
317                         }
318
319                         ch = ch.substr (0, ch.length() - 2);
320
321                         if (!ch.empty ()) {
322                                 hint (String::compose (
323                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
324                                               ch
325                                               )
326                                         );
327                         }
328                 } catch (OldFormatError& e) {
329                         /* The audio analysis is too old to load in; just skip this hint as if
330                            it had never been run.
331                         */
332                 }
333         }
334 }
335
336
337 static
338 bool
339 subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
340 {
341         return asset && asset->file() && boost::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK);
342 }
343
344
345 void
346 Hints::check_out_of_range_markers ()
347 {
348         auto const length = film()->length();
349         for (auto const& i: film()->markers()) {
350                 if (i.second >= length) {
351                         hint (_("At least one marker comes after the end of the project and will be ignored."));
352                 }
353         }
354 }
355
356
357 void
358 Hints::thread ()
359 {
360         auto film = _film.lock ();
361         if (!film) {
362                 return;
363         }
364
365         auto content = film->content ();
366
367         check_interop ();
368         check_big_font_files ();
369         check_few_audio_channels ();
370         check_upmixers ();
371         check_incorrect_container ();
372         check_unusual_container ();
373         check_high_j2k_bandwidth ();
374         check_frame_rate ();
375         check_speed_up ();
376         check_vob ();
377         check_3d_in_2d ();
378         check_loudness ();
379         check_ffec_and_ffmc_in_smpte_feature ();
380         check_out_of_range_markers ();
381
382         emit (bind(boost::ref(Progress), _("Examining closed captions")));
383
384         auto player = make_shared<Player>(film);
385         player->set_ignore_video ();
386         player->set_ignore_audio ();
387         player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4));
388
389         struct timeval last_pulse;
390         gettimeofday (&last_pulse, 0);
391
392         try {
393                 while (!player->pass()) {
394
395                         struct timeval now;
396                         gettimeofday (&now, 0);
397                         if ((seconds(now) - seconds(last_pulse)) > 1) {
398                                 if (_stop) {
399                                         break;
400                                 }
401                                 emit (bind (boost::ref(Pulse)));
402                                 last_pulse = now;
403                         }
404                 }
405         } catch (...) {
406                 store_current ();
407         }
408
409         _writer->write (player->get_subtitle_fonts());
410
411         bool ccap_xml_too_big = false;
412         bool ccap_mxf_too_big = false;
413         bool subs_mxf_too_big = false;
414
415         boost::filesystem::path dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
416         boost::filesystem::remove_all (dcp_dir);
417
418         try {
419                 _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
420         } catch (...) {
421                 store_current ();
422                 emit (bind(boost::ref(Finished)));
423                 return;
424         }
425
426         dcp::DCP dcp (dcp_dir);
427         dcp.read ();
428         DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
429         for (auto reel: dcp.cpls()[0]->reels()) {
430                 for (auto ccap: reel->closed_captions()) {
431                         if (ccap->asset() && ccap->asset()->xml_as_string().length() > static_cast<size_t>(MAX_CLOSED_CAPTION_XML_SIZE - SIZE_SLACK) && !ccap_xml_too_big) {
432                                 hint (_(
433                                                 "At least one of your closed caption files' XML part is larger than " MAX_CLOSED_CAPTION_XML_SIZE_TEXT
434                                                 ".  You should divide the DCP into shorter reels."
435                                        ));
436                                 ccap_xml_too_big = true;
437                         }
438                         if (subtitle_mxf_too_big(ccap->asset()) && !ccap_mxf_too_big) {
439                                 hint (_(
440                                                 "At least one of your closed caption files is larger than " MAX_TEXT_MXF_SIZE_TEXT
441                                                 " in total.  You should divide the DCP into shorter reels."
442                                        ));
443                                 ccap_mxf_too_big = true;
444                         }
445                 }
446                 if (reel->main_subtitle() && subtitle_mxf_too_big(reel->main_subtitle()->asset()) && !subs_mxf_too_big) {
447                         hint (_(
448                                         "At least one of your subtitle files is larger than " MAX_TEXT_MXF_SIZE_TEXT " in total.  "
449                                         "You should divide the DCP into shorter reels."
450                                ));
451                         subs_mxf_too_big = true;
452                 }
453         }
454         boost::filesystem::remove_all (dcp_dir);
455
456         emit (bind(boost::ref(Finished)));
457 }
458
459 void
460 Hints::hint (string h)
461 {
462         emit(bind(boost::ref(Hint), h));
463 }
464
465 void
466 Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
467 {
468         _writer->write (text, type, track, period);
469
470         switch (type) {
471         case TextType::CLOSED_CAPTION:
472                 closed_caption (text, period);
473                 break;
474         case TextType::OPEN_SUBTITLE:
475                 open_subtitle (text, period);
476                 break;
477         default:
478                 break;
479         }
480 }
481
482
483 void
484 Hints::closed_caption (PlayerText text, DCPTimePeriod period)
485 {
486         int lines = text.string.size();
487         for (auto i: text.string) {
488                 if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
489                         ++lines;
490                         if (!_long_ccap) {
491                                 _long_ccap = true;
492                                 hint (
493                                         String::compose(
494                                                 "At least one of your closed caption lines has more than %1 characters.  "
495                                                 "It is advisable to make each line %1 characters at most in length.",
496                                                 MAX_CLOSED_CAPTION_LENGTH,
497                                                 MAX_CLOSED_CAPTION_LENGTH)
498                                      );
499                         }
500                 }
501         }
502
503         if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
504                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
505                 _too_many_ccap_lines = true;
506         }
507
508         /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
509         if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
510                 _overlap_ccap = true;
511                 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs.  Change your DCP standard to SMPTE."));
512         }
513
514         _last_ccap = period;
515 }
516
517
518 void
519 Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
520 {
521         if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
522                 _early_subtitle = true;
523                 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."));
524         }
525
526         int const vfr = film()->video_frame_rate ();
527
528         if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
529                 _short_subtitle = true;
530                 hint (_("At least one of your subtitles lasts less than 15 frames.  It is advisable to make each subtitle at least 15 frames long."));
531         }
532
533         if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
534                 _subtitles_too_close = true;
535                 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."));
536         }
537
538         if (text.string.size() > 3 && !_too_many_subtitle_lines) {
539                 _too_many_subtitle_lines = true;
540                 hint (_("At least one of your subtitles has more than 3 lines.  It is advisable to use no more than 3 lines."));
541         }
542
543         size_t longest_line = 0;
544         for (auto const& i: text.string) {
545                 longest_line = max (longest_line, i.text().length());
546         }
547
548         if (longest_line > 52 && !_long_subtitle) {
549                 _long_subtitle = true;
550                 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."));
551         }
552
553         _last_subtitle = period;
554 }
555
556
557 void
558 Hints::check_ffec_and_ffmc_in_smpte_feature ()
559 {
560         auto f = film();
561         if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::ContentKind::FEATURE && (!f->marker(dcp::Marker::FFEC) || !f->marker(dcp::Marker::FFMC))) {
562                 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."));
563         }
564 }
565
566
567 void
568 Hints::join ()
569 {
570         _thread.join ();
571 }