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