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