More player debugging for butler video-full states.
[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 narrower_than_scope = 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                                 ++narrower_than_scope;
136                         }
137                 }
138         }
139
140         string const film_container = film->container()->id();
141
142         if (scope && !narrower_than_scope && 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 && narrower_than_scope && film_container == "239") {
147                 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."));
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         switch (film->video_frame_rate()) {
159         case 24:
160                 /* Fine */
161                 break;
162         case 25:
163         {
164                 /* You might want to go to 24 */
165                 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);
166                 if (film->interop()) {
167                         base += "  ";
168                         base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
169                 }
170                 hint (base);
171                 break;
172         }
173         case 30:
174                 /* 30fps: we can't really offer any decent solutions */
175                 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."));
176                 break;
177         case 48:
178         case 50:
179         case 60:
180                 /* You almost certainly want to go to half frame rate */
181                 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));
182                 break;
183         }
184
185         optional<double> lowest_speed_up;
186         optional<double> highest_speed_up;
187         BOOST_FOREACH (shared_ptr<const Content> i, content) {
188                 double spu = film->active_frame_rate_change(i->position()).speed_up;
189                 if (!lowest_speed_up || spu < *lowest_speed_up) {
190                         lowest_speed_up = spu;
191                 }
192                 if (!highest_speed_up || spu > *highest_speed_up) {
193                         highest_speed_up = spu;
194                 }
195         }
196
197         double worst_speed_up = 1;
198         if (highest_speed_up) {
199                 worst_speed_up = *highest_speed_up;
200         }
201         if (lowest_speed_up) {
202                 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
203         }
204
205         if (worst_speed_up > 25.5/24.0) {
206                 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."));
207         }
208
209         int vob = 0;
210         BOOST_FOREACH (shared_ptr<const Content> i, content) {
211                 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
212                         ++vob;
213                 }
214         }
215
216         if (vob > 1) {
217                 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));
218         }
219
220         int three_d = 0;
221         BOOST_FOREACH (shared_ptr<const Content> i, content) {
222                 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
223                         ++three_d;
224                 }
225         }
226
227         if (three_d > 0 && !film->three_d()) {
228                 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.)"));
229         }
230
231         boost::filesystem::path path = film->audio_analysis_path (film->playlist ());
232         if (boost::filesystem::exists (path)) {
233                 try {
234                         shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
235
236                         string ch;
237
238                         vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
239                         vector<float> true_peak = an->true_peak ();
240
241                         for (size_t i = 0; i < sample_peak.size(); ++i) {
242                                 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
243                                 float const peak_dB = 20 * log10 (peak) + an->gain_correction (film->playlist ());
244                                 if (peak_dB > -3) {
245                                         ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
246                                 }
247                         }
248
249                         ch = ch.substr (0, ch.length() - 2);
250
251                         if (!ch.empty ()) {
252                                 hint (String::compose (
253                                               _("Your audio level is very high (on %1).  You should reduce the gain of your audio content."),
254                                               ch
255                                               )
256                                         );
257                         }
258                 } catch (OldFormatError& e) {
259                         /* The audio analysis is too old to load in; just skip this hint as if
260                            it had never been run.
261                         */
262                 }
263         }
264
265         emit (bind(boost::ref(Progress), _("Examining closed captions")));
266
267         shared_ptr<Player> player (new Player (film, film->playlist ()));
268         player->set_ignore_video ();
269         player->set_ignore_audio ();
270         player->Text.connect (bind(&Hints::text, this, _1, _2, _4));
271
272         struct timeval last_pulse;
273         gettimeofday (&last_pulse, 0);
274
275         while (!player->pass()) {
276
277                 struct timeval now;
278                 gettimeofday (&now, 0);
279                 if ((seconds(now) - seconds(last_pulse)) > 1) {
280                         {
281                                 boost::mutex::scoped_lock lm (_mutex);
282                                 if (_stop) {
283                                         break;
284                                 }
285                         }
286                         emit (bind (boost::ref(Pulse)));
287                         last_pulse = now;
288                 }
289         }
290
291         emit (bind(boost::ref(Finished)));
292 }
293
294 void
295 Hints::hint (string h)
296 {
297         emit(bind(boost::ref(Hint), h));
298 }
299
300 void
301 Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
302 {
303         if (type != TEXT_CLOSED_CAPTION) {
304                 return;
305         }
306
307         int lines = text.string.size();
308         BOOST_FOREACH (StringText i, text.string) {
309                 if (utf8_strlen(i.text()) > CLOSED_CAPTION_LENGTH) {
310                         ++lines;
311                         if (!_long_ccap) {
312                                 _long_ccap = true;
313                                 hint (String::compose(_("Some of your closed captions have lines longer than %1 characters, so they will probably be word-wrapped."), CLOSED_CAPTION_LENGTH));
314                         }
315                 }
316         }
317
318         if (!_too_many_ccap_lines && lines > CLOSED_CAPTION_LINES) {
319                 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), CLOSED_CAPTION_LINES));
320                 _too_many_ccap_lines = true;
321         }
322
323         if (!_overlap_ccap && _last && _last->overlap(period)) {
324                 _overlap_ccap = true;
325                 hint (_("You have overlapping closed captions, which are not allowed."));
326         }
327
328         _last = period;
329 }