then check the result for Bv2.1 violations (part of #1800).
#include "util.h"
#include "cross.h"
#include "player.h"
+#include "writer.h"
+#include <dcp/cpl.h>
#include <dcp/raw_convert.h>
+#include <dcp/reel.h>
+#include <dcp/reel_closed_caption_asset.h>
+#include <dcp/reel_subtitle_asset.h>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
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)
: WeakConstFilm (film)
+ , _writer (new Writer(film, weak_ptr<Job>(), true))
, _long_ccap (false)
, _overlap_ccap (false)
, _too_many_ccap_lines (false)
BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
BOOST_FOREACH (shared_ptr<Font> 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;
}
}
}
+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 ()
{
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);
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);
+ _writer->finish (film->dir("hints") / dcpomatic::get_process_id());
+ dcp::DCP dcp (dcp_dir);
+ dcp.read ();
+ DCPOMATIC_ASSERT (dcp.cpls().size() == 1);
+ BOOST_FOREACH (shared_ptr<dcp::Reel> reel, dcp.cpls().front()->reels()) {
+ BOOST_FOREACH (shared_ptr<dcp::ReelClosedCaptionAsset> 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)));
}
}
void
-Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
+Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
+ _writer->write (text, type, track, period);
+
switch (type) {
case TEXT_CLOSED_CAPTION:
closed_caption (text, period);
#include <vector>
#include <string>
+
class Film;
+class Writer;
+
class Hints : public Signaller, public ExceptionStore, public WeakConstFilm
{
void thread ();
void hint (std::string h);
- void text (PlayerText text, TextType type, dcpomatic::DCPTimePeriod period);
+ void text (PlayerText text, TextType type, boost::optional<DCPTextTrack> track, dcpomatic::DCPTimePeriod period);
void closed_caption (PlayerText text, dcpomatic::DCPTimePeriod period);
void open_subtitle (PlayerText text, dcpomatic::DCPTimePeriod period);
void check_ffec_and_ffmc_in_smpte_feature ();
boost::thread _thread;
+ /** This is used to make a partial DCP containing only the subtitles and closed captions that
+ * our final DCP will have. This means we can see how big the files will be and warn if they
+ * will be too big.
+ */
+ boost::shared_ptr<Writer> _writer;
bool _long_ccap;
bool _overlap_ccap;
return meta;
}
-/** @param job Related job, or 0 */
+/** @param job Related job, or 0.
+ * @param text_only true to enable a special mode where the writer will expect only subtitles and closed captions to be written
+ * (no picture nor sound) and not give errors in that case. This is used by the hints system to check the potential sizes of
+ * subtitle / closed caption files.
+ */
ReelWriter::ReelWriter (
- weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count
+ weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, bool text_only
)
: WeakConstFilm (weak_film)
, _period (period)
, _reel_count (reel_count)
, _content_summary (film()->content_summary(period))
, _job (job)
+ , _text_only (text_only)
{
/* Create or find our picture asset in a subdirectory, named
according to those film's parameters which affect the video
list<shared_ptr<Font> > const & fonts,
shared_ptr<const Film> film,
DCPTimePeriod period,
- boost::filesystem::path output_dcp
+ boost::filesystem::path output_dcp,
+ bool text_only
)
{
Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
}
if (reel_asset) {
- if (reel_asset->actual_duration() != period_duration) {
+ if (!text_only && reel_asset->actual_duration() != period_duration) {
throw ProgrammingError (
__FILE__, __LINE__,
String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
) const
{
shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (
- _subtitle_asset, duration, reel, refs, fonts, film(), _period, output_dcp
+ _subtitle_asset, duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
);
if (subtitle && !film()->subtitle_languages().empty()) {
subtitle->set_language (film()->subtitle_languages().front());
for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
- i->second, duration, reel, refs, fonts, film(), _period, output_dcp
+ i->second, duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
);
if (a) {
a->set_annotation_text (i->first.name);
LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
shared_ptr<dcp::Reel> reel (new dcp::Reel());
- shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
- create_reel_sound (reel, refs);
- create_reel_text (reel, refs, fonts, reel_picture_asset->actual_duration(), output_dcp);
- create_reel_markers (reel);
+
+ /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
+ * how long the subtitle / CCAP assets should be. However, since we're only writing them to see
+ * how big they are, we don't care about that.
+ */
+ int64_t duration = 0;
+ if (!_text_only) {
+ shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
+ duration = reel_picture_asset->actual_duration ();
+ create_reel_sound (reel, refs);
+ create_reel_markers (reel);
+ }
+
+ create_reel_text (reel, refs, fonts, duration, output_dcp);
if (_atmos_asset) {
reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
dcpomatic::DCPTimePeriod period,
boost::shared_ptr<Job> job,
int reel_index,
- int reel_count
+ int reel_count,
+ bool text_only
);
void write (boost::shared_ptr<const dcp::Data> encoded, Frame frame, Eyes eyes);
int _reel_count;
boost::optional<std::string> _content_summary;
boost::weak_ptr<Job> _job;
+ bool _text_only;
boost::shared_ptr<dcp::PictureAsset> _picture_asset;
/** picture asset writer, or 0 if we are not writing any picture because we already have one */
#define MAX_CLOSED_CAPTION_LINES 3
/** Maximum line length of closed caption viewers, according to SMPTE Bv2.1 */
#define MAX_CLOSED_CAPTION_LENGTH 32
+/** Maximum size of a subtitle / closed caption MXF in bytes, according to SMPTE Bv2.1 */
+#define MAX_TEXT_MXF_SIZE (115 * 1024 * 1024)
+#define MAX_TEXT_MXF_SIZE_TEXT "115MB"
+/** Maximum size of a font file, in bytes */
+#define MAX_FONT_FILE_SIZE (640 * 1024)
+#define MAX_FONT_FILE_SIZE_TEXT "640KB"
+/** Maximum size of the XML part of a closed caption file, according to SMPTE Bv2.1 */
+#define MAX_CLOSED_CAPTION_XML_SIZE (256 * 1024)
+#define MAX_CLOSED_CAPTION_XML_SIZE_TEXT "256KB"
extern std::string program_name;
extern bool is_batch_converter;
}
-/** @param j Job to report progress to, or 0 */
-Writer::Writer (weak_ptr<const Film> weak_film, weak_ptr<Job> j)
+/** @param j Job to report progress to, or 0.
+ * @param text_only true to enable only the text (subtitle/ccap) parts of the writer.
+ */
+Writer::Writer (weak_ptr<const Film> weak_film, weak_ptr<Job> j, bool text_only)
: WeakConstFilm (weak_film)
, _job (j)
, _finish (false)
, _fake_written (0)
, _repeat_written (0)
, _pushed_to_disk (0)
+ , _text_only (text_only)
{
shared_ptr<Job> job = _job.lock ();
int reel_index = 0;
list<DCPTimePeriod> const reels = film()->reels();
BOOST_FOREACH (DCPTimePeriod p, reels) {
- _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size()));
+ _reels.push_back (ReelWriter(weak_film, p, job, reel_index++, reels.size(), text_only));
}
_last_written.resize (reels.size());
void
Writer::start ()
{
- _thread = boost::thread (boost::bind(&Writer::thread, this));
+ if (!_text_only) {
+ _thread = boost::thread (boost::bind(&Writer::thread, this));
#ifdef DCPOMATIC_LINUX
- pthread_setname_np (_thread.native_handle(), "writer");
+ pthread_setname_np (_thread.native_handle(), "writer");
#endif
+ }
}
Writer::~Writer ()
{
- terminate_thread (false);
+ if (!_text_only) {
+ terminate_thread (false);
+ }
}
/** Pass a video frame to the writer for writing to disk at some point.
void
Writer::finish (boost::filesystem::path output_dcp)
{
- if (!_thread.joinable()) {
- return;
+ if (_thread.joinable()) {
+ LOG_GENERAL_NC ("Terminating writer thread");
+ terminate_thread (true);
}
- LOG_GENERAL_NC ("Terminating writer thread");
-
- terminate_thread (true);
-
LOG_GENERAL_NC ("Finishing ReelWriters");
BOOST_FOREACH (ReelWriter& i, _reels) {
class Writer : public ExceptionStore, public boost::noncopyable, public WeakConstFilm
{
public:
- Writer (boost::weak_ptr<const Film>, boost::weak_ptr<Job>);
+ Writer (boost::weak_ptr<const Film>, boost::weak_ptr<Job>, bool text_only = false);
~Writer ();
void start ();
*/
int _pushed_to_disk;
+ bool _text_only;
+
boost::mutex _digest_progresses_mutex;
std::map<boost::thread::id, float> _digest_progresses;
-Subproject commit 66e5e6e6a7bd9fe817a00011c1c1c32c6f4e01c7
+Subproject commit 578c16ee95b416d4dcec31c72219dac91e632169
#include "lib/content.h"
#include "lib/content_factory.h"
+#include "lib/cross.h"
#include "lib/film.h"
+#include "lib/font.h"
#include "lib/hints.h"
#include "lib/text_content.h"
#include "lib/util.h"
using std::string;
using std::vector;
+using boost::optional;
using boost::shared_ptr;
static
void
-check (TextType type, string name, string expected_hint)
+check (TextType type, string name, optional<string> expected_hint = optional<string>())
{
shared_ptr<Film> film = new_test_film2 (name);
shared_ptr<Content> content = content_factory("test/data/" + name + ".srt").front();
BOOST_REQUIRE (!wait_for_jobs());
vector<string> hints = get_hints (film);
- BOOST_REQUIRE_EQUAL (hints.size(), 1);
- BOOST_CHECK_EQUAL (hints[0], expected_hint);
+ if (expected_hint) {
+ BOOST_REQUIRE_EQUAL (hints.size(), 1);
+ BOOST_CHECK_EQUAL (hints[0], *expected_hint);
+ } else {
+ BOOST_CHECK (hints.empty());
+ }
}
check (
TEXT_OPEN_SUBTITLE,
"hint_subtitle_too_early",
- "It is advisable to put your first subtitle at least 4 seconds after the start of the DCP to make sure it is seen."
+ string("It is advisable to put your first subtitle at least 4 seconds after the start of the DCP to make sure it is seen.")
);
}
check (
TEXT_OPEN_SUBTITLE,
"hint_short_subtitles",
- "At least one of your subtitles lasts less than 15 frames. It is advisable to make each subtitle at least 15 frames long."
+ string("At least one of your subtitles lasts less than 15 frames. It is advisable to make each subtitle at least 15 frames long.")
);
}
check (
TEXT_OPEN_SUBTITLE,
"hint_subtitles_too_close",
- "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."
+ string("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.")
);
}
check (
TEXT_OPEN_SUBTITLE,
"hint_many_subtitle_lines",
- "At least one of your subtitles has more than 3 lines. It is advisable to use no more than 3 lines."
+ string("At least one of your subtitles has more than 3 lines. It is advisable to use no more than 3 lines.")
);
}
check (
TEXT_OPEN_SUBTITLE,
"hint_subtitle_too_long",
- "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."
+ string("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.")
);
}
+
+BOOST_AUTO_TEST_CASE (hint_subtitle_mxf_too_big)
+{
+ string const name = "hint_subtitle_mxf_too_big";
+
+ shared_ptr<Film> film = new_test_film2 (name);
+ shared_ptr<Content> content = content_factory("test/data/" + name + ".srt").front();
+ content->text.front()->set_type (TEXT_OPEN_SUBTITLE);
+ for (int i = 1; i < 512; ++i) {
+ shared_ptr<dcpomatic::Font> font(new dcpomatic::Font(String::compose("font_%1", i)));
+ font->set_file ("test/data/LiberationSans-Regular.ttf");
+ content->text.front()->add_font(font);
+ }
+ film->examine_and_add_content (content);
+ BOOST_REQUIRE (!wait_for_jobs());
+ vector<string> hints = get_hints (film);
+
+ BOOST_REQUIRE_EQUAL (hints.size(), 1);
+ BOOST_CHECK_EQUAL (
+ hints[0],
+ "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."
+ );
+}
+
+
+BOOST_AUTO_TEST_CASE (hint_closed_caption_xml_too_big)
+{
+ string const name = "hint_closed_caption_xml_too_big";
+
+ shared_ptr<Film> film = new_test_film2 (name);
+
+ FILE* ccap = fopen_boost (String::compose("build/test/%1.srt", name), "w");
+ BOOST_REQUIRE (ccap);
+ for (int i = 0; i < 2048; ++i) {
+ fprintf(ccap, "%d\n", i + 1);
+ int second = i * 2;
+ int minute = second % 60;
+ fprintf(ccap, "00:%02d:%02d,000 --> 00:%02d:%02d,000\n", minute, second, minute, second + 1);
+ fprintf(ccap, "Here are some closed captions.\n\n");
+ }
+ fclose (ccap);
+
+ shared_ptr<Content> content = content_factory("build/test/" + name + ".srt").front();
+ content->text.front()->set_type (TEXT_CLOSED_CAPTION);
+ film->examine_and_add_content (content);
+ BOOST_REQUIRE (!wait_for_jobs());
+ vector<string> hints = get_hints (film);
+
+ BOOST_REQUIRE_EQUAL (hints.size(), 1);
+ BOOST_CHECK_EQUAL (
+ hints[0],
+ "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."
+ );
+}
+
{
shared_ptr<Film> film = new_test_film2 ("write_frame_info_test");
dcpomatic::DCPTimePeriod const period (dcpomatic::DCPTime(0), dcpomatic::DCPTime(96000));
- ReelWriter writer (film, period, shared_ptr<Job>(), 0, 1);
+ ReelWriter writer (film, period, shared_ptr<Job>(), 0, 1, false);
/* Write the first one */