Tweak message.
[dcpomatic.git] / src / lib / hints.cc
1 /*
2     Copyright (C) 2016-2018 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
53 Hints::Hints (weak_ptr<const Film> film)
54         : _film (film)
55         , _thread (0)
56         , _long_ccap (false)
57         , _overlap_ccap (false)
58         , _too_many_ccap_lines (false)
59 {
60
61 }
62
63 void
64 Hints::stop_thread ()
65 {
66         if (_thread) {
67                 try {
68                         _thread->interrupt ();
69                         _thread->join ();
70                 } catch (...) {
71
72                 }
73
74                 delete _thread;
75                 _thread = 0;
76         }
77 }
78
79 Hints::~Hints ()
80 {
81         stop_thread ();
82 }
83
84 void
85 Hints::start ()
86 {
87         stop_thread ();
88         _long_ccap = false;
89         _overlap_ccap = false;
90         _too_many_ccap_lines = false;
91         _thread = new boost::thread (bind(&Hints::thread, this));
92 }
93
94 void
95 Hints::thread ()
96 {
97         shared_ptr<const Film> film = _film.lock ();
98         if (!film) {
99                 return;
100         }
101
102         ContentList content = film->content ();
103
104         bool big_font_files = false;
105         if (film->interop ()) {
106                 BOOST_FOREACH (shared_ptr<Content> i, content) {
107                         BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
108                                 BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
109                                         for (int l = 0; l < FontFiles::VARIANTS; ++l) {
110                                                 optional<boost::filesystem::path> const p = k->file (static_cast<FontFiles::Variant>(l));
111                                                 if (p && boost::filesystem::file_size (p.get()) >= (640 * 1024)) {
112                                                         big_font_files = true;
113                                                 }
114                                         }
115                                 }
116                         }
117                 }
118         }
119
120         if (big_font_files) {
121                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
122         }
123
124         if (film->audio_channels() < 6) {
125                 hint (_("Your DCP has fewer than 6 audio channels.  This may cause problems on some projectors."));
126         }
127
128         AudioProcessor const * ap = film->audio_processor();
129         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
130                 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."));
131         }
132
133         int flat_or_narrower = 0;
134         int scope = 0;
135         BOOST_FOREACH (shared_ptr<const Content> i, content) {
136                 if (i->video) {
137                         Ratio const * r = i->video->scale().ratio ();
138                         if (r && r->id() == "239") {
139                                 ++scope;
140                         } else if (r && r->id() != "239" && r->id() != "190") {
141                                 ++flat_or_narrower;
142                         }
143                 }
144         }
145
146         string const film_container = film->container()->id();
147
148         if (scope && !flat_or_narrower && film_container == "185") {
149                 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."));
150         }
151
152         if (!scope && flat_or_narrower && film_container == "239") {
153                 hint (_("All of your content is at 1.85:1 or narrower but your DCP's container is Scope (2.39:1).  This will pillar-box your content inside a Flat (1.85:1) frame.  You may prefer to set your DCP's container to Flat (1.85:1) in the \"DCP\" tab."));
154         }
155
156         if (film_container != "185" && film_container != "239" && film_container != "190") {
157                 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"));
158         }
159
160         if (film->j2k_bandwidth() >= 245000000) {
161                 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."));
162         }
163
164         if (film->interop() && film->video_frame_rate() != 24 && film->video_frame_rate() != 48) {
165                 string base = _("You are set up for an Interop DCP at a frame rate which is not officially supported.  You are advised either to change the frame rate of your DCP or to make a SMPTE DCP instead.");
166                 base += "  ";
167                 pair<double, double> range24 = film->speed_up_range (24);
168                 pair<double, double> range48 = film->speed_up_range (48);
169                 pair<double, double> range (max (range24.first, range48.first), min (range24.second, range48.second));
170                 string h;
171                 if (range.second > (29.0/24)) {
172                         h = base;
173                         h += _("However, setting your DCP frame rate to 24 or 48 will cause a significant speed-up of your content, and SMPTE DCPs are not supported by all projectors.");
174                 } else if (range.first < (24.0/29)) {
175                         h = base;
176                         h += _("However, setting your DCP frame rate to 24 or 48 will cause a significant slowdown of your content, and SMPTE DCPs are not supported by all projectors.");
177                 } else {
178                         h = _("You are set up for an Interop DCP at a frame rate which is not officially supported.  You are advised either to change the frame rate of your DCP or to make a SMPTE DCP instead (although SMPTE DCPs are not supported by all projectors).");
179                 }
180
181                 hint (h);
182         }
183
184         optional<double> lowest_speed_up;
185         optional<double> highest_speed_up;
186         BOOST_FOREACH (shared_ptr<const Content> i, content) {
187                 double spu = film->active_frame_rate_change(i->position()).speed_up;
188                 if (!lowest_speed_up || spu < *lowest_speed_up) {
189                         lowest_speed_up = spu;
190                 }
191                 if (!highest_speed_up || spu > *highest_speed_up) {
192                         highest_speed_up = spu;
193                 }
194         }
195
196         double worst_speed_up = 1;
197         if (highest_speed_up) {
198                 worst_speed_up = *highest_speed_up;
199         }
200         if (lowest_speed_up) {
201                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
202         }
203
204         if (worst_speed_up > 25.5/24.0) {
205                 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."));
206         }
207
208         int vob = 0;
209         BOOST_FOREACH (shared_ptr<const Content> i, content) {
210                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
211                         ++vob;
212                 }
213         }
214
215         if (vob > 1) {
216                 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));
217         }
218
219         int three_d = 0;
220         BOOST_FOREACH (shared_ptr<const Content> i, content) {
221                 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
222                         ++three_d;
223                 }
224         }
225
226         if (three_d > 0 && !film->three_d()) {
227                 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.)"));
228         }
229
230         boost::filesystem::path path = film->audio_analysis_path (film->playlist ());
231         if (boost::filesystem::exists (path)) {
232                 try {
233                         shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
234
235                         string ch;
236
237                         vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
238                         vector<float> true_peak = an->true_peak ();
239
240                         for (size_t i = 0; i < sample_peak.size(); ++i) {
241                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
242                                 float const peak_dB = 20 * log10 (peak) + an->gain_correction (film->playlist ());
243                                 if (peak_dB > -3) {
244                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
245                                 }
246                         }
247
248                         ch = ch.substr (0, ch.length() - 2);
249
250                         if (!ch.empty ()) {
251                                 hint (String::compose (
252                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
253                                               ch
254                                               )
255                                         );
256                         }
257                 } catch (OldFormatError& e) {
258                         /* The audio analysis is too old to load in; just skip this hint as if
259                            it had never been run.
260                         */
261                 }
262         }
263
264         emit (bind(boost::ref(Progress), _("Examining closed captions")));
265
266         shared_ptr<Player> player (new Player (film, film->playlist ()));
267         player->set_ignore_video ();
268         player->set_ignore_audio ();
269         player->Text.connect (bind(&Hints::text, this, _1, _2, _3));
270         while (!player->pass ()) {
271                 bind (boost::ref(Pulse));
272         }
273
274         emit (bind(boost::ref(Finished)));
275 }
276
277 void
278 Hints::hint (string h)
279 {
280         emit(bind(boost::ref(Hint), h));
281 }
282
283 void
284 Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
285 {
286         if (type != TEXT_CLOSED_CAPTION) {
287                 return;
288         }
289
290         int lines = text.text.size();
291         BOOST_FOREACH (StringText i, text.text) {
292                 if (i.text().length() > CLOSED_CAPTION_LENGTH) {
293                         ++lines;
294                         if (!_long_ccap) {
295                                 _long_ccap = true;
296                                 hint (String::compose(_("Some of your closed captions have lines longer than %1 characters, so they will probably be word-wrapped."), CLOSED_CAPTION_LENGTH));
297                         }
298                 }
299         }
300
301         if (!_too_many_ccap_lines && lines > CLOSED_CAPTION_LINES) {
302                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), CLOSED_CAPTION_LINES));
303                 _too_many_ccap_lines = true;
304         }
305
306         if (!_overlap_ccap && _last && _last->overlap(period)) {
307                 _overlap_ccap = true;
308                 hint (_("You have overlapping closed captions, which are not allowed."));
309         }
310
311         _last = period;
312 }