2 Copyright (C) 2016-2022 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/>.
22 #include "audio_analysis.h"
23 #include "audio_content.h"
24 #include "audio_processor.h"
25 #include "compose.hpp"
27 #include "constants.h"
30 #include "dcp_content_type.h"
34 #include "maths_util.h"
37 #include "text_content.h"
39 #include "video_content.h"
42 #include <dcp/filesystem.h>
43 #include <dcp/raw_convert.h>
45 #include <dcp/reel_closed_caption_asset.h>
46 #include <dcp/reel_subtitle_asset.h>
47 #include <boost/algorithm/string.hpp>
54 using std::make_shared;
56 using std::shared_ptr;
59 using boost::optional;
61 using namespace dcpomatic;
62 #if BOOST_VERSION >= 106100
63 using namespace boost::placeholders;
67 /* When checking to see if things are too big, we'll say they are if they
68 * are more than the target size minus this "slack."
70 #define SIZE_SLACK 4096
73 /* When writing hints:
74 * - put quotation marks around the name of a GUI tab that you are referring to (e.g. "DCP" or "DCP→Video" tab)
78 Hints::Hints (weak_ptr<const Film> weak_film)
79 : WeakConstFilm (weak_film)
80 , _writer (new Writer(weak_film, weak_ptr<Job>(), true))
81 , _analyser (film(), film()->playlist(), true, [](float) {})
91 _thread = boost::thread (bind(&Hints::thread, this));
97 boost::this_thread::disable_interruption dis;
101 _thread.interrupt ();
108 Hints::check_few_audio_channels ()
110 if (film()->audio_channels() < 6) {
112 variant::insert_dcpomatic(
113 _("Your DCP has fewer than 6 audio channels. This may cause problems on some projectors. "
114 "You may want to set the DCP to have 6 channels. It does not matter if your content has "
115 "fewer channels, as %1 will fill the extras with silence.")
123 Hints::check_upmixers ()
125 auto ap = film()->audio_processor();
126 if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) {
127 hint(variant::insert_dcpomatic(
128 _("You are using %1's stereo-to-5.1 upmixer. This is experimental and "
129 "may result in poor-quality audio. If you continue, you should listen to the "
130 "resulting DCP in a cinema to make sure that it sounds good.")
138 Hints::check_incorrect_container ()
140 int narrower_than_scope = 0;
142 for (auto i: film()->content()) {
143 if (i->video && i->video->size()) {
144 auto const r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size())->ratio());
145 if (r && r->id() == "239") {
147 } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") {
148 ++narrower_than_scope;
153 string const film_container = film()->container()->id();
155 if (scope && !narrower_than_scope && film_container == "185") {
156 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."));
159 if (!scope && narrower_than_scope && film_container == "239") {
160 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."));
166 Hints::check_unusual_container ()
168 auto const film_container = film()->container()->id();
169 if (film_container != "185" && film_container != "239") {
170 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."));
176 Hints::check_high_video_bit_rate()
178 if (film()->video_bit_rate() >= 245000000) {
179 hint (_("A few projectors have problems playing back very high bit-rate DCPs. It is a good idea to drop the video bit rate down to about 200Mbit/s; this is unlikely to have any visible effect on the image."));
185 Hints::check_frame_rate ()
188 switch (f->video_frame_rate()) {
194 /* You might want to go to 24 */
195 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);
198 base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
204 /* 30fps: we can't really offer any decent solutions */
205 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."));
210 /* You almost certainly want to go to half frame rate */
211 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. It is advisable to change the DCP frame rate to %2 fps."), f->video_frame_rate(), f->video_frame_rate() / 2));
218 Hints::check_4k_3d ()
221 if (f->resolution() == Resolution::FOUR_K && f->three_d()) {
222 hint (_("4K 3D is only supported by a very limited number of projectors. Unless you know that you will play this DCP back on a capable projector, it is advisable to set the DCP to be 2K in the \"DCP→Video\" tab."));
228 Hints::check_speed_up ()
230 optional<double> lowest_speed_up;
231 optional<double> highest_speed_up;
232 for (auto i: film()->content()) {
233 double spu = film()->active_frame_rate_change(i->position()).speed_up;
234 if (!lowest_speed_up || spu < *lowest_speed_up) {
235 lowest_speed_up = spu;
237 if (!highest_speed_up || spu > *highest_speed_up) {
238 highest_speed_up = spu;
242 double worst_speed_up = 1;
243 if (highest_speed_up) {
244 worst_speed_up = *highest_speed_up;
246 if (lowest_speed_up) {
247 worst_speed_up = max (worst_speed_up, 1 / *lowest_speed_up);
250 if (worst_speed_up > 25.5/24.0) {
251 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. It is advisable to set your DCP frame rate to one closer to your content, provided that your target projection systems support your chosen DCP rate."));
258 Hints::check_interop ()
260 if (film()->interop()) {
261 hint (_("In general it is now advisable to make SMPTE DCPs unless you have a particular reason to use Interop. It is advisable to set your DCP to use the SMPTE standard in the \"DCP\" tab."));
267 Hints::check_big_font_files ()
269 bool big_font_files = false;
270 if (film()->interop ()) {
271 for (auto i: film()->content()) {
272 for (auto j: i->text) {
273 for (auto k: j->fonts()) {
274 auto const p = k->file ();
275 if (p && dcp::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
276 big_font_files = true;
283 if (big_font_files) {
284 hint (_("You have specified a font file which is larger than 640kB. This is very likely to cause problems on playback."));
293 for (auto i: film()->content()) {
294 if (boost::algorithm::starts_with(i->path(0).filename().string(), "VTS_")) {
300 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));
306 Hints::check_3d_in_2d ()
309 for (auto i: film()->content()) {
310 if (i->video && i->video->frame_type() != VideoFrameType::TWO_D) {
315 if (three_d > 0 && !film()->three_d()) {
316 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.)"));
321 /** @return true if the loudness could be checked, false if it could not because no analysis was available */
323 Hints::check_loudness ()
325 auto path = film()->audio_analysis_path(film()->playlist());
326 if (!dcp::filesystem::exists(path)) {
331 auto an = make_shared<AudioAnalysis>(path);
335 auto sample_peak = an->sample_peak ();
336 auto true_peak = an->true_peak ();
338 for (size_t i = 0; i < sample_peak.size(); ++i) {
339 float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
340 float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist());
342 ch += dcp::raw_convert<string>(short_audio_channel_name(i)) + ", ";
346 ch = ch.substr (0, ch.length() - 2);
349 hint(String::compose(
350 _("Your audio level is very high (on %1). You should reduce the gain of your audio content."),
355 } catch (OldFormatError& e) {
356 /* The audio analysis is too old to load in */
366 subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
368 return asset && asset->file() && dcp::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK);
373 Hints::check_out_of_range_markers ()
375 auto const length = film()->length();
376 for (auto const& i: film()->markers()) {
377 if (i.second >= length) {
378 hint (_("At least one marker comes after the end of the project and will be ignored."));
385 Hints::scan_content(shared_ptr<const Film> film)
387 auto const check_loudness_done = check_loudness();
389 auto content = film->playlist()->content();
390 auto iter = std::find_if(content.begin(), content.end(), [](shared_ptr<const Content> content) {
391 auto text_iter = std::find_if(content->text.begin(), content->text.end(), [](shared_ptr<const TextContent> text) {
394 return text_iter != content->text.end();
397 auto const have_text = iter != content.end();
399 if (check_loudness_done && !have_text) {
400 /* We don't need to check loudness, and we don't have any active text to check,
401 * so a scan of the content is pointless.
406 if (check_loudness_done && have_text) {
407 emit (bind(boost::ref(Progress), _("Examining subtitles and closed captions")));
408 } else if (!check_loudness_done && !have_text) {
409 emit (bind(boost::ref(Progress), _("Examining audio")));
411 emit (bind(boost::ref(Progress), _("Examining audio, subtitles and closed captions")));
414 auto player = make_shared<Player>(film, Image::Alignment::COMPACT);
415 player->set_ignore_video();
416 if (check_loudness_done || _disable_audio_analysis) {
417 /* We don't need to analyse audio because we already loaded a suitable analysis */
418 player->set_ignore_audio();
420 /* Send auto to the analyser to check loudness */
421 player->Audio.connect(bind(&Hints::audio, this, _1, _2));
423 player->Text.connect(bind(&Hints::text, this, _1, _2, _3, _4));
425 struct timeval last_pulse;
426 gettimeofday(&last_pulse, 0);
428 _writer->write(player->get_subtitle_fonts());
430 while (!player->pass()) {
433 gettimeofday(&now, 0);
434 if ((seconds(now) - seconds(last_pulse)) > 1) {
438 emit(bind(boost::ref(Pulse)));
443 if (!check_loudness_done) {
445 _analyser.get().write(film->audio_analysis_path(film->playlist()));
455 start_of_thread ("Hints");
457 auto film = _film.lock ();
462 auto content = film->content ();
464 check_certificates ();
466 check_big_font_files ();
467 check_few_audio_channels ();
469 check_incorrect_container ();
470 check_unusual_container ();
471 check_high_video_bit_rate();
477 check_ffec_and_ffmc_in_smpte_feature ();
478 check_out_of_range_markers ();
479 check_subtitle_languages();
480 check_audio_language ();
481 check_8_or_16_audio_channels();
485 if (_long_subtitle && !_very_long_subtitle) {
486 hint (_("At least one of your subtitle lines has more than 52 characters. It is recommended to make each line 52 characters at most in length."));
487 } else if (_very_long_subtitle) {
488 hint (_("At least one of your subtitle lines has more than 79 characters. You should make each line 79 characters at most in length."));
491 bool ccap_xml_too_big = false;
492 bool ccap_mxf_too_big = false;
493 bool subs_mxf_too_big = false;
495 auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
496 dcp::filesystem::remove_all(dcp_dir);
498 _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
500 dcp::DCP dcp (dcp_dir);
502 DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
503 for (auto reel: dcp.cpls()[0]->reels()) {
504 for (auto ccap: reel->closed_captions()) {
505 if (ccap->asset() && ccap->asset()->xml_as_string().length() > static_cast<size_t>(MAX_CLOSED_CAPTION_XML_SIZE - SIZE_SLACK) && !ccap_xml_too_big) {
507 "At least one of your closed caption files' XML part is larger than " MAX_CLOSED_CAPTION_XML_SIZE_TEXT
508 ". You should divide the DCP into shorter reels."
510 ccap_xml_too_big = true;
512 if (subtitle_mxf_too_big(ccap->asset()) && !ccap_mxf_too_big) {
514 "At least one of your closed caption files is larger than " MAX_TEXT_MXF_SIZE_TEXT
515 " in total. You should divide the DCP into shorter reels."
517 ccap_mxf_too_big = true;
520 if (reel->main_subtitle() && subtitle_mxf_too_big(reel->main_subtitle()->asset()) && !subs_mxf_too_big) {
522 "At least one of your subtitle files is larger than " MAX_TEXT_MXF_SIZE_TEXT " in total. "
523 "You should divide the DCP into shorter reels."
525 subs_mxf_too_big = true;
528 dcp::filesystem::remove_all(dcp_dir);
530 emit (bind(boost::ref(Finished)));
532 catch (boost::thread_interrupted)
534 /* The Hints object is being destroyed before it has finished, so just give up */
543 Hints::hint (string h)
545 emit(bind(boost::ref(Hint), h));
550 Hints::audio (shared_ptr<AudioBuffers> audio, DCPTime time)
552 _analyser.analyse (audio, time);
557 Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
559 _writer->write (text, type, track, period);
562 case TextType::CLOSED_CAPTION:
563 closed_caption (text, period);
565 case TextType::OPEN_SUBTITLE:
566 open_subtitle (text, period);
575 Hints::closed_caption (PlayerText text, DCPTimePeriod period)
577 int lines = text.string.size();
578 for (auto i: text.string) {
579 if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
585 "At least one of your closed caption lines has more than %1 characters. "
586 "It is advisable to make each line %1 characters at most in length.",
587 MAX_CLOSED_CAPTION_LENGTH,
588 MAX_CLOSED_CAPTION_LENGTH)
594 if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
595 hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
596 _too_many_ccap_lines = true;
599 /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
600 if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
601 _overlap_ccap = true;
602 hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs. Change your DCP standard to SMPTE."));
610 Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
612 if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
613 _early_subtitle = true;
614 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."));
617 int const vfr = film()->video_frame_rate ();
619 if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
620 _short_subtitle = true;
621 hint (_("At least one of your subtitles lasts less than 15 frames. It is advisable to make each subtitle at least 15 frames long."));
624 if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
625 _subtitles_too_close = true;
626 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."));
635 bool operator<(VPos const& other) const {
636 if (static_cast<int>(align) != static_cast<int>(other.align)) {
637 return static_cast<int>(align) < static_cast<int>(other.align);
639 return position < other.position;
643 /* This is rather an approximate way to count distinct lines, but I guess it will do;
644 * to make it better we need to take into account font metrics, and the SMPTE alignment
645 * debacle, and so on.
647 std::set<VPos> lines;
648 for (auto const& line: text.string) {
649 lines.insert({ line.v_align(), line.v_position() });
652 if (lines.size() > 3 && !_too_many_subtitle_lines) {
653 _too_many_subtitle_lines = true;
654 hint (_("At least one of your subtitles has more than 3 lines. It is advisable to use no more than 3 lines."));
657 size_t longest_line = 0;
658 for (auto const& i: text.string) {
659 longest_line = max (longest_line, i.text().length());
662 if (longest_line > 52) {
663 _long_subtitle = true;
666 if (longest_line > 79) {
667 _very_long_subtitle = true;
670 _last_subtitle = period;
675 Hints::check_ffec_and_ffmc_in_smpte_feature ()
678 if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::ContentKind::FEATURE && (!f->marker(dcp::Marker::FFEC) || !f->marker(dcp::Marker::FFMC))) {
679 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."));
692 Hints::check_subtitle_languages()
694 for (auto i: film()->content()) {
695 for (auto j: i->text) {
696 if (j->use() && j->type() == TextType::OPEN_SUBTITLE && !j->language()) {
697 hint (_("At least one piece of subtitle content has no specified language. "
698 "It is advisable to set the language for each piece of subtitle content "
699 "in the \"Content→Timed text\" or \"Content→Open subtitles\" tab."));
708 Hints::check_audio_language ()
710 auto content = film()->content();
712 std::find_if(content.begin(), content.end(), [](shared_ptr<const Content> c) {
713 return c->audio && !c->audio->mapping().mapped_output_channels().empty();
716 if (mapped_audio != content.end() && !film()->audio_language()) {
717 hint (_("Some of your content has audio but you have not set the audio language. It is advisable to set the audio language "
718 "in the \"DCP\" tab unless your audio has no spoken parts."));
724 Hints::check_certificates ()
726 auto bad = Config::instance()->check_certificates();
732 case Config::BAD_SIGNER_UTF8_STRINGS:
733 hint(variant::insert_dcpomatic(
734 _("The certificate chain that %1 uses for signing DCPs and KDMs contains a small error "
735 "which will prevent DCPs from being validated correctly on some systems. It is advisable to "
736 "re-create the signing certificate chain by clicking the \"Re-make certificates and key...\" "
737 "button in the Keys page of Preferences.")
740 case Config::BAD_SIGNER_VALIDITY_TOO_LONG:
741 hint(variant::insert_dcpomatic(
742 _("The certificate chain that %1 uses for signing DCPs and KDMs has a validity period "
743 "that is too long. This will cause problems playing back DCPs on some systems. "
744 "It is advisable to re-create the signing certificate chain by clicking the "
745 "\"Re-make certificates and key...\" button in the Keys page of Preferences.")
749 /* Some bad situations can't happen here as DCP-o-matic would have refused to start until they are fixed */
756 Hints::check_8_or_16_audio_channels()
758 auto const channels = film()->audio_channels();
759 if (channels != 8 && channels != 16) {
760 hint(String::compose(_("Your DCP has %1 audio channels, rather than 8 or 16. This may cause some distributors to raise QC errors when they check your DCP. To avoid this, set the DCP audio channels to 8 or 16."), channels));