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