2 Copyright (C) 2016-2020 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
21 #include "dcp_content_type.h"
26 #include "video_content.h"
27 #include "text_content.h"
28 #include "audio_processor.h"
30 #include "font_data.h"
32 #include "audio_analysis.h"
33 #include "compose.hpp"
39 #include <dcp/raw_convert.h>
41 #include <dcp/reel_closed_caption_asset.h>
42 #include <dcp/reel_subtitle_asset.h>
43 #include <boost/foreach.hpp>
44 #include <boost/algorithm/string.hpp>
55 using std::shared_ptr;
57 using boost::optional;
59 using namespace dcpomatic;
60 #if BOOST_VERSION >= 106100
61 using namespace boost::placeholders;
65 /* When checking to see if things are too big, we'll say they are if they
66 * are more than the target size minus this "slack."
68 #define SIZE_SLACK 4096
71 Hints::Hints (weak_ptr<const Film> film)
72 : WeakConstFilm (film)
73 , _writer (new Writer(film, weak_ptr<Job>(), true))
75 , _overlap_ccap (false)
76 , _too_many_ccap_lines (false)
77 , _early_subtitle (false)
78 , _short_subtitle (false)
79 , _subtitles_too_close (false)
80 , _too_many_subtitle_lines (false)
81 , _long_subtitle (false)
90 _thread = boost::thread (bind(&Hints::thread, this));
95 boost::this_thread::disable_interruption dis;
106 Hints::check_few_audio_channels ()
108 if (film()->audio_channels() < 6) {
109 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."));
115 Hints::check_upmixers ()
117 AudioProcessor const * ap = film()->audio_processor();
118 if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
119 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 Hints::check_incorrect_container ()
127 int narrower_than_scope = 0;
129 BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
131 Ratio const * r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio());
132 if (r && r->id() == "239") {
134 } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") {
135 ++narrower_than_scope;
140 string const film_container = film()->container()->id();
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."));
146 if (!scope && narrower_than_scope && film_container == "239") {
147 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."));
153 Hints::check_unusual_container ()
155 string const film_container = film()->container()->id();
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"));
163 Hints::check_high_j2k_bandwidth ()
165 if (film()->j2k_bandwidth() >= 245000000) {
166 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."));
172 Hints::check_frame_rate ()
174 shared_ptr<const Film> f = film ();
175 switch (f->video_frame_rate()) {
181 /* You might want to go to 24 */
182 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);
185 base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
191 /* 30fps: we can't really offer any decent solutions */
192 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."));
197 /* You almost certainly want to go to half frame rate */
198 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."), f->video_frame_rate(), f->video_frame_rate() / 2));
205 Hints::check_speed_up ()
207 optional<double> lowest_speed_up;
208 optional<double> highest_speed_up;
209 BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
210 double spu = film()->active_frame_rate_change(i->position()).speed_up;
211 if (!lowest_speed_up || spu < *lowest_speed_up) {
212 lowest_speed_up = spu;
214 if (!highest_speed_up || spu > *highest_speed_up) {
215 highest_speed_up = spu;
219 double worst_speed_up = 1;
220 if (highest_speed_up) {
221 worst_speed_up = *highest_speed_up;
223 if (lowest_speed_up) {
224 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
227 if (worst_speed_up > 25.5/24.0) {
228 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."));
235 Hints::check_big_font_files ()
237 bool big_font_files = false;
238 if (film()->interop ()) {
239 BOOST_FOREACH (shared_ptr<Content> i, film()->content()) {
240 BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
241 BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
242 optional<boost::filesystem::path> const p = k->file ();
243 if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
244 big_font_files = true;
251 if (big_font_files) {
252 hint (_("You have specified a font file which is larger than 640kB. This is very likely to cause problems on playback."));
261 BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
262 if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
268 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));
274 Hints::check_3d_in_2d ()
277 BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
278 if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
283 if (three_d > 0 && !film()->three_d()) {
284 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.)"));
290 Hints::check_loudness ()
292 boost::filesystem::path path = film()->audio_analysis_path(film()->playlist());
293 if (boost::filesystem::exists (path)) {
295 shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
299 vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
300 vector<float> true_peak = an->true_peak ();
302 for (size_t i = 0; i < sample_peak.size(); ++i) {
303 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
304 float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
306 ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
310 ch = ch.substr (0, ch.length() - 2);
313 hint (String::compose (
314 _("Your audio level is very high (on %1). You should reduce the gain of your audio content."),
319 } catch (OldFormatError& e) {
320 /* The audio analysis is too old to load in; just skip this hint as if
321 it had never been run.
330 subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
332 return asset && asset->file() && boost::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK);
339 shared_ptr<const Film> film = _film.lock ();
344 ContentList content = film->content ();
346 check_big_font_files ();
347 check_few_audio_channels ();
349 check_incorrect_container ();
350 check_unusual_container ();
351 check_high_j2k_bandwidth ();
357 check_ffec_and_ffmc_in_smpte_feature ();
359 emit (bind(boost::ref(Progress), _("Examining closed captions")));
361 shared_ptr<Player> player (new Player(film));
362 player->set_ignore_video ();
363 player->set_ignore_audio ();
364 player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4));
366 struct timeval last_pulse;
367 gettimeofday (&last_pulse, 0);
370 while (!player->pass()) {
373 gettimeofday (&now, 0);
374 if ((seconds(now) - seconds(last_pulse)) > 1) {
378 emit (bind (boost::ref(Pulse)));
386 _writer->write (player->get_subtitle_fonts());
388 bool ccap_xml_too_big = false;
389 bool ccap_mxf_too_big = false;
390 bool subs_mxf_too_big = false;
392 boost::filesystem::path dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
393 boost::filesystem::remove_all (dcp_dir);
396 _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
399 emit (bind(boost::ref(Finished)));
403 dcp::DCP dcp (dcp_dir);
405 DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
406 BOOST_FOREACH (shared_ptr<dcp::Reel> reel, dcp.cpls().front()->reels()) {
407 BOOST_FOREACH (shared_ptr<dcp::ReelClosedCaptionAsset> ccap, reel->closed_captions()) {
408 if (ccap->asset() && ccap->asset()->xml_as_string().length() > static_cast<size_t>(MAX_CLOSED_CAPTION_XML_SIZE - SIZE_SLACK) && !ccap_xml_too_big) {
410 "At least one of your closed caption files' XML part is larger than " MAX_CLOSED_CAPTION_XML_SIZE_TEXT
411 ". You should divide the DCP into shorter reels."
413 ccap_xml_too_big = true;
415 if (subtitle_mxf_too_big(ccap->asset()) && !ccap_mxf_too_big) {
417 "At least one of your closed caption files is larger than " MAX_TEXT_MXF_SIZE_TEXT
418 " in total. You should divide the DCP into shorter reels."
420 ccap_mxf_too_big = true;
423 if (reel->main_subtitle() && subtitle_mxf_too_big(reel->main_subtitle()->asset()) && !subs_mxf_too_big) {
425 "At least one of your subtitle files is larger than " MAX_TEXT_MXF_SIZE_TEXT " in total. "
426 "You should divide the DCP into shorter reels."
428 subs_mxf_too_big = true;
431 boost::filesystem::remove_all (dcp_dir);
433 emit (bind(boost::ref(Finished)));
437 Hints::hint (string h)
439 emit(bind(boost::ref(Hint), h));
443 Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
445 _writer->write (text, type, track, period);
448 case TEXT_CLOSED_CAPTION:
449 closed_caption (text, period);
451 case TEXT_OPEN_SUBTITLE:
452 open_subtitle (text, period);
461 Hints::closed_caption (PlayerText text, DCPTimePeriod period)
463 int lines = text.string.size();
464 BOOST_FOREACH (StringText i, text.string) {
465 if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
471 "At least one of your closed caption lines has more than %1 characters. "
472 "It is advisable to make each line %1 characters at most in length.",
473 MAX_CLOSED_CAPTION_LENGTH,
474 MAX_CLOSED_CAPTION_LENGTH)
480 if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
481 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
482 _too_many_ccap_lines = true;
485 /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
486 if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
487 _overlap_ccap = true;
488 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs. Change your DCP standard to SMPTE."));
496 Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
498 if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
499 _early_subtitle = true;
500 hint (_("It is advisable to put your first subtitle at least 4 seconds after the start of the DCP to make sure it is seen."));
503 int const vfr = film()->video_frame_rate ();
505 if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
506 _short_subtitle = true;
507 hint (_("At least one of your subtitles lasts less than 15 frames. It is advisable to make each subtitle at least 15 frames long."));
510 if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
511 _subtitles_too_close = true;
512 hint (_("At least one of your subtitles starts less than 2 frames after the previous one. It is advisable to make the gap between subtitles at least 2 frames."));
515 if (text.string.size() > 3 && !_too_many_subtitle_lines) {
516 _too_many_subtitle_lines = true;
517 hint (_("At least one of your subtitles has more than 3 lines. It is advisable to use no more than 3 lines."));
520 size_t longest_line = 0;
521 BOOST_FOREACH (StringText const& i, text.string) {
522 longest_line = max (longest_line, i.text().length());
525 if (longest_line > 52 && !_long_subtitle) {
526 _long_subtitle = true;
527 hint (_("At least one of your subtitle lines has more than 52 characters. It is advisable to make each line 52 characters at most in length."));
530 _last_subtitle = period;
535 Hints::check_ffec_and_ffmc_in_smpte_feature ()
537 shared_ptr<const Film> f = film();
538 if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::FEATURE && (!f->marker(dcp::FFEC) || !f->marker(dcp::FFMC))) {
539 hint (_("SMPTE DCPs with the type FTR (feature) should have markers for the first frame of end credits (FFEC) and the first frame of moving credits (FFMC). You should add these markers using the 'Markers' button in the DCP tab."));