Read UTF8 string lengths correctly when checking closed captions (part of #1446).
[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         , _stop (false)
60 {
61
62 }
63
64 void
65 Hints::stop_thread ()
66 {
67         if (_thread) {
68                 try {
69                         {
70                                 boost::mutex::scoped_lock lm (_mutex);
71                                 _stop = true;
72                         }
73                         _thread->interrupt ();
74                         _thread->join ();
75                 } catch (...) {
76
77                 }
78
79                 delete _thread;
80                 _thread = 0;
81         }
82 }
83
84 Hints::~Hints ()
85 {
86         stop_thread ();
87 }
88
89 void
90 Hints::start ()
91 {
92         stop_thread ();
93         _long_ccap = false;
94         _overlap_ccap = false;
95         _too_many_ccap_lines = false;
96         _stop = false;
97         _thread = new boost::thread (bind(&Hints::thread, this));
98 }
99
100 void
101 Hints::thread ()
102 {
103         shared_ptr<const Film> film = _film.lock ();
104         if (!film) {
105                 return;
106         }
107
108         ContentList content = film->content ();
109
110         bool big_font_files = false;
111         if (film->interop ()) {
112                 BOOST_FOREACH (shared_ptr<Content> i, content) {
113                         BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
114                                 BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
115                                         for (int l = 0; l < FontFiles::VARIANTS; ++l) {
116                                                 optional<boost::filesystem::path> const p = k->file (static_cast<FontFiles::Variant>(l));
117                                                 if (p && boost::filesystem::file_size (p.get()) >= (640 * 1024)) {
118                                                         big_font_files = true;
119                                                 }
120                                         }
121                                 }
122                         }
123                 }
124         }
125
126         if (big_font_files) {
127                 hint (_("You have specified a font file which is larger than 640kB.  This is very likely to cause problems on playback."));
128         }
129
130         if (film->audio_channels() < 6) {
131                 hint (_("Your DCP has fewer than 6 audio channels.  This may cause problems on some projectors."));
132         }
133
134         AudioProcessor const * ap = film->audio_processor();
135         if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
136                 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."));
137         }
138
139         int flat_or_narrower = 0;
140         int scope = 0;
141         BOOST_FOREACH (shared_ptr<const Content> i, content) {
142                 if (i->video) {
143                         Ratio const * r = i->video->scale().ratio ();
144                         if (r && r->id() == "239") {
145                                 ++scope;
146                         } else if (r && r->id() != "239" && r->id() != "190") {
147                                 ++flat_or_narrower;
148                         }
149                 }
150         }
151
152         string const film_container = film->container()->id();
153
154         if (scope && !flat_or_narrower && film_container == "185") {
155                 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."));
156         }
157
158         if (!scope && flat_or_narrower && film_container == "239") {
159                 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."));
160         }
161
162         if (film_container != "185" && film_container != "239" && film_container != "190") {
163                 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"));
164         }
165
166         if (film->j2k_bandwidth() >= 245000000) {
167                 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."));
168         }
169
170         if (film->interop() && film->video_frame_rate() != 24 && film->video_frame_rate() != 48) {
171                 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.");
172                 base += "  ";
173                 pair<double, double> range24 = film->speed_up_range (24);
174                 pair<double, double> range48 = film->speed_up_range (48);
175                 pair<double, double> range (max (range24.first, range48.first), min (range24.second, range48.second));
176                 string h;
177                 if (range.second > (29.0/24)) {
178                         h = base;
179                         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.");
180                 } else if (range.first < (24.0/29)) {
181                         h = base;
182                         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.");
183                 } else {
184                         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).");
185                 }
186
187                 hint (h);
188         }
189
190         optional<double> lowest_speed_up;
191         optional<double> highest_speed_up;
192         BOOST_FOREACH (shared_ptr<const Content> i, content) {
193                 double spu = film->active_frame_rate_change(i->position()).speed_up;
194                 if (!lowest_speed_up || spu < *lowest_speed_up) {
195                         lowest_speed_up = spu;
196                 }
197                 if (!highest_speed_up || spu > *highest_speed_up) {
198                         highest_speed_up = spu;
199                 }
200         }
201
202         double worst_speed_up = 1;
203         if (highest_speed_up) {
204                 worst_speed_up = *highest_speed_up;
205         }
206         if (lowest_speed_up) {
207                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
208         }
209
210         if (worst_speed_up > 25.5/24.0) {
211                 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."));
212         }
213
214         int vob = 0;
215         BOOST_FOREACH (shared_ptr<const Content> i, content) {
216                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
217                         ++vob;
218                 }
219         }
220
221         if (vob > 1) {
222                 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));
223         }
224
225         int three_d = 0;
226         BOOST_FOREACH (shared_ptr<const Content> i, content) {
227                 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
228                         ++three_d;
229                 }
230         }
231
232         if (three_d > 0 && !film->three_d()) {
233                 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.)"));
234         }
235
236         boost::filesystem::path path = film->audio_analysis_path (film->playlist ());
237         if (boost::filesystem::exists (path)) {
238                 try {
239                         shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
240
241                         string ch;
242
243                         vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
244                         vector<float> true_peak = an->true_peak ();
245
246                         for (size_t i = 0; i < sample_peak.size(); ++i) {
247                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
248                                 float const peak_dB = 20 * log10 (peak) + an->gain_correction (film->playlist ());
249                                 if (peak_dB > -3) {
250                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
251                                 }
252                         }
253
254                         ch = ch.substr (0, ch.length() - 2);
255
256                         if (!ch.empty ()) {
257                                 hint (String::compose (
258                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
259                                               ch
260                                               )
261                                         );
262                         }
263                 } catch (OldFormatError& e) {
264                         /* The audio analysis is too old to load in; just skip this hint as if
265                            it had never been run.
266                         */
267                 }
268         }
269
270         emit (bind(boost::ref(Progress), _("Examining closed captions")));
271
272         shared_ptr<Player> player (new Player (film, film->playlist ()));
273         player->set_ignore_video ();
274         player->set_ignore_audio ();
275         player->Text.connect (bind(&Hints::text, this, _1, _2, _4));
276
277         struct timeval last_pulse;
278         gettimeofday (&last_pulse, 0);
279
280         while (!player->pass()) {
281
282                 struct timeval now;
283                 gettimeofday (&now, 0);
284                 if ((seconds(now) - seconds(last_pulse)) > 1) {
285                         {
286                                 boost::mutex::scoped_lock lm (_mutex);
287                                 if (_stop) {
288                                         break;
289                                 }
290                         }
291                         emit (bind (boost::ref(Pulse)));
292                         last_pulse = now;
293                 }
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         if (!_overlap_ccap && _last && _last->overlap(period)) {
329                 _overlap_ccap = true;
330                 hint (_("You have overlapping closed captions, which are not allowed."));
331         }
332
333         _last = period;
334 }