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