/*
- Copyright (C) 2016-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2016-2022 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
-#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 "font_data.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 "constants.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 "video_content.h"
#include "writer.h"
#include <dcp/cpl.h>
#include <dcp/raw_convert.h>
#include "i18n.h"
+
using std::cout;
using std::make_shared;
using std::max;
-using std::min;
-using std::pair;
using std::shared_ptr;
using std::string;
-using std::vector;
using std::weak_ptr;
using boost::optional;
using boost::bind;
*/
-Hints::Hints (weak_ptr<const Film> film)
- : WeakConstFilm (film)
- , _writer (new Writer(film, weak_ptr<Job>(), true))
+Hints::Hints (weak_ptr<const Film> weak_film)
+ : WeakConstFilm (weak_film)
+ , _writer (new Writer(weak_film, weak_ptr<Job>(), 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;
int scope = 0;
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") {
{
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"));
+ 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."));
}
}
for (auto i: film()->content()) {
for (auto j: i->text) {
for (auto k: j->fonts()) {
- optional<boost::filesystem::path> const p = k->file ();
+ auto const p = k->file ();
if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
big_font_files = true;
}
{
int vob = 0;
for (auto i: film()->content()) {
- if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
+ if (boost::algorithm::starts_with(i->path(0).filename().string(), "VTS_")) {
++vob;
}
}
}
-void
+/** @return true if the loudness could be checked, false if it could not because no analysis was available */
+bool
Hints::check_loudness ()
{
auto path = film()->audio_analysis_path(film()->playlist());
- if (boost::filesystem::exists (path)) {
- try {
- auto an = make_shared<AudioAnalysis>(path);
+ if (!boost::filesystem::exists(path)) {
+ return false;
+ }
- string ch;
+ try {
+ auto an = make_shared<AudioAnalysis>(path);
- auto sample_peak = an->sample_peak ();
- auto 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<string> (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<string>(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;
}
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_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")));
+ }
- auto player = make_shared<Player>(film);
+ auto player = make_shared<Player>(film, Image::Alignment::COMPACT);
player->set_ignore_video ();
- player->set_ignore_audio ();
+ 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;
}
- } catch (...) {
- store_current ();
}
- _writer->write (player->get_subtitle_fonts());
+ if (!check_loudness_done) {
+ _analyser.finish ();
+ _analyser.get().write(film->audio_analysis_path(film->playlist()));
+ check_loudness ();
+ }
+
+
+ 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;
- boost::filesystem::path dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
+ auto dcp_dir = film->dir("hints") / dcpomatic::get_process_id();
boost::filesystem::remove_all (dcp_dir);
- try {
- _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
- } catch (...) {
- store_current ();
- emit (bind(boost::ref(Finished)));
- return;
- }
+ _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
dcp::DCP dcp (dcp_dir);
dcp.read ();
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)
{
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 recommended to make each line 52 characters at most in length."));
}
- if (longest_line > 79 && !_very_long_subtitle) {
+ if (longest_line > 79) {
_very_long_subtitle = true;
- hint (_("At least one of your subtitle lines has more than 79 characters. You should make each line 79 characters at most in length."));
}
_last_subtitle = period;
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"));
+ "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<const Content> 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;
+ }
+}
+