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