Add hint when making a FTR without FFEC/FFMC markers (#1804).
[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         : _film (film)
60         , _long_ccap (false)
61         , _overlap_ccap (false)
62         , _too_many_ccap_lines (false)
63         , _stop (false)
64 {
65
66 }
67
68 void
69 Hints::start ()
70 {
71         _thread = boost::thread (bind(&Hints::thread, this));
72 }
73
74 Hints::~Hints ()
75 {
76         boost::this_thread::disable_interruption dis;
77
78         try {
79                 _stop = true;
80                 _thread.interrupt ();
81                 _thread.join ();
82         } catch (...) {}
83 }
84
85
86 void
87 Hints::check_few_audio_channels ()
88 {
89         if (film()->audio_channels() < 6) {
90                 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."));
91         }
92 }
93
94
95 void
96 Hints::check_upmixers ()
97 {
98         AudioProcessor const * ap = film()->audio_processor();
99         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
100                 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."));
101         }
102 }
103
104
105 void
106 Hints::check_incorrect_container ()
107 {
108         int narrower_than_scope = 0;
109         int scope = 0;
110         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
111                 if (i->video) {
112                         Ratio const * r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio());
113                         if (r && r->id() == "239") {
114                                 ++scope;
115                         } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") {
116                                 ++narrower_than_scope;
117                         }
118                 }
119         }
120
121         string const film_container = film()->container()->id();
122
123         if (scope && !narrower_than_scope && film_container == "185") {
124                 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."));
125         }
126
127         if (!scope && narrower_than_scope && film_container == "239") {
128                 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."));
129         }
130 }
131
132
133 void
134 Hints::check_unusual_container ()
135 {
136         string const film_container = film()->container()->id();
137         if (film_container != "185" && film_container != "239" && film_container != "190") {
138                 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"));
139         }
140 }
141
142
143 void
144 Hints::check_high_j2k_bandwidth ()
145 {
146         if (film()->j2k_bandwidth() >= 245000000) {
147                 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."));
148         }
149 }
150
151
152 void
153 Hints::check_frame_rate ()
154 {
155         shared_ptr<const Film> f = film ();
156         switch (f->video_frame_rate()) {
157         case 24:
158                 /* Fine */
159                 break;
160         case 25:
161         {
162                 /* You might want to go to 24 */
163                 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);
164                 if (f->interop()) {
165                         base += "  ";
166                         base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
167                 }
168                 hint (base);
169                 break;
170         }
171         case 30:
172                 /* 30fps: we can't really offer any decent solutions */
173                 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."));
174                 break;
175         case 48:
176         case 50:
177         case 60:
178                 /* You almost certainly want to go to half frame rate */
179                 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));
180                 break;
181         }
182 }
183
184
185 void
186 Hints::check_speed_up ()
187 {
188         optional<double> lowest_speed_up;
189         optional<double> highest_speed_up;
190         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
191                 double spu = film()->active_frame_rate_change(i->position()).speed_up;
192                 if (!lowest_speed_up || spu < *lowest_speed_up) {
193                         lowest_speed_up = spu;
194                 }
195                 if (!highest_speed_up || spu > *highest_speed_up) {
196                         highest_speed_up = spu;
197                 }
198         }
199
200         double worst_speed_up = 1;
201         if (highest_speed_up) {
202                 worst_speed_up = *highest_speed_up;
203         }
204         if (lowest_speed_up) {
205                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
206         }
207
208         if (worst_speed_up > 25.5/24.0) {
209                 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."));
210         }
211
212 }
213
214
215 void
216 Hints::check_big_font_files ()
217 {
218         bool big_font_files = false;
219         if (film()->interop ()) {
220                 BOOST_FOREACH (shared_ptr<Content> i, film()->content()) {
221                         BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
222                                 BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
223                                         optional<boost::filesystem::path> const p = k->file ();
224                                         if (p && boost::filesystem::file_size(p.get()) >= (640 * 1024)) {
225                                                 big_font_files = true;
226                                         }
227                                 }
228                         }
229                 }
230         }
231
232         if (big_font_files) {
233                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
234         }
235 }
236
237
238 void
239 Hints::check_vob ()
240 {
241         int vob = 0;
242         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
243                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
244                         ++vob;
245                 }
246         }
247
248         if (vob > 1) {
249                 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));
250         }
251 }
252
253
254 void
255 Hints::check_3d_in_2d ()
256 {
257         int three_d = 0;
258         BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
259                 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
260                         ++three_d;
261                 }
262         }
263
264         if (three_d > 0 && !film()->three_d()) {
265                 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.)"));
266         }
267 }
268
269
270 void
271 Hints::check_loudness ()
272 {
273         boost::filesystem::path path = film()->audio_analysis_path(film()->playlist());
274         if (boost::filesystem::exists (path)) {
275                 try {
276                         shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
277
278                         string ch;
279
280                         vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
281                         vector<float> true_peak = an->true_peak ();
282
283                         for (size_t i = 0; i < sample_peak.size(); ++i) {
284                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
285                                 float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
286                                 if (peak_dB > -3) {
287                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
288                                 }
289                         }
290
291                         ch = ch.substr (0, ch.length() - 2);
292
293                         if (!ch.empty ()) {
294                                 hint (String::compose (
295                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
296                                               ch
297                                               )
298                                         );
299                         }
300                 } catch (OldFormatError& e) {
301                         /* The audio analysis is too old to load in; just skip this hint as if
302                            it had never been run.
303                         */
304                 }
305         }
306 }
307
308
309 void
310 Hints::thread ()
311 {
312         shared_ptr<const Film> film = _film.lock ();
313         if (!film) {
314                 return;
315         }
316
317         ContentList content = film->content ();
318
319         check_big_font_files ();
320         check_few_audio_channels ();
321         check_upmixers ();
322         check_incorrect_container ();
323         check_unusual_container ();
324         check_high_j2k_bandwidth ();
325         check_frame_rate ();
326         check_speed_up ();
327         check_vob ();
328         check_3d_in_2d ();
329         check_loudness ();
330         check_ffec_and_ffmc_in_smpte_feature ();
331
332         emit (bind(boost::ref(Progress), _("Examining closed captions")));
333
334         shared_ptr<Player> player (new Player(film));
335         player->set_ignore_video ();
336         player->set_ignore_audio ();
337         player->Text.connect (bind(&Hints::text, this, _1, _2, _4));
338
339         struct timeval last_pulse;
340         gettimeofday (&last_pulse, 0);
341
342         try {
343                 while (!player->pass()) {
344
345                         struct timeval now;
346                         gettimeofday (&now, 0);
347                         if ((seconds(now) - seconds(last_pulse)) > 1) {
348                                 if (_stop) {
349                                         break;
350                                 }
351                                 emit (bind (boost::ref(Pulse)));
352                                 last_pulse = now;
353                         }
354                 }
355         } catch (...) {
356                 store_current ();
357         }
358
359         emit (bind(boost::ref(Finished)));
360 }
361
362 void
363 Hints::hint (string h)
364 {
365         emit(bind(boost::ref(Hint), h));
366 }
367
368 void
369 Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
370 {
371         if (type != TEXT_CLOSED_CAPTION) {
372                 return;
373         }
374
375         int lines = text.string.size();
376         BOOST_FOREACH (StringText i, text.string) {
377                 if (utf8_strlen(i.text()) > CLOSED_CAPTION_LENGTH) {
378                         ++lines;
379                         if (!_long_ccap) {
380                                 _long_ccap = true;
381                                 hint (String::compose(_("Some of your closed captions have lines longer than %1 characters, so they will probably be word-wrapped."), CLOSED_CAPTION_LENGTH));
382                         }
383                 }
384         }
385
386         if (!_too_many_ccap_lines && lines > CLOSED_CAPTION_LINES) {
387                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), CLOSED_CAPTION_LINES));
388                 _too_many_ccap_lines = true;
389         }
390
391         /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
392         if (film()->interop() && !_overlap_ccap && _last && _last->overlap(period)) {
393                 _overlap_ccap = true;
394                 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs.  Change your DCP standard to SMPTE."));
395         }
396
397         _last = period;
398 }
399
400
401 shared_ptr<const Film>
402 Hints::film () const
403 {
404         shared_ptr<const Film> film = _film.lock ();
405         DCPOMATIC_ASSERT (film);
406         return film;
407 }
408
409
410 void
411 Hints::check_ffec_and_ffmc_in_smpte_feature ()
412 {
413         shared_ptr<const Film> f = film();
414         if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::FEATURE && (!f->marker(dcp::FFEC) || !f->marker(dcp::FFMC))) {
415                 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."));
416         }
417 }