+ string ch;
+
+ 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<string>(short_audio_channel_name(i)) + ", ";
+ }
+ }
+
+ 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 */
+ return false;
+ }
+
+ return true;
+}
+
+
+static
+bool
+subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
+{
+ return asset && asset->file() && dcp::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."));
+ }
+ }
+}
+
+
+void
+Hints::scan_content(shared_ptr<const Film> film)
+{
+ auto const check_loudness_done = check_loudness();
+
+ auto content = film->playlist()->content();
+ auto iter = std::find_if(content.begin(), content.end(), [](shared_ptr<const Content> content) {
+ auto text_iter = std::find_if(content->text.begin(), content->text.end(), [](shared_ptr<const TextContent> text) {
+ return text->use();
+ });
+ return text_iter != content->text.end();
+ });
+
+ auto const have_text = iter != content.end();
+
+ if (check_loudness_done && !have_text) {
+ /* We don't need to check loudness, and we don't have any active text to check,
+ * so a scan of the content is pointless.
+ */
+ return;
+ }
+
+ if (check_loudness_done && have_text) {
+ emit (bind(boost::ref(Progress), _("Examining subtitles and closed captions")));
+ } else if (!check_loudness_done && !have_text) {
+ emit (bind(boost::ref(Progress), _("Examining audio")));
+ } else {
+ emit (bind(boost::ref(Progress), _("Examining audio, subtitles and closed captions")));
+ }
+
+ auto player = make_shared<Player>(film, Image::Alignment::COMPACT);
+ player->set_ignore_video();
+ 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();
+ } else {
+ /* Send auto to the analyser to check loudness */
+ 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);
+
+ _writer->write(player->get_subtitle_fonts());
+
+ 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();
+ }
+}
+
+
+void
+Hints::thread ()
+try
+{
+ start_of_thread ("Hints");
+
+ auto film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ auto content = film->content ();
+
+ check_certificates ();
+ check_interop ();
+ check_big_font_files ();
+ check_few_audio_channels ();
+ check_upmixers ();
+ check_incorrect_container ();
+ check_unusual_container ();
+ check_high_j2k_bandwidth ();
+ check_frame_rate ();
+ check_4k_3d ();
+ check_speed_up ();
+ check_vob ();
+ check_3d_in_2d ();
+ check_ffec_and_ffmc_in_smpte_feature ();
+ check_out_of_range_markers ();
+ check_subtitle_languages();
+ check_audio_language ();
+ check_8_or_16_audio_channels();
+
+ scan_content(film);
+
+ 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();
+ dcp::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<size_t>(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;
+ }
+ }
+ 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;
+ }
+ }
+ dcp::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)
+{
+ emit(bind(boost::ref(Hint), h));
+}
+
+
+void
+Hints::audio (shared_ptr<AudioBuffers> audio, DCPTime time)
+{
+ _analyser.analyse (audio, time);
+}
+
+
+void
+Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
+{
+ _writer->write (text, type, track, period);
+
+ switch (type) {
+ case TextType::CLOSED_CAPTION:
+ closed_caption (text, period);
+ break;
+ case TextType::OPEN_SUBTITLE:
+ open_subtitle (text, period);
+ break;
+ default:
+ break;
+ }
+}
+
+
+void
+Hints::closed_caption (PlayerText text, DCPTimePeriod period)
+{
+ int lines = text.string.size();
+ for (auto i: text.string) {
+ if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
+ ++lines;
+ if (!_long_ccap) {
+ _long_ccap = true;
+ hint (
+ String::compose(
+ "At least one of your closed caption lines has more than %1 characters. "
+ "It is advisable to make each line %1 characters at most in length.",
+ MAX_CLOSED_CAPTION_LENGTH,
+ MAX_CLOSED_CAPTION_LENGTH)
+ );
+ }
+ }
+ }
+
+ if (!_too_many_ccap_lines && lines > MAX_CLOSED_CAPTION_LINES) {
+ hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), MAX_CLOSED_CAPTION_LINES));
+ _too_many_ccap_lines = true;
+ }
+
+ /* XXX: maybe overlapping closed captions (i.e. different languages) are OK with Interop? */
+ if (film()->interop() && !_overlap_ccap && _last_ccap && _last_ccap->overlap(period)) {
+ _overlap_ccap = true;
+ hint (_("You have overlapping closed captions, which are not allowed in Interop DCPs. Change your DCP standard to SMPTE."));
+ }
+
+ _last_ccap = period;
+}
+
+
+void
+Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
+{
+ if (period.from < DCPTime::from_seconds(4) && !_early_subtitle) {
+ _early_subtitle = true;
+ 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."));
+ }
+
+ int const vfr = film()->video_frame_rate ();
+
+ if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
+ _short_subtitle = true;
+ hint (_("At least one of your subtitles lasts less than 15 frames. It is advisable to make each subtitle at least 15 frames long."));
+ }
+
+ if (_last_subtitle && DCPTime(period.from - _last_subtitle->to).frames_round(vfr) < 2 && !_subtitles_too_close) {
+ _subtitles_too_close = true;
+ 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."));
+ }
+
+ struct VPos
+ {
+ public:
+ dcp::VAlign align;
+ float position;
+
+ bool operator<(VPos const& other) const {
+ if (static_cast<int>(align) != static_cast<int>(other.align)) {
+ return static_cast<int>(align) < static_cast<int>(other.align);