Supporters update.
[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 #if BOOST_VERSION >= 106100
53 using namespace boost::placeholders;
54 #endif
55
56 Hints::Hints (weak_ptr<const Film> film)
57         : _film (film)
58         , _thread (0)
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 = new boost::thread (bind(&Hints::thread, this));
71 }
72
73 Hints::~Hints ()
74 {
75         if (!_thread) {
76                 return;
77         }
78
79         try {
80                 {
81                         boost::mutex::scoped_lock lm (_mutex);
82                         _stop = true;
83                 }
84                 _thread->interrupt ();
85                 _thread->join ();
86         } catch (...) {
87
88         }
89
90         delete _thread;
91 }
92
93 void
94 Hints::thread ()
95 {
96         shared_ptr<const Film> film = _film.lock ();
97         if (!film) {
98                 return;
99         }
100
101         ContentList content = film->content ();
102
103         bool big_font_files = false;
104         if (film->interop ()) {
105                 BOOST_FOREACH (shared_ptr<Content> i, content) {
106                         BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
107                                 BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
108                                         optional<boost::filesystem::path> const p = k->file ();
109                                         if (p && boost::filesystem::file_size(p.get()) >= (640 * 1024)) {
110                                                 big_font_files = true;
111                                         }
112                                 }
113                         }
114                 }
115         }
116
117         if (big_font_files) {
118                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
119         }
120
121         if (film->audio_channels() < 6) {
122                 hint (_("Your DCP has fewer than 6 audio channels.  This may cause problems on some projectors."));
123         }
124
125         AudioProcessor const * ap = film->audio_processor();
126         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
127                 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."));
128         }
129
130         int narrower_than_scope = 0;
131         int scope = 0;
132         BOOST_FOREACH (shared_ptr<const Content> i, content) {
133                 if (i->video) {
134                         Ratio const * r = i->video->scale().ratio ();
135                         if (r && r->id() == "239") {
136                                 ++scope;
137                         } else if (r && r->id() != "239" && 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 is 2.35:1 or narrower 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         if (film_container != "185" && film_container != "239" && film_container != "190") {
154                 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"));
155         }
156
157         if (film->j2k_bandwidth() >= 245000000) {
158                 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."));
159         }
160
161         switch (film->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 (film->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."), film->video_frame_rate(), film->video_frame_rate() / 2));
185                 break;
186         }
187
188         optional<double> lowest_speed_up;
189         optional<double> highest_speed_up;
190         BOOST_FOREACH (shared_ptr<const Content> i, 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         int vob = 0;
213         BOOST_FOREACH (shared_ptr<const Content> i, content) {
214                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
215                         ++vob;
216                 }
217         }
218
219         if (vob > 1) {
220                 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));
221         }
222
223         int three_d = 0;
224         BOOST_FOREACH (shared_ptr<const Content> i, content) {
225                 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
226                         ++three_d;
227                 }
228         }
229
230         if (three_d > 0 && !film->three_d()) {
231                 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.)"));
232         }
233
234         boost::filesystem::path path = film->audio_analysis_path (film->playlist ());
235         if (boost::filesystem::exists (path)) {
236                 try {
237                         shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
238
239                         string ch;
240
241                         vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
242                         vector<float> true_peak = an->true_peak ();
243
244                         for (size_t i = 0; i < sample_peak.size(); ++i) {
245                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
246                                 float const peak_dB = 20 * log10 (peak) + an->gain_correction (film->playlist ());
247                                 if (peak_dB > -3) {
248                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
249                                 }
250                         }
251
252                         ch = ch.substr (0, ch.length() - 2);
253
254                         if (!ch.empty ()) {
255                                 hint (String::compose (
256                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
257                                               ch
258                                               )
259                                         );
260                         }
261                 } catch (OldFormatError& e) {
262                         /* The audio analysis is too old to load in; just skip this hint as if
263                            it had never been run.
264                         */
265                 }
266         }
267
268         emit (bind(boost::ref(Progress), _("Examining closed captions")));
269
270         shared_ptr<Player> player (new Player (film, film->playlist ()));
271         player->set_ignore_video ();
272         player->set_ignore_audio ();
273         player->Text.connect (bind(&Hints::text, this, _1, _2, _4));
274
275         struct timeval last_pulse;
276         gettimeofday (&last_pulse, 0);
277
278         while (!player->pass()) {
279
280                 struct timeval now;
281                 gettimeofday (&now, 0);
282                 if ((seconds(now) - seconds(last_pulse)) > 1) {
283                         {
284                                 boost::mutex::scoped_lock lm (_mutex);
285                                 if (_stop) {
286                                         break;
287                                 }
288                         }
289                         emit (bind (boost::ref(Pulse)));
290                         last_pulse = now;
291                 }
292         }
293
294         emit (bind(boost::ref(Finished)));
295 }
296
297 void
298 Hints::hint (string h)
299 {
300         emit(bind(boost::ref(Hint), h));
301 }
302
303 void
304 Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
305 {
306         if (type != TEXT_CLOSED_CAPTION) {
307                 return;
308         }
309
310         int lines = text.string.size();
311         BOOST_FOREACH (StringText i, text.string) {
312                 if (utf8_strlen(i.text()) > CLOSED_CAPTION_LENGTH) {
313                         ++lines;
314                         if (!_long_ccap) {
315                                 _long_ccap = true;
316                                 hint (String::compose(_("Some of your closed captions have lines longer than %1 characters, so they will probably be word-wrapped."), CLOSED_CAPTION_LENGTH));
317                         }
318                 }
319         }
320
321         if (!_too_many_ccap_lines && lines > CLOSED_CAPTION_LINES) {
322                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), CLOSED_CAPTION_LINES));
323                 _too_many_ccap_lines = true;
324         }
325
326         shared_ptr<const Film> film = _film.lock ();
327         DCPOMATIC_ASSERT (film);
328
329         /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
330         if (film->interop() && !_overlap_ccap && _last && _last->overlap(period)) {
331                 _overlap_ccap = true;
332                 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs.  Change your DCP standard to SMPTE."));
333         }
334
335         _last = period;
336 }