C++11 tidying.
[dcpomatic.git] / src / lib / hints.cc
index cd9251e7cd8fce72496b35191b9ecc6d6d69d3ff..64122db8d5bab6be1ab41a6d14d5c68933a66d2a 100644 (file)
 
 */
 
+
+#include "audio_analysis.h"
+#include "audio_content.h"
+#include "audio_processor.h"
+#include "compose.hpp"
+#include "content.h"
+#include "cross.h"
 #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 "hints.h"
+#include "player.h"
 #include "ratio.h"
-#include "audio_analysis.h"
-#include "compose.hpp"
+#include "text_content.h"
+#include "types.h"
 #include "util.h"
-#include "cross.h"
-#include "player.h"
+#include "video_content.h"
 #include "writer.h"
 #include <dcp/cpl.h>
 #include <dcp/raw_convert.h>
@@ -45,6 +47,7 @@
 
 #include "i18n.h"
 
+
 using std::cout;
 using std::make_shared;
 using std::max;
@@ -68,28 +71,28 @@ using namespace boost::placeholders;
 #define SIZE_SLACK 4096
 
 
-Hints::Hints (weak_ptr<const Film> film)
-       : WeakConstFilm (film)
-       , _writer (new Writer(film, weak_ptr<Job>(), true))
-       , _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 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<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;
@@ -128,7 +131,7 @@ Hints::check_incorrect_container ()
        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") {
@@ -153,8 +156,8 @@ void
 Hints::check_unusual_container ()
 {
        auto 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"));
+       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."));
        }
 }
 
@@ -201,6 +204,16 @@ 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 ()
 {
@@ -235,7 +248,7 @@ 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."));
+               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."));
        }
 }
 
@@ -248,7 +261,7 @@ Hints::check_big_font_files ()
                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;
                                        }
@@ -268,7 +281,7 @@ Hints::check_vob ()
 {
        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;
                }
        }
@@ -295,42 +308,46 @@ 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 ()
 {
        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;
+       }
+
+       try {
+               auto an = make_shared<AudioAnalysis>(path);
 
-                       string ch;
+               string ch;
 
-                       auto sample_peak = an->sample_peak ();
-                       auto true_peak = an->true_peak ();
+               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)) + ", ";
-                               }
+               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;
 }
 
 
@@ -356,7 +373,10 @@ Hints::check_out_of_range_markers ()
 
 void
 Hints::thread ()
+try
 {
+       start_of_thread ("Hints");
+
        auto film = _film.lock ();
        if (!film) {
                return;
@@ -372,56 +392,69 @@ 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")));
+       }
 
        auto player = make_shared<Player>(film);
        player->set_ignore_video ();
-       player->set_ignore_audio ();
+       if (check_loudness_done) {
+               /* 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()) {
+       while (!player->pass()) {
 
-                       struct timeval now;
-                       gettimeofday (&now, 0);
-                       if ((seconds(now) - seconds(last_pulse)) > 1) {
-                               if (_stop) {
-                                       break;
-                               }
-                               emit (bind (boost::ref(Pulse)));
-                               last_pulse = now;
+               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 ();
+       }
+
+       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;
 
-       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 ();
@@ -455,6 +488,15 @@ Hints::thread ()
 
        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)
@@ -462,6 +504,14 @@ 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)
 {
@@ -545,9 +595,12 @@ Hints::open_subtitle (PlayerText text, 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 advisable to make each line 52 characters at most in length."));
+       }
+
+       if (longest_line > 79) {
+               _very_long_subtitle = true;
        }
 
        _last_subtitle = period;
@@ -559,7 +612,7 @@ Hints::check_ffec_and_ffmc_in_smpte_feature ()
 {
        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."));
+               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."));
        }
 }
 
@@ -569,3 +622,36 @@ 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<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."));
+       }
+}
+