BOOST_FOREACH.
[dcpomatic.git] / src / lib / hints.cc
index 14022bb591f6515eb62924ae655ae32cf3bac9e6..ca697ad74a8fc8abbaebe3b17521463b4ab1f68c 100644 (file)
 #include "text_content.h"
 #include "audio_processor.h"
 #include "font.h"
+#include "font_data.h"
 #include "ratio.h"
 #include "audio_analysis.h"
 #include "compose.hpp"
 #include "util.h"
 #include "cross.h"
 #include "player.h"
+#include "writer.h"
+#include <dcp/cpl.h>
 #include <dcp/raw_convert.h>
-#include <boost/foreach.hpp>
+#include <dcp/reel.h>
+#include <dcp/reel_closed_caption_asset.h>
+#include <dcp/reel_subtitle_asset.h>
 #include <boost/algorithm/string.hpp>
 #include <iostream>
 
@@ -46,8 +51,8 @@ using std::pair;
 using std::min;
 using std::max;
 using std::cout;
-using boost::shared_ptr;
-using boost::weak_ptr;
+using std::shared_ptr;
+using std::weak_ptr;
 using boost::optional;
 using boost::bind;
 using namespace dcpomatic;
@@ -55,11 +60,24 @@ using namespace dcpomatic;
 using namespace boost::placeholders;
 #endif
 
+
+/* 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
+
+
 Hints::Hints (weak_ptr<const Film> film)
-       : _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)
        , _stop (false)
 {
 
@@ -107,7 +125,7 @@ Hints::check_incorrect_container ()
 {
        int narrower_than_scope = 0;
        int scope = 0;
-       BOOST_FOREACH (shared_ptr<const Content> 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());
                        if (r && r->id() == "239") {
@@ -187,7 +205,7 @@ Hints::check_speed_up ()
 {
        optional<double> lowest_speed_up;
        optional<double> highest_speed_up;
-       BOOST_FOREACH (shared_ptr<const Content> 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,11 +235,11 @@ Hints::check_big_font_files ()
 {
        bool big_font_files = false;
        if (film()->interop ()) {
-               BOOST_FOREACH (shared_ptr<Content> i, film()->content()) {
-                       BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
-                               BOOST_FOREACH (shared_ptr<Font> k, j->fonts()) {
+               for (auto i: film()->content()) {
+                       for (auto j: i->text) {
+                               for (auto k: j->fonts()) {
                                        optional<boost::filesystem::path> const p = k->file ();
-                                       if (p && boost::filesystem::file_size(p.get()) >= (640 * 1024)) {
+                                       if (p && boost::filesystem::file_size(p.get()) >= (MAX_FONT_FILE_SIZE - SIZE_SLACK)) {
                                                big_font_files = true;
                                        }
                                }
@@ -239,7 +257,7 @@ void
 Hints::check_vob ()
 {
        int vob = 0;
-       BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
+       for (auto i: film()->content()) {
                if (boost::algorithm::starts_with (i->path(0).filename().string(), "VTS_")) {
                        ++vob;
                }
@@ -255,7 +273,7 @@ void
 Hints::check_3d_in_2d ()
 {
        int three_d = 0;
-       BOOST_FOREACH (shared_ptr<const Content> i, film()->content()) {
+       for (auto i: film()->content()) {
                if (i->video && i->video->frame_type() != VIDEO_FRAME_TYPE_2D) {
                        ++three_d;
                }
@@ -306,6 +324,14 @@ Hints::check_loudness ()
 }
 
 
+static
+bool
+subtitle_mxf_too_big (shared_ptr<dcp::SubtitleAsset> asset)
+{
+       return asset && asset->file() && boost::filesystem::file_size(*asset->file()) >= (MAX_TEXT_MXF_SIZE - SIZE_SLACK);
+}
+
+
 void
 Hints::thread ()
 {
@@ -334,7 +360,7 @@ Hints::thread ()
        shared_ptr<Player> player (new Player(film));
        player->set_ignore_video ();
        player->set_ignore_audio ();
-       player->Text.connect (bind(&Hints::text, this, _1, _2, _4));
+       player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4));
 
        struct timeval last_pulse;
        gettimeofday (&last_pulse, 0);
@@ -356,6 +382,53 @@ Hints::thread ()
                store_current ();
        }
 
+       _writer->write (player->get_subtitle_fonts());
+
+       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();
+       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;
+       }
+
+       dcp::DCP dcp (dcp_dir);
+       dcp.read ();
+       DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
+       for (auto reel: dcp.cpls().front()->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;
+               }
+       }
+       boost::filesystem::remove_all (dcp_dir);
+
        emit (bind(boost::ref(Finished)));
 }
 
@@ -366,44 +439,94 @@ Hints::hint (string h)
 }
 
 void
-Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
+Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
 {
-       if (type != TEXT_CLOSED_CAPTION) {
-               return;
+       _writer->write (text, type, track, period);
+
+       switch (type) {
+       case TEXT_CLOSED_CAPTION:
+               closed_caption (text, period);
+               break;
+       case TEXT_OPEN_SUBTITLE:
+               open_subtitle (text, period);
+               break;
+       default:
+               break;
        }
+}
 
+
+void
+Hints::closed_caption (PlayerText text, DCPTimePeriod period)
+{
        int lines = text.string.size();
-       BOOST_FOREACH (StringText i, text.string) {
-               if (utf8_strlen(i.text()) > CLOSED_CAPTION_LENGTH) {
+       for (auto i: text.string) {
+               if (utf8_strlen(i.text()) > MAX_CLOSED_CAPTION_LENGTH) {
                        ++lines;
                        if (!_long_ccap) {
                                _long_ccap = true;
-                               hint (String::compose(_("Some of your closed captions have lines longer than %1 characters, so they will probably be word-wrapped."), CLOSED_CAPTION_LENGTH));
+                               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 > CLOSED_CAPTION_LINES) {
-               hint (String::compose(_("Some of your closed captions span more than %1 lines, so they will be truncated."), CLOSED_CAPTION_LINES));
+       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 && _last->overlap(period)) {
+       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 = period;
+       _last_ccap = period;
 }
 
 
-shared_ptr<const Film>
-Hints::film () const
+void
+Hints::open_subtitle (PlayerText text, DCPTimePeriod period)
 {
-       shared_ptr<const Film> film = _film.lock ();
-       DCPOMATIC_ASSERT (film);
-       return film;
+       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."));
+       }
+
+       if (text.string.size() > 3 && !_too_many_subtitle_lines) {
+               _too_many_subtitle_lines = true;
+               hint (_("At least one of your subtitles has more than 3 lines.  It is advisable to use no more than 3 lines."));
+       }
+
+       size_t longest_line = 0;
+       for (auto const& i: text.string) {
+               longest_line = max (longest_line, i.text().length());
+       }
+
+       if (longest_line > 52 && !_long_subtitle) {
+               _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."));
+       }
+
+       _last_subtitle = period;
 }
 
 
@@ -415,3 +538,10 @@ Hints::check_ffec_and_ffmc_in_smpte_feature ()
                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."));
        }
 }
+
+
+void
+Hints::join ()
+{
+       _thread.join ();
+}