*/
-#include "reel_writer.h"
-#include "film.h"
+
+#include "audio_buffers.h"
+#include "compose.hpp"
+#include "config.h"
#include "cross.h"
-#include "job.h"
-#include "log.h"
#include "dcpomatic_log.h"
#include "digester.h"
-#include "font_data.h"
-#include "compose.hpp"
-#include "config.h"
-#include "audio_buffers.h"
+#include "film.h"
#include "image.h"
+#include "image_png.h"
+#include "job.h"
+#include "log.h"
+#include "reel_writer.h"
#include <dcp/atmos_asset.h>
#include <dcp/atmos_asset_writer.h>
#include <dcp/certificate_chain.h>
#include "i18n.h"
-using std::list;
-using std::string;
-using std::cout;
+
+using std::dynamic_pointer_cast;
using std::exception;
+using std::list;
+using std::make_shared;
using std::map;
using std::set;
-using std::vector;
using std::shared_ptr;
-using std::make_shared;
+using std::string;
+using std::vector;
+using std::weak_ptr;
using boost::optional;
-using std::dynamic_pointer_cast;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
#endif
-using std::weak_ptr;
using dcp::ArrayData;
using dcp::Data;
using dcp::raw_convert;
using namespace dcpomatic;
+
int const ReelWriter::_info_size = 48;
+
static dcp::MXFMetadata
mxf_metadata ()
{
return meta;
}
+
/** @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
boost::filesystem::path const asset =
film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
- _first_nonexistant_frame = check_existing_picture_asset (asset);
+ _first_nonexistent_frame = check_existing_picture_asset (asset);
- if (_first_nonexistant_frame < period.duration().frames_round(film()->video_frame_rate())) {
+ if (_first_nonexistent_frame < period.duration().frames_round(film()->video_frame_rate())) {
/* We do not have a complete picture asset. If there is an
existing asset, break any hard links to it as we are about
to change its contents (if only by changing the IDs); see
}
_picture_asset->set_file (asset);
- _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
+ _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistent_frame > 0);
} else if (!text_only) {
/* We already have a complete picture asset that we can just re-use */
/* XXX: what about if the encryption key changes? */
_default_font = dcp::ArrayData(default_font_file());
}
+
/** @param frame reel-relative frame */
void
ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
{
auto handle = film()->info_file_handle(_period, false);
- dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
- checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
- checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
- checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
+ handle->get().seek(frame_info_position(frame, eyes), SEEK_SET);
+ handle->get().checked_write(&info.offset, sizeof(info.offset));
+ handle->get().checked_write(&info.size, sizeof(info.size));
+ handle->get().checked_write(info.hash.c_str(), info.hash.size());
}
+
dcp::FrameInfo
ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
{
dcp::FrameInfo frame_info;
- dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
- checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
- checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
+ info->get().seek(frame_info_position(frame, eyes), SEEK_SET);
+ info->get().checked_read(&frame_info.offset, sizeof(frame_info.offset));
+ info->get().checked_read(&frame_info.size, sizeof(frame_info.size));
char hash_buffer[33];
- checked_fread (hash_buffer, 32, info->get(), info->file());
+ info->get().checked_read(hash_buffer, 32);
hash_buffer[32] = '\0';
frame_info.hash = hash_buffer;
return frame_info;
}
+
long
ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
{
DCPOMATIC_ASSERT (false);
}
+
Frame
ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
{
}
/* Try to open the existing asset */
- auto asset_file = fopen_boost (asset, "rb");
+ dcp::File asset_file(asset, "rb");
if (!asset_file) {
LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
return 0;
info_file = film()->info_file_handle (_period, true);
} catch (OpenFileError &) {
LOG_GENERAL_NC ("Could not open film info file");
- fclose (asset_file);
return 0;
}
/* Offset of the last dcp::FrameInfo in the info file */
- int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
- LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
+ int const n = (boost::filesystem::file_size(info_file->get().path()) / _info_size) - 1;
+ LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->get().path()), _info_size);
- Frame first_nonexistant_frame;
+ Frame first_nonexistent_frame;
if (film()->three_d()) {
/* Start looking at the last left frame */
- first_nonexistant_frame = n / 2;
+ first_nonexistent_frame = n / 2;
} else {
- first_nonexistant_frame = n;
+ first_nonexistent_frame = n;
}
- while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
- --first_nonexistant_frame;
+ while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistent_frame) && first_nonexistent_frame > 0) {
+ --first_nonexistent_frame;
}
- if (!film()->three_d() && first_nonexistant_frame > 0) {
+ if (!film()->three_d() && first_nonexistent_frame > 0) {
/* If we are doing 3D we might have found a good L frame with no R, so only
do this if we're in 2D and we've just found a good B(oth) frame.
*/
- ++first_nonexistant_frame;
+ ++first_nonexistent_frame;
}
- LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
+ LOG_GENERAL ("Proceeding with first nonexistent frame %1", first_nonexistent_frame);
- fclose (asset_file);
-
- return first_nonexistant_frame;
+ return first_nonexistent_frame;
}
+
void
ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
{
auto fin = _picture_asset_writer->write (encoded->data(), encoded->size());
write_frame_info (frame, eyes, fin);
- _last_written[static_cast<int>(eyes)] = encoded;
+ _last_written[eyes] = encoded;
}
_picture_asset_writer->fake_write (size);
}
+
void
ReelWriter::repeat_write (Frame frame, Eyes eyes)
{
return;
}
- auto fin = _picture_asset_writer->write (
- _last_written[static_cast<int>(eyes)]->data(),
- _last_written[static_cast<int>(eyes)]->size()
- );
+ auto fin = _picture_asset_writer->write(_last_written[eyes]->data(), _last_written[eyes]->size());
write_frame_info (frame, eyes, fin);
}
+
void
ReelWriter::finish (boost::filesystem::path output_dcp)
{
boost::filesystem::create_hard_link (video_from, video_to, ec);
if (ec) {
- LOG_WARNING_NC ("Hard-link failed; copying instead");
+ LOG_WARNING("Hard-link failed (%1); copying instead", error_details(ec));
auto job = _job.lock ();
if (job) {
job->sub (_("Copying video file into DCP"));
} else {
boost::filesystem::copy_file (video_from, video_to, ec);
if (ec) {
- LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
+ LOG_ERROR("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), error_details(ec));
throw FileError (ec.message(), video_from);
}
}
boost::filesystem::rename (film()->file(aaf), audio_to, ec);
if (ec) {
throw FileError (
- String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
+ String::compose(_("could not move audio asset into the DCP (%1)"), error_details(ec)), aaf
);
}
boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
if (ec) {
throw FileError (
- String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
+ String::compose(_("could not move atmos asset into the DCP (%1)"), error_details(ec)), aaf
);
}
shared_ptr<dcp::SubtitleAsset> asset,
int64_t picture_duration,
shared_ptr<dcp::Reel> reel,
+ int reel_index,
+ int reel_count,
+ optional<string> content_summary,
list<ReferencedReelAsset> const & refs,
- vector<FontData> const & fonts,
+ FontIdMap const& fonts,
+ shared_ptr<dcpomatic::Font> chosen_interop_font,
dcp::ArrayData default_font,
shared_ptr<const Film> film,
DCPTimePeriod period,
shared_ptr<Result> reel_asset;
if (asset) {
- /* Add the font to the subtitle content */
- for (auto const& j: fonts) {
- asset->add_font (j.id, j.data.get_value_or(default_font));
+ if (film->interop()) {
+ if (chosen_interop_font) {
+ /* We only add one font, as Interop will ignore subsequent ones (and some validators will
+ * complain if they are even present)
+ */
+ asset->add_font(fonts.get(chosen_interop_font), chosen_interop_font->data().get_value_or(default_font));
+ }
+ } else {
+ for (auto const& font: fonts.map()) {
+ asset->add_font(font.second, font.first->data().get_value_or(default_font));
+ }
}
if (auto interop = dynamic_pointer_cast<dcp::InteropSubtitleAsset>(asset)) {
auto directory = output_dcp / interop->id ();
boost::filesystem::create_directories (directory);
- interop->write (directory / ("sub_" + interop->id() + ".xml"));
+ interop->write (directory / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".xml"));
reel_asset = make_shared<Interop> (
interop,
dcp::Fraction(film->video_frame_rate(), 1),
*/
smpte->set_intrinsic_duration(picture_duration);
smpte->write (
- output_dcp / ("sub_" + asset->id() + ".mxf")
+ output_dcp / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".mxf")
);
reel_asset = make_shared<SMPTE> (
smpte,
ReelWriter::create_reel_text (
shared_ptr<dcp::Reel> reel,
list<ReferencedReelAsset> const & refs,
- vector<FontData> const& fonts,
+ FontIdMap const& fonts,
+ shared_ptr<dcpomatic::Font> chosen_interop_font,
int64_t duration,
boost::filesystem::path output_dcp,
bool ensure_subtitles,
) const
{
auto subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
- _subtitle_asset, duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
+ _subtitle_asset, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
);
if (subtitle) {
empty_text_asset(TextType::OPEN_SUBTITLE, optional<DCPTextTrack>(), true),
duration,
reel,
+ _reel_index,
+ _reel_count,
+ _content_summary,
refs,
fonts,
+ chosen_interop_font,
_default_font,
film(),
_period,
for (auto const& i: _closed_caption_assets) {
auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
- i.second, duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
+ i.second, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
);
DCPOMATIC_ASSERT (a);
a->set_annotation_text (i.first.name);
/* Make empty tracks for anything we've been asked to ensure but that we haven't added */
for (auto i: ensure_closed_captions) {
auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
- empty_text_asset(TextType::CLOSED_CAPTION, i, true), duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
+ empty_text_asset(TextType::CLOSED_CAPTION, i, true),
+ duration,
+ reel,
+ _reel_index,
+ _reel_count,
+ _content_summary,
+ refs,
+ fonts,
+ chosen_interop_font,
+ _default_font,
+ film(),
+ _period,
+ output_dcp,
+ _text_only
);
DCPOMATIC_ASSERT (a);
a->set_annotation_text (i.name);
shared_ptr<dcp::Reel>
ReelWriter::create_reel (
list<ReferencedReelAsset> const & refs,
- vector<FontData> const & fonts,
+ FontIdMap const & fonts,
+ shared_ptr<dcpomatic::Font> chosen_interop_font,
boost::filesystem::path output_dcp,
bool ensure_subtitles,
set<DCPTextTrack> ensure_closed_captions
create_reel_markers (reel);
}
- create_reel_text (reel, refs, fonts, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
+ create_reel_text (reel, refs, fonts, chosen_interop_font, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
if (_atmos_asset) {
reel->add (make_shared<dcp::ReelAtmosAsset>(_atmos_asset, 0));
*/
}
+
Frame
ReelWriter::start () const
{
dcp::HAlign::CENTER,
0.5,
dcp::VAlign::CENTER,
+ 0,
dcp::Direction::LTR,
" ",
dcp::Effect::NONE,
void
-ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
+ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period, FontIdMap const& fonts)
{
shared_ptr<dcp::SubtitleAsset> asset;
for (auto i: subs.string) {
i.set_in (dcp::Time(period.from.seconds() - _period.from.seconds(), tcr));
i.set_out (dcp::Time(period.to.seconds() - _period.from.seconds(), tcr));
- asset->add (make_shared<dcp::SubtitleString>(i));
+ auto sub = make_shared<dcp::SubtitleString>(i);
+ if (type == TextType::OPEN_SUBTITLE) {
+ sub->set_font(fonts.get(i.font));
+ }
+ asset->add(sub);
}
for (auto i: subs.bitmap) {
asset->add (
make_shared<dcp::SubtitleImage>(
- i.image->as_png(),
+ image_as_png(i.image),
dcp::Time(period.from.seconds() - _period.from.seconds(), tcr),
dcp::Time(period.to.seconds() - _period.from.seconds(), tcr),
- i.rectangle.x, dcp::HAlign::LEFT, i.rectangle.y, dcp::VAlign::TOP,
+ i.rectangle.x, dcp::HAlign::LEFT, i.rectangle.y, dcp::VAlign::TOP, 0,
dcp::Time(), dcp::Time()
)
);
}
}
+
bool
-ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
+ReelWriter::existing_picture_frame_ok (dcp::File& asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
{
LOG_GENERAL ("Checking existing picture frame %1", frame);
bool ok = true;
/* Read the data from the asset and hash it */
- dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
+ asset_file.seek(info.offset, SEEK_SET);
ArrayData data (info.size);
- size_t const read = fread (data.data(), 1, data.size(), asset_file);
+ size_t const read = asset_file.read(data.data(), 1, data.size());
LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
if (read != static_cast<size_t> (data.size ())) {
LOG_GENERAL ("Existing frame %1 is incomplete", frame);