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