Add WeakFilm and WeakConstFilm and use them a bit.
[dcpomatic.git] / src / lib / hints.cc
1 /*
2     Copyright (C) 2016-2020 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 "ratio.h"
31 #include "audio_analysis.h"
32 #include "compose.hpp"
33 #include "util.h"
34 #include "cross.h"
35 #include "player.h"
36 #include <dcp/raw_convert.h>
37 #include <boost/foreach.hpp>
38 #include <boost/algorithm/string.hpp>
39 #include <iostream>
40
41 #include "i18n.h"
42
43 using std::vector;
44 using std::string;
45 using std::pair;
46 using std::min;
47 using std::max;
48 using std::cout;
49 using boost::shared_ptr;
50 using boost::weak_ptr;
51 using boost::optional;
52 using boost::bind;
53 using namespace dcpomatic;
54 #if BOOST_VERSION >= 106100
55 using namespace boost::placeholders;
56 #endif
57
58 Hints::Hints (weak_ptr<const Film> film)
59         : WeakConstFilm (film)
60         , _long_ccap (false)
61         , _overlap_ccap (false)
62         , _too_many_ccap_lines (false)
63         , _early_subtitle (false)
64         , _short_subtitle (false)
65         , _subtitles_too_close (false)
66         , _too_many_subtitle_lines (false)
67         , _long_subtitle (false)
68         , _stop (false)
69 {
70
71 }
72
73 void
74 Hints::start ()
75 {
76         _thread = boost::thread (bind(&Hints::thread, this));
77 }
78
79 Hints::~Hints ()
80 {
81         boost::this_thread::disable_interruption dis;
82
83         try {
84                 _stop = true;
85                 _thread.interrupt ();
86                 _thread.join ();
87         } catch (...) {}
88 }
89
90
91 void
92 Hints::check_few_audio_channels ()
93 {
94         if (film()->audio_channels() < 6) {
95                 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."));
96         }
97 }
98
99
100 void
101 Hints::check_upmixers ()
102 {
103         AudioProcessor const * ap = film()->audio_processor();
104         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
105                 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."));
106         }
107 }
108
109
110 void
111 Hints::check_incorrect_container ()
112 {
113         int narrower_than_scope = 0;
114         int scope = 0;
115         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
116                 if (i->video) {
117                         Ratio const * r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio());
118                         if (r && r->id() == "239") {
119                                 ++scope;
120                         } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") {
121                                 ++narrower_than_scope;
122                         }
123                 }
124         }
125
126         string const film_container = film()->container()->id();
127
128         if (scope && !narrower_than_scope && film_container == "185") {
129                 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."));
130         }
131
132         if (!scope && narrower_than_scope && film_container == "239") {
133                 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."));
134         }
135 }
136
137
138 void
139 Hints::check_unusual_container ()
140 {
141         string const film_container = film()->container()->id();
142         if (film_container != "185" && film_container != "239" && film_container != "190") {
143                 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"));
144         }
145 }
146
147
148 void
149 Hints::check_high_j2k_bandwidth ()
150 {
151         if (film()->j2k_bandwidth() >= 245000000) {
152                 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."));
153         }
154 }
155
156
157 void
158 Hints::check_frame_rate ()
159 {
160         shared_ptr<const Film> f = film ();
161         switch (f->video_frame_rate()) {
162         case 24:
163                 /* Fine */
164                 break;
165         case 25:
166         {
167                 /* You might want to go to 24 */
168                 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);
169                 if (f->interop()) {
170                         base += "  ";
171                         base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
172                 }
173                 hint (base);
174                 break;
175         }
176         case 30:
177                 /* 30fps: we can't really offer any decent solutions */
178                 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."));
179                 break;
180         case 48:
181         case 50:
182         case 60:
183                 /* You almost certainly want to go to half frame rate */
184                 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));
185                 break;
186         }
187 }
188
189
190 void
191 Hints::check_speed_up ()
192 {
193         optional<double> lowest_speed_up;
194         optional<double> highest_speed_up;
195         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
196                 double spu = film()->active_frame_rate_change(i->position()).speed_up;
197                 if (!lowest_speed_up || spu < *lowest_speed_up) {
198                         lowest_speed_up = spu;
199                 }
200                 if (!highest_speed_up || spu > *highest_speed_up) {
201                         highest_speed_up = spu;
202                 }
203         }
204
205         double worst_speed_up = 1;
206         if (highest_speed_up) {
207                 worst_speed_up = *highest_speed_up;
208         }
209         if (lowest_speed_up) {
210                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
211         }
212
213         if (worst_speed_up > 25.5/24.0) {
214                 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."));
215         }
216
217 }
218
219
220 void
221 Hints::check_big_font_files ()
222 {
223         bool big_font_files = false;
224         if (film()->interop ()) {
225                 BOOST_FOREACH (shared_ptr<Content> i, film()->content()) {
226                         BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
227                                 BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
228                                         optional<boost::filesystem::path> const p = k->file ();
229                                         if (p && boost::filesystem::file_size(p.get()) >= (640 * 1024)) {
230                                                 big_font_files = true;
231                                         }
232                                 }
233                         }
234                 }
235         }
236
237         if (big_font_files) {
238                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
239         }
240 }
241
242
243 void
244 Hints::check_vob ()
245 {
246         int vob = 0;
247         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
248                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
249                         ++vob;
250                 }
251         }
252
253         if (vob > 1) {
254                 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));
255         }
256 }
257
258
259 void
260 Hints::check_3d_in_2d ()
261 {
262         int three_d = 0;
263         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
264                 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
265                         ++three_d;
266                 }
267         }
268
269         if (three_d > 0 && !film()->three_d()) {
270                 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.)"));
271         }
272 }
273
274
275 void
276 Hints::check_loudness ()
277 {
278         boost::filesystem::path path = film()->audio_analysis_path(film()->playlist());
279         if (boost::filesystem::exists (path)) {
280                 try {
281                         shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
282
283                         string ch;
284
285                         vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
286                         vector<float> true_peak = an->true_peak ();
287
288                         for (size_t i = 0; i < sample_peak.size(); ++i) {
289                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
290                                 float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
291                                 if (peak_dB > -3) {
292                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
293                                 }
294                         }
295
296                         ch = ch.substr (0, ch.length() - 2);
297
298                         if (!ch.empty ()) {
299                                 hint (String::compose (
300                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
301                                               ch
302                                               )
303                                         );
304                         }
305                 } catch (OldFormatError& e) {
306                         /* The audio analysis is too old to load in; just skip this hint as if
307                            it had never been run.
308                         */
309                 }
310         }
311 }
312
313
314 void
315 Hints::thread ()
316 {
317         shared_ptr<const Film> film = _film.lock ();
318         if (!film) {
319                 return;
320         }
321
322         ContentList content = film->content ();
323
324         check_big_font_files ();
325         check_few_audio_channels ();
326         check_upmixers ();
327         check_incorrect_container ();
328         check_unusual_container ();
329         check_high_j2k_bandwidth ();
330         check_frame_rate ();
331         check_speed_up ();
332         check_vob ();
333         check_3d_in_2d ();
334         check_loudness ();
335         check_ffec_and_ffmc_in_smpte_feature ();
336
337         emit (bind(boost::ref(Progress), _("Examining closed captions")));
338
339         shared_ptr<Player> player (new Player(film));
340         player->set_ignore_video ();
341         player->set_ignore_audio ();
342         player->Text.connect (bind(&Hints::text, this, _1, _2, _4));
343
344         struct timeval last_pulse;
345         gettimeofday (&last_pulse, 0);
346
347         try {
348                 while (!player->pass()) {
349
350                         struct timeval now;
351                         gettimeofday (&now, 0);
352                         if ((seconds(now) - seconds(last_pulse)) > 1) {
353                                 if (_stop) {
354                                         break;
355                                 }
356                                 emit (bind (boost::ref(Pulse)));
357                                 last_pulse = now;
358                         }
359                 }
360         } catch (...) {
361                 store_current ();
362         }
363
364         emit (bind(boost::ref(Finished)));
365 }
366
367 void
368 Hints::hint (string h)
369 {
370         emit(bind(boost::ref(Hint), h));
371 }
372
373 void
374 Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
375 {
376         switch (type) {
377         case TEXT_CLOSED_CAPTION:
378                 closed_caption (text, period);
379                 break;
380         case TEXT_OPEN_SUBTITLE:
381                 open_subtitle (text, period);
382                 break;
383         default:
384                 break;
385         }
386 }
387
388
389 void
390 Hints::closed_caption (PlayerText text, DCPTimePeriod period)
391 {
392         int lines = text.string.size();
393         BOOST_FOREACH (StringText i, text.string) {
394                 if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
395                         ++lines;
396                         if (!_long_ccap) {
397                                 _long_ccap = true;
398                                 hint (
399                                         String::compose(
400                                                 "At least one of your closed caption lines has more than %1 characters.  "
401                                                 "It is advisable to make each line %1 characters at most in length.",
402                                                 MAX_CLOSED_CAPTION_LENGTH,
403                                                 MAX_CLOSED_CAPTION_LENGTH)
404                                      );
405                         }
406                 }
407         }
408
409         if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
410                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
411                 _too_many_ccap_lines = true;
412         }
413
414         /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
415         if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
416                 _overlap_ccap = true;
417                 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs.  Change your DCP standard to SMPTE."));
418         }
419
420         _last_ccap = period;
421 }
422
423
424 void
425 Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
426 {
427         if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
428                 _early_subtitle = true;
429                 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."));
430         }
431
432         int const vfr = film()->video_frame_rate ();
433
434         if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
435                 _short_subtitle = true;
436                 hint (_("At least one of your subtitles lasts less than 15 frames.  It is advisable to make each subtitle at least 15 frames long."));
437         }
438
439         if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
440                 _subtitles_too_close = true;
441                 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."));
442         }
443
444         if (text.string.size() > 3 && !_too_many_subtitle_lines) {
445                 _too_many_subtitle_lines = true;
446                 hint (_("At least one of your subtitles has more than 3 lines.  It is advisable to use no more than 3 lines."));
447         }
448
449         size_t longest_line = 0;
450         BOOST_FOREACH (StringText const& i, text.string) {
451                 longest_line = max (longest_line, i.text().length());
452         }
453
454         if (longest_line > 52 && !_long_subtitle) {
455                 _long_subtitle = true;
456                 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."));
457         }
458
459         _last_subtitle = period;
460 }
461
462
463 void
464 Hints::check_ffec_and_ffmc_in_smpte_feature ()
465 {
466         shared_ptr<const Film> f = film();
467         if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::FEATURE && (!f->marker(dcp::FFEC) || !f->marker(dcp::FFMC))) {
468                 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."));
469         }
470 }
471
472
473 void
474 Hints::join ()
475 {
476         _thread.join ();
477 }