X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fhints.cc;h=4a5c984380a95af51c5e3fae1eeb4b2a548d06fa;hb=5a820bb8fae34591be5ac6d19a73461b9dab532a;hp=5c9d3d8a41964fc96a33384dce8a2561f7129ffd;hpb=dfb68bf9190ec0be31f01b61c17aebc8b6e30ad8;p=dcpomatic.git diff --git a/src/lib/hints.cc b/src/lib/hints.cc index 5c9d3d8a4..4a5c98438 100644 --- a/src/lib/hints.cc +++ b/src/lib/hints.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2016-2020 Carl Hetherington + Copyright (C) 2016-2022 Carl Hetherington This file is part of DCP-o-matic. @@ -18,36 +18,42 @@ */ -#include "dcp_content_type.h" -#include "hints.h" -#include "types.h" -#include "film.h" -#include "content.h" -#include "video_content.h" -#include "text_content.h" -#include "audio_processor.h" -#include "font.h" -#include "ratio.h" + #include "audio_analysis.h" +#include "audio_content.h" +#include "audio_processor.h" #include "compose.hpp" -#include "util.h" +#include "config.h" +#include "content.h" #include "cross.h" +#include "dcp_content_type.h" +#include "film.h" +#include "font.h" +#include "hints.h" +#include "maths_util.h" #include "player.h" +#include "ratio.h" +#include "text_content.h" +#include "types.h" +#include "video_content.h" +#include "writer.h" +#include #include -#include +#include +#include +#include #include #include #include "i18n.h" -using std::vector; -using std::string; -using std::pair; -using std::min; -using std::max; + using std::cout; -using boost::shared_ptr; -using boost::weak_ptr; +using std::make_shared; +using std::max; +using std::shared_ptr; +using std::string; +using std::weak_ptr; using boost::optional; using boost::bind; using namespace dcpomatic; @@ -55,27 +61,35 @@ using namespace dcpomatic; using namespace boost::placeholders; #endif -Hints::Hints (weak_ptr film) - : WeakConstFilm (film) - , _long_ccap (false) - , _overlap_ccap (false) - , _too_many_ccap_lines (false) - , _early_subtitle (false) - , _short_subtitle (false) - , _subtitles_too_close (false) - , _too_many_subtitle_lines (false) - , _long_subtitle (false) + +/* When checking to see if things are too big, we'll say they are if they + * are more than the target size minus this "slack." + */ +#define SIZE_SLACK 4096 + + +/* When writing hints: + * - put quotation marks around the name of a GUI tab that you are referring to (e.g. "DCP" or "DCP→Video" tab) + */ + + +Hints::Hints (weak_ptr weak_film) + : WeakConstFilm (weak_film) + , _writer (new Writer(weak_film, weak_ptr(), true)) + , _analyser (film(), film()->playlist(), true, [](float) {}) , _stop (false) { } + void Hints::start () { _thread = boost::thread (bind(&Hints::thread, this)); } + Hints::~Hints () { boost::this_thread::disable_interruption dis; @@ -100,7 +114,7 @@ Hints::check_few_audio_channels () void Hints::check_upmixers () { - AudioProcessor const * ap = film()->audio_processor(); + auto ap = film()->audio_processor(); if (ap && (ap->id() == "stereo-5.1-upmix-a" || ap->id() == "stereo-5.1-upmix-b")) { 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.")); } @@ -112,9 +126,9 @@ Hints::check_incorrect_container () { int narrower_than_scope = 0; int scope = 0; - BOOST_FOREACH (shared_ptr i, film()->content()) { + for (auto i: film()->content()) { if (i->video) { - Ratio const * r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio()); + auto const r = Ratio::nearest_from_ratio(i->video->scaled_size(film()->frame_size()).ratio()); if (r && r->id() == "239") { ++scope; } else if (r && r->id() != "239" && r->id() != "235" && r->id() != "190") { @@ -138,9 +152,9 @@ Hints::check_incorrect_container () void Hints::check_unusual_container () { - string const film_container = film()->container()->id(); - if (film_container != "185" && film_container != "239" && film_container != "190") { - 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")); + auto const film_container = film()->container()->id(); + if (film_container != "185" && film_container != "239") { + 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.")); } } @@ -157,7 +171,7 @@ Hints::check_high_j2k_bandwidth () void Hints::check_frame_rate () { - shared_ptr f = film (); + auto f = film (); switch (f->video_frame_rate()) { case 24: /* Fine */ @@ -187,12 +201,22 @@ Hints::check_frame_rate () } +void +Hints::check_4k_3d () +{ + auto f = film(); + if (f->resolution() == Resolution::FOUR_K && f->three_d()) { + 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.")); + } +} + + void Hints::check_speed_up () { optional lowest_speed_up; optional highest_speed_up; - BOOST_FOREACH (shared_ptr i, film()->content()) { + for (auto i: film()->content()) { double spu = film()->active_frame_rate_change(i->position()).speed_up; if (!lowest_speed_up || spu < *lowest_speed_up) { lowest_speed_up = spu; @@ -217,16 +241,25 @@ Hints::check_speed_up () } +void +Hints::check_interop () +{ + if (film()->interop()) { + hint (_("In general it is now advisable to make SMPTE DCPs unless you have a particular reason to use Interop. You are advised to set your DCP to use the SMPTE standard in the \"DCP\" tab.")); + } +} + + void Hints::check_big_font_files () { bool big_font_files = false; if (film()->interop ()) { - BOOST_FOREACH (shared_ptr i, film()->content()) { - BOOST_FOREACH (shared_ptr j, i->text) { - BOOST_FOREACH (shared_ptr k, j->fonts()) { - optional const p = k->file (); - if (p && boost::filesystem::file_size(p.get()) >= (640 * 1024)) { + for (auto i: film()->content()) { + for (auto j: i->text) { + for (auto k: j->fonts()) { + auto const p = k->file (); + if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) { big_font_files = true; } } @@ -244,8 +277,8 @@ void Hints::check_vob () { int vob = 0; - BOOST_FOREACH (shared_ptr i, film()->content()) { - if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) { + for (auto i: film()->content()) { + if (boost::algorithm::starts_with(i->path(0).filename().string(), "VTS_")) { ++vob; } } @@ -260,8 +293,8 @@ void Hints::check_3d_in_2d () { int three_d = 0; - BOOST_FOREACH (shared_ptr i, film()->content()) { - if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) { + for (auto i: film()->content()) { + if (i->video && i->video->frame_type() != VideoFrameType::TWO_D) { ++three_d; } } @@ -272,40 +305,64 @@ Hints::check_3d_in_2d () } -void +/** @return true if the loudness could be checked, false if it could not because no analysis was available */ +bool Hints::check_loudness () { - boost::filesystem::path path = film()->audio_analysis_path(film()->playlist()); - if (boost::filesystem::exists (path)) { - try { - shared_ptr an (new AudioAnalysis (path)); + auto path = film()->audio_analysis_path(film()->playlist()); + if (!boost::filesystem::exists(path)) { + return false; + } - string ch; + try { + auto an = make_shared(path); - vector sample_peak = an->sample_peak (); - vector true_peak = an->true_peak (); + string ch; - for (size_t i = 0; i < sample_peak.size(); ++i) { - float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]); - float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist()); - if (peak_dB > -3) { - ch += dcp::raw_convert (short_audio_channel_name (i)) + ", "; - } + auto sample_peak = an->sample_peak (); + auto true_peak = an->true_peak (); + + for (size_t i = 0; i < sample_peak.size(); ++i) { + float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]); + float const peak_dB = linear_to_db(peak) + an->gain_correction(film()->playlist()); + if (peak_dB > -3) { + ch += dcp::raw_convert(short_audio_channel_name(i)) + ", "; } + } - ch = ch.substr (0, ch.length() - 2); + ch = ch.substr (0, ch.length() - 2); - if (!ch.empty ()) { - hint (String::compose ( - _("Your audio level is very high (on %1). You should reduce the gain of your audio content."), - ch - ) - ); - } - } catch (OldFormatError& e) { - /* The audio analysis is too old to load in; just skip this hint as if - it had never been run. - */ + if (!ch.empty()) { + hint(String::compose( + _("Your audio level is very high (on %1). You should reduce the gain of your audio content."), + ch + ) + ); + } + } catch (OldFormatError& e) { + /* The audio analysis is too old to load in */ + return false; + } + + return true; +} + + +static +bool +subtitle_mxf_too_big (shared_ptr asset) +{ + return asset && asset->file() && boost::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK); +} + + +void +Hints::check_out_of_range_markers () +{ + auto const length = film()->length(); + for (auto const& i: film()->markers()) { + if (i.second >= length) { + hint (_("At least one marker comes after the end of the project and will be ignored.")); } } } @@ -313,14 +370,19 @@ Hints::check_loudness () void Hints::thread () +try { - shared_ptr film = _film.lock (); + start_of_thread ("Hints"); + + auto film = _film.lock (); if (!film) { return; } - ContentList content = film->content (); + auto content = film->content (); + check_certificates (); + check_interop (); check_big_font_files (); check_few_audio_channels (); check_upmixers (); @@ -328,41 +390,113 @@ Hints::thread () check_unusual_container (); check_high_j2k_bandwidth (); check_frame_rate (); + check_4k_3d (); check_speed_up (); check_vob (); check_3d_in_2d (); - check_loudness (); + auto const check_loudness_done = check_loudness (); check_ffec_and_ffmc_in_smpte_feature (); + check_out_of_range_markers (); + check_text_languages (); + check_audio_language (); - emit (bind(boost::ref(Progress), _("Examining closed captions"))); + if (check_loudness_done) { + emit (bind(boost::ref(Progress), _("Examining subtitles and closed captions"))); + } else { + emit (bind(boost::ref(Progress), _("Examining audio, subtitles and closed captions"))); + } - shared_ptr player (new Player(film)); + auto player = make_shared(film, Image::Alignment::COMPACT); player->set_ignore_video (); - player->set_ignore_audio (); - player->Text.connect (bind(&Hints::text, this, _1, _2, _4)); + if (check_loudness_done || _disable_audio_analysis) { + /* We don't need to analyse audio because we already loaded a suitable analysis */ + player->set_ignore_audio (); + } + player->Audio.connect (bind(&Hints::audio, this, _1, _2)); + player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4)); struct timeval last_pulse; gettimeofday (&last_pulse, 0); - try { - while (!player->pass()) { + _writer->write (player->get_subtitle_fonts()); - struct timeval now; - gettimeofday (&now, 0); - if ((seconds(now) - seconds(last_pulse)) > 1) { - if (_stop) { - break; - } - emit (bind (boost::ref(Pulse))); - last_pulse = now; + while (!player->pass()) { + + struct timeval now; + gettimeofday (&now, 0); + if ((seconds(now) - seconds(last_pulse)) > 1) { + if (_stop) { + return; + } + emit (bind (boost::ref(Pulse))); + last_pulse = now; + } + } + + if (!check_loudness_done) { + _analyser.finish (); + _analyser.get().write(film->audio_analysis_path(film->playlist())); + check_loudness (); + } + + _writer->write (player->get_subtitle_fonts()); + + if (_long_subtitle && !_very_long_subtitle) { + 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.")); + } else if (_very_long_subtitle) { + hint (_("At least one of your subtitle lines has more than 79 characters. You should make each line 79 characters at most in length.")); + } + + bool ccap_xml_too_big = false; + bool ccap_mxf_too_big = false; + bool subs_mxf_too_big = false; + + auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id(); + boost::filesystem::remove_all (dcp_dir); + + _writer->finish (film->dir("hints") / dcpomatic::get_process_id()); + + dcp::DCP dcp (dcp_dir); + dcp.read (); + DCPOMATIC_ASSERT (dcp.cpls().size() == 1); + for (auto reel: dcp.cpls()[0]->reels()) { + for (auto ccap: reel->closed_captions()) { + if (ccap->asset() && ccap->asset()->xml_as_string().length() > static_cast(MAX_CLOSED_CAPTION_XML_SIZE - SIZE_SLACK) && !ccap_xml_too_big) { + hint (_( + "At least one of your closed caption files' XML part is larger than " MAX_CLOSED_CAPTION_XML_SIZE_TEXT + ". You should divide the DCP into shorter reels." + )); + ccap_xml_too_big = true; + } + if (subtitle_mxf_too_big(ccap->asset()) && !ccap_mxf_too_big) { + hint (_( + "At least one of your closed caption files is larger than " MAX_TEXT_MXF_SIZE_TEXT + " in total. You should divide the DCP into shorter reels." + )); + ccap_mxf_too_big = true; } } - } catch (...) { - store_current (); + if (reel->main_subtitle() && subtitle_mxf_too_big(reel->main_subtitle()->asset()) && !subs_mxf_too_big) { + hint (_( + "At least one of your subtitle files is larger than " MAX_TEXT_MXF_SIZE_TEXT " in total. " + "You should divide the DCP into shorter reels." + )); + subs_mxf_too_big = true; + } } + boost::filesystem::remove_all (dcp_dir); emit (bind(boost::ref(Finished))); } +catch (boost::thread_interrupted) +{ + /* The Hints object is being destroyed before it has finished, so just give up */ +} +catch (...) +{ + store_current (); +} + void Hints::hint (string h) @@ -370,14 +504,24 @@ Hints::hint (string h) emit(bind(boost::ref(Hint), h)); } + +void +Hints::audio (shared_ptr audio, DCPTime time) +{ + _analyser.analyse (audio, time); +} + + void -Hints::text (PlayerText text, TextType type, DCPTimePeriod period) +Hints::text (PlayerText text, TextType type, optional track, DCPTimePeriod period) { + _writer->write (text, type, track, period); + switch (type) { - case TEXT_CLOSED_CAPTION: + case TextType::CLOSED_CAPTION: closed_caption (text, period); break; - case TEXT_OPEN_SUBTITLE: + case TextType::OPEN_SUBTITLE: open_subtitle (text, period); break; default: @@ -390,7 +534,7 @@ void Hints::closed_caption (PlayerText text, DCPTimePeriod period) { int lines = text.string.size(); - BOOST_FOREACH (StringText i, text.string) { + for (auto i: text.string) { if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) { ++lines; if (!_long_ccap) { @@ -447,13 +591,16 @@ Hints::open_subtitle (PlayerText text, DCPTimePeriod period) } size_t longest_line = 0; - BOOST_FOREACH (StringText const& i, text.string) { + for (auto const& i: text.string) { longest_line = max (longest_line, i.text().length()); } - if (longest_line > 52 && !_long_subtitle) { + if (longest_line > 52) { _long_subtitle = true; - 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.")); + } + + if (longest_line > 79) { + _very_long_subtitle = true; } _last_subtitle = period; @@ -463,9 +610,9 @@ Hints::open_subtitle (PlayerText text, DCPTimePeriod period) void Hints::check_ffec_and_ffmc_in_smpte_feature () { - shared_ptr f = film(); - if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::FEATURE && (!f->marker(dcp::FFEC) || !f->marker(dcp::FFMC))) { - 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.")); + auto f = film(); + if (!f->interop() && f->dcp_content_type()->libdcp_kind() == dcp::ContentKind::FEATURE && (!f->marker(dcp::Marker::FFEC) || !f->marker(dcp::Marker::FFMC))) { + 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.")); } } @@ -475,3 +622,64 @@ Hints::join () { _thread.join (); } + + +void +Hints::check_text_languages () +{ + for (auto i: film()->content()) { + for (auto j: i->text) { + if (j->use() && !j->language()) { + hint (_("At least one piece of subtitle or closed caption content has no specified language. " + "It is advisable to set the language for each piece of subtitle or closed caption content " + "in the \"Content→Timed text\", \"Content→Open subtitles\" or \"Content→Closed captions\" tab.")); + return; + } + } + } +} + + +void +Hints::check_audio_language () +{ + auto content = film()->content(); + auto mapped_audio = + std::find_if(content.begin(), content.end(), [](shared_ptr c) { + return c->audio && !c->audio->mapping().mapped_output_channels().empty(); + }); + + if (mapped_audio != content.end() && !film()->audio_language()) { + hint (_("Some of your content has audio but you have not set the audio language. It is advisable to set the audio language " + "in the \"DCP\" tab unless your audio has no spoken parts.")); + } +} + + +void +Hints::check_certificates () +{ + auto bad = Config::instance()->check_certificates(); + if (!bad) { + return; + } + + switch (*bad) { + case Config::BAD_SIGNER_UTF8_STRINGS: + hint(_("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs contains a small error " + "which will prevent DCPs from being validated correctly on some systems. You are advised to " + "re-create the signing certificate chain by clicking the \"Re-make certificates and key...\" " + "button in the Keys page of Preferences.")); + break; + case Config::BAD_SIGNER_VALIDITY_TOO_LONG: + hint(_("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs has a validity period " + "that is too long. This will cause problems playing back DCPs on some systems. " + "You are advised to re-create the signing certificate chain by clicking the " + "\"Re-make certificates and key...\" button in the Keys page of Preferences.")); + break; + default: + /* Some bad situations can't happen here as DCP-o-matic would have refused to start until they are fixed */ + break; + } +} +