2 Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
22 #include "audio_buffers.h"
23 #include "compose.hpp"
26 #include "dcpomatic_log.h"
30 #include "image_png.h"
33 #include "reel_writer.h"
34 #include <dcp/atmos_asset.h>
35 #include <dcp/atmos_asset_writer.h>
36 #include <dcp/certificate_chain.h>
39 #include <dcp/interop_subtitle_asset.h>
40 #include <dcp/mono_picture_asset.h>
41 #include <dcp/raw_convert.h>
43 #include <dcp/reel_atmos_asset.h>
44 #include <dcp/reel_interop_closed_caption_asset.h>
45 #include <dcp/reel_interop_subtitle_asset.h>
46 #include <dcp/reel_markers_asset.h>
47 #include <dcp/reel_mono_picture_asset.h>
48 #include <dcp/reel_smpte_closed_caption_asset.h>
49 #include <dcp/reel_smpte_subtitle_asset.h>
50 #include <dcp/reel_sound_asset.h>
51 #include <dcp/reel_stereo_picture_asset.h>
52 #include <dcp/smpte_subtitle_asset.h>
53 #include <dcp/sound_asset.h>
54 #include <dcp/sound_asset_writer.h>
55 #include <dcp/stereo_picture_asset.h>
56 #include <dcp/subtitle_image.h>
61 using std::dynamic_pointer_cast;
64 using std::make_shared;
67 using std::shared_ptr;
71 using boost::optional;
72 #if BOOST_VERSION >= 106100
73 using namespace boost::placeholders;
77 using dcp::raw_convert;
78 using namespace dcpomatic;
81 int const ReelWriter::_info_size = 48;
84 static dcp::MXFMetadata
87 dcp::MXFMetadata meta;
88 auto config = Config::instance();
89 if (!config->dcp_company_name().empty()) {
90 meta.company_name = config->dcp_company_name ();
92 if (!config->dcp_product_name().empty()) {
93 meta.product_name = config->dcp_product_name ();
95 if (!config->dcp_product_version().empty()) {
96 meta.product_version = config->dcp_product_version ();
102 /** @param job Related job, or 0.
103 * @param text_only true to enable a special mode where the writer will expect only subtitles and closed captions to be written
104 * (no picture nor sound) and not give errors in that case. This is used by the hints system to check the potential sizes of
105 * subtitle / closed caption files.
107 ReelWriter::ReelWriter (
108 weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, bool text_only
110 : WeakConstFilm (weak_film)
112 , _reel_index (reel_index)
113 , _reel_count (reel_count)
114 , _content_summary (film()->content_summary(period))
116 , _text_only (text_only)
118 /* Create or find our picture asset in a subdirectory, named
119 according to those film's parameters which affect the video
120 output. We will hard-link it into the DCP later.
123 auto const standard = film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE;
125 boost::filesystem::path const asset =
126 film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
128 _first_nonexistant_frame = check_existing_picture_asset (asset);
130 if (_first_nonexistant_frame < period.duration().frames_round(film()->video_frame_rate())) {
131 /* We do not have a complete picture asset. If there is an
132 existing asset, break any hard links to it as we are about
133 to change its contents (if only by changing the IDs); see
136 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
138 job->sub (_("Copying old video file"));
139 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
141 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
143 boost::filesystem::remove (asset);
144 boost::filesystem::rename (asset.string() + ".tmp", asset);
148 if (film()->three_d()) {
149 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
151 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
154 _picture_asset->set_size (film()->frame_size());
155 _picture_asset->set_metadata (mxf_metadata());
157 if (film()->encrypted()) {
158 _picture_asset->set_key (film()->key());
159 _picture_asset->set_context_id (film()->context_id());
162 _picture_asset->set_file (asset);
163 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
164 } else if (!text_only) {
165 /* We already have a complete picture asset that we can just re-use */
166 /* XXX: what about if the encryption key changes? */
167 if (film()->three_d()) {
168 _picture_asset = make_shared<dcp::StereoPictureAsset>(asset);
170 _picture_asset = make_shared<dcp::MonoPictureAsset>(asset);
174 if (film()->audio_channels()) {
175 auto lang = film()->audio_language();
176 _sound_asset = make_shared<dcp::SoundAsset> (
177 dcp::Fraction(film()->video_frame_rate(), 1),
178 film()->audio_frame_rate(),
179 film()->audio_channels(),
180 lang ? *lang : dcp::LanguageTag("en-US"),
184 _sound_asset->set_metadata (mxf_metadata());
186 if (film()->encrypted()) {
187 _sound_asset->set_key (film()->key());
190 DCPOMATIC_ASSERT (film()->directory());
192 /* Write the sound asset into the film directory so that we leave the creation
193 of the DCP directory until the last minute.
195 _sound_asset_writer = _sound_asset->start_write (
196 film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
197 film()->contains_atmos_content()
201 _default_font = dcp::ArrayData(default_font_file());
205 /** @param frame reel-relative frame */
207 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
209 auto handle = film()->info_file_handle(_period, false);
210 handle->get().seek(frame_info_position(frame, eyes), SEEK_SET);
211 handle->get().checked_write(&info.offset, sizeof(info.offset));
212 handle->get().checked_write(&info.size, sizeof(info.size));
213 handle->get().checked_write(info.hash.c_str(), info.hash.size());
218 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
220 dcp::FrameInfo frame_info;
221 info->get().seek(frame_info_position(frame, eyes), SEEK_SET);
222 info->get().checked_read(&frame_info.offset, sizeof(frame_info.offset));
223 info->get().checked_read(&frame_info.size, sizeof(frame_info.size));
225 char hash_buffer[33];
226 info->get().checked_read(hash_buffer, 32);
227 hash_buffer[32] = '\0';
228 frame_info.hash = hash_buffer;
235 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
239 return frame * _info_size;
241 return frame * _info_size * 2;
243 return frame * _info_size * 2 + _info_size;
245 DCPOMATIC_ASSERT (false);
248 DCPOMATIC_ASSERT (false);
253 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
255 auto job = _job.lock ();
258 job->sub (_("Checking existing image data"));
261 /* Try to open the existing asset */
262 dcp::File asset_file(asset, "rb");
264 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
267 LOG_GENERAL ("Opened existing asset at %1", asset.string());
270 shared_ptr<InfoFileHandle> info_file;
273 info_file = film()->info_file_handle (_period, true);
274 } catch (OpenFileError &) {
275 LOG_GENERAL_NC ("Could not open film info file");
279 /* Offset of the last dcp::FrameInfo in the info file */
280 int const n = (boost::filesystem::file_size(info_file->get().path()) / _info_size) - 1;
281 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);
283 Frame first_nonexistant_frame;
284 if (film()->three_d()) {
285 /* Start looking at the last left frame */
286 first_nonexistant_frame = n / 2;
288 first_nonexistant_frame = n;
291 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
292 --first_nonexistant_frame;
295 if (!film()->three_d() && first_nonexistant_frame > 0) {
296 /* If we are doing 3D we might have found a good L frame with no R, so only
297 do this if we're in 2D and we've just found a good B(oth) frame.
299 ++first_nonexistant_frame;
302 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
304 return first_nonexistant_frame;
309 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
311 if (!_picture_asset_writer) {
312 /* We're not writing any data */
316 auto fin = _picture_asset_writer->write (encoded->data(), encoded->size());
317 write_frame_info (frame, eyes, fin);
318 _last_written[static_cast<int>(eyes)] = encoded;
323 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
326 _atmos_asset = metadata.create (dcp::Fraction(film()->video_frame_rate(), 1));
327 if (film()->encrypted()) {
328 _atmos_asset->set_key(film()->key());
330 _atmos_asset_writer = _atmos_asset->start_write (
331 film()->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
334 _atmos_asset_writer->write (atmos);
339 ReelWriter::fake_write (int size)
341 if (!_picture_asset_writer) {
342 /* We're not writing any data */
346 _picture_asset_writer->fake_write (size);
351 ReelWriter::repeat_write (Frame frame, Eyes eyes)
353 if (!_picture_asset_writer) {
354 /* We're not writing any data */
358 auto fin = _picture_asset_writer->write (
359 _last_written[static_cast<int>(eyes)]->data(),
360 _last_written[static_cast<int>(eyes)]->size()
362 write_frame_info (frame, eyes, fin);
367 ReelWriter::finish (boost::filesystem::path output_dcp)
369 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
370 /* Nothing was written to the picture asset */
371 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
372 _picture_asset.reset ();
375 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
376 /* Nothing was written to the sound asset */
377 _sound_asset.reset ();
380 /* Hard-link any video asset file into the DCP */
381 if (_picture_asset) {
382 DCPOMATIC_ASSERT (_picture_asset->file());
383 boost::filesystem::path video_from = _picture_asset->file().get();
384 boost::filesystem::path video_to = output_dcp;
385 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
386 /* There may be an existing "to" file if we are recreating a DCP in the same place without
389 boost::system::error_code ec;
390 boost::filesystem::remove (video_to, ec);
392 boost::filesystem::create_hard_link (video_from, video_to, ec);
394 LOG_WARNING("Hard-link failed (%1); copying instead", error_details(ec));
395 auto job = _job.lock ();
397 job->sub (_("Copying video file into DCP"));
399 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
400 } catch (exception& e) {
401 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
402 throw FileError (e.what(), video_from);
405 boost::filesystem::copy_file (video_from, video_to, ec);
407 LOG_ERROR("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), error_details(ec));
408 throw FileError (ec.message(), video_from);
413 _picture_asset->set_file (video_to);
416 /* Move the audio asset into the DCP */
418 boost::filesystem::path audio_to = output_dcp;
419 auto const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
422 boost::system::error_code ec;
423 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
426 String::compose(_("could not move audio asset into the DCP (%1)"), error_details(ec)), aaf
430 _sound_asset->set_file (audio_to);
434 _atmos_asset_writer->finalize ();
435 boost::filesystem::path atmos_to = output_dcp;
436 auto const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
439 boost::system::error_code ec;
440 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
443 String::compose(_("could not move atmos asset into the DCP (%1)"), error_details(ec)), aaf
447 _atmos_asset->set_file (atmos_to);
452 /** Try to make a ReelAsset for a subtitles or closed captions in a given period in the DCP.
453 * A SubtitleAsset can be provided, or we will use one from @ref refs if not.
455 template <class Interop, class SMPTE, class Result>
458 shared_ptr<dcp::SubtitleAsset> asset,
459 int64_t picture_duration,
460 shared_ptr<dcp::Reel> reel,
463 optional<string> content_summary,
464 list<ReferencedReelAsset> const & refs,
465 FontIdMap const& fonts,
466 shared_ptr<dcpomatic::Font> chosen_interop_font,
467 dcp::ArrayData default_font,
468 shared_ptr<const Film> film,
469 DCPTimePeriod period,
470 boost::filesystem::path output_dcp,
474 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
476 shared_ptr<Result> reel_asset;
479 if (film->interop()) {
480 if (chosen_interop_font) {
481 /* We only add one font, as Interop will ignore subsequent ones (and some validators will
482 * complain if they are even present)
484 asset->add_font(fonts.get(chosen_interop_font), chosen_interop_font->data().get_value_or(default_font));
487 for (auto const& font: fonts.map()) {
488 asset->add_font(font.second, font.first->data().get_value_or(default_font));
492 if (auto interop = dynamic_pointer_cast<dcp::InteropSubtitleAsset>(asset)) {
493 auto directory = output_dcp / interop->id ();
494 boost::filesystem::create_directories (directory);
495 interop->write (directory / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".xml"));
496 reel_asset = make_shared<Interop> (
498 dcp::Fraction(film->video_frame_rate(), 1),
502 } else if (auto smpte = dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)) {
503 /* All our assets should be the same length; use the picture asset length here
504 as a reference to set the subtitle one. We'll use the duration rather than
505 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
506 just interested in its presentation length.
508 smpte->set_intrinsic_duration(picture_duration);
510 output_dcp / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".mxf")
512 reel_asset = make_shared<SMPTE> (
514 dcp::Fraction(film->video_frame_rate(), 1),
521 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
523 auto k = dynamic_pointer_cast<Result> (j.asset);
524 if (k && j.period == period) {
526 /* If we have a hash for this asset in the CPL, assume that it is correct */
528 k->asset_ref()->set_hash (k->hash().get());
535 if (!text_only && reel_asset->actual_duration() != period_duration) {
536 throw ProgrammingError (
538 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
541 reel->add (reel_asset);
548 shared_ptr<dcp::ReelPictureAsset>
549 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
551 shared_ptr<dcp::ReelPictureAsset> reel_asset;
553 if (_picture_asset) {
554 /* We have made a picture asset of our own. Put it into the reel */
555 auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
557 reel_asset = make_shared<dcp::ReelMonoPictureAsset>(mono, 0);
560 auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
562 reel_asset = make_shared<dcp::ReelStereoPictureAsset>(stereo, 0);
565 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
566 /* We don't have a picture asset of our own; hopefully we have one to reference */
568 auto k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
570 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
572 if (k && j.period == _period) {
578 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
580 DCPOMATIC_ASSERT (reel_asset);
581 if (reel_asset->duration() != period_duration) {
582 throw ProgrammingError (
584 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
587 reel->add (reel_asset);
589 /* If we have a hash for this asset in the CPL, assume that it is correct */
590 if (reel_asset->hash()) {
591 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
599 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
601 shared_ptr<dcp::ReelSoundAsset> reel_asset;
604 /* We have made a sound asset of our own. Put it into the reel */
605 reel_asset = make_shared<dcp::ReelSoundAsset>(_sound_asset, 0);
607 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
608 /* We don't have a sound asset of our own; hopefully we have one to reference */
610 auto k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
612 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
614 if (k && j.period == _period) {
616 /* If we have a hash for this asset in the CPL, assume that it is correct */
618 k->asset_ref()->set_hash (k->hash().get());
624 auto const period_duration = _period.duration().frames_round(film()->video_frame_rate());
626 DCPOMATIC_ASSERT (reel_asset);
627 if (reel_asset->actual_duration() != period_duration) {
629 "Reel sound asset has length %1 but reel period is %2",
630 reel_asset->actual_duration(),
633 if (reel_asset->actual_duration() != period_duration) {
634 throw ProgrammingError (
636 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
641 reel->add (reel_asset);
646 ReelWriter::create_reel_text (
647 shared_ptr<dcp::Reel> reel,
648 list<ReferencedReelAsset> const & refs,
649 FontIdMap const& fonts,
650 shared_ptr<dcpomatic::Font> chosen_interop_font,
652 boost::filesystem::path output_dcp,
653 bool ensure_subtitles,
654 set<DCPTextTrack> ensure_closed_captions
657 auto subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
658 _subtitle_asset, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
662 /* We have a subtitle asset that we either made or are referencing */
663 if (auto main_language = film()->subtitle_languages().first) {
664 subtitle->set_language (*main_language);
666 } else if (ensure_subtitles) {
667 /* We had no subtitle asset, but we've been asked to make sure there is one */
668 subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
669 empty_text_asset(TextType::OPEN_SUBTITLE, optional<DCPTextTrack>(), true),
686 for (auto const& i: _closed_caption_assets) {
687 auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
688 i.second, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
690 DCPOMATIC_ASSERT (a);
691 a->set_annotation_text (i.first.name);
692 if (i.first.language) {
693 a->set_language (i.first.language.get());
696 ensure_closed_captions.erase (i.first);
699 /* Make empty tracks for anything we've been asked to ensure but that we haven't added */
700 for (auto i: ensure_closed_captions) {
701 auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
702 empty_text_asset(TextType::CLOSED_CAPTION, i, true),
717 DCPOMATIC_ASSERT (a);
718 a->set_annotation_text (i.name);
720 a->set_language (i.language.get());
727 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
729 auto markers = film()->markers();
730 film()->add_ffoc_lfoc(markers);
731 Film::Markers reel_markers;
732 for (auto const& i: markers) {
733 if (_period.contains(i.second)) {
734 reel_markers[i.first] = i.second;
738 if (!reel_markers.empty ()) {
739 auto ma = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(film()->video_frame_rate(), 1), reel->duration());
740 for (auto const& i: reel_markers) {
741 DCPTime relative = i.second - _period.from;
742 auto hmsf = relative.split (film()->video_frame_rate());
743 ma->set (i.first, dcp::Time(hmsf.h, hmsf.m, hmsf.s, hmsf.f, film()->video_frame_rate()));
750 /** @param ensure_subtitles true to make sure the reel has a subtitle asset.
751 * @param ensure_closed_captions make sure the reel has these closed caption tracks.
753 shared_ptr<dcp::Reel>
754 ReelWriter::create_reel (
755 list<ReferencedReelAsset> const & refs,
756 FontIdMap const & fonts,
757 shared_ptr<dcpomatic::Font> chosen_interop_font,
758 boost::filesystem::path output_dcp,
759 bool ensure_subtitles,
760 set<DCPTextTrack> ensure_closed_captions
763 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
765 auto reel = make_shared<dcp::Reel>();
767 /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
768 * how long the subtitle / CCAP assets should be. However, since we're only writing them to see
769 * how big they are, we don't care about that.
771 int64_t duration = 0;
773 auto reel_picture_asset = create_reel_picture (reel, refs);
774 duration = reel_picture_asset->actual_duration ();
775 create_reel_sound (reel, refs);
776 create_reel_markers (reel);
779 create_reel_text (reel, refs, fonts, chosen_interop_font, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
782 reel->add (make_shared<dcp::ReelAtmosAsset>(_atmos_asset, 0));
789 ReelWriter::calculate_digests (std::function<void (float)> set_progress)
792 if (_picture_asset) {
793 _picture_asset->hash (set_progress);
797 _sound_asset->hash (set_progress);
801 _atmos_asset->hash (set_progress);
803 } catch (boost::thread_interrupted) {
804 /* set_progress contains an interruption_point, so any of these methods
805 * may throw thread_interrupted, at which point we just give up.
811 ReelWriter::start () const
813 return _period.from.frames_floor (film()->video_frame_rate());
818 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
820 if (!_sound_asset_writer) {
824 DCPOMATIC_ASSERT (audio);
825 _sound_asset_writer->write (audio->data(), audio->frames());
829 shared_ptr<dcp::SubtitleAsset>
830 ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track, bool with_dummy) const
832 shared_ptr<dcp::SubtitleAsset> asset;
834 auto lang = film()->subtitle_languages();
835 if (film()->interop()) {
836 auto s = make_shared<dcp::InteropSubtitleAsset>();
837 s->set_movie_title (film()->name());
838 if (type == TextType::OPEN_SUBTITLE) {
839 s->set_language (lang.first ? lang.first->to_string() : "Unknown");
840 } else if (track->language) {
841 s->set_language (track->language->to_string());
843 s->set_reel_number (raw_convert<string> (_reel_index + 1));
846 auto s = make_shared<dcp::SMPTESubtitleAsset>();
847 s->set_content_title_text (film()->name());
848 s->set_metadata (mxf_metadata());
849 if (type == TextType::OPEN_SUBTITLE && lang.first) {
850 s->set_language (*lang.first);
851 } else if (track && track->language) {
852 s->set_language (dcp::LanguageTag(track->language->to_string()));
854 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
855 s->set_reel_number (_reel_index + 1);
856 s->set_time_code_rate (film()->video_frame_rate());
857 s->set_start_time (dcp::Time ());
858 if (film()->encrypted()) {
859 s->set_key (film()->key());
863 std::make_shared<dcp::SubtitleString>(
864 optional<std::string>(),
871 dcp::Time(0, 0, 0, 0, 24),
872 dcp::Time(0, 0, 1, 0, 24),
895 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period, FontIdMap const& fonts)
897 shared_ptr<dcp::SubtitleAsset> asset;
900 case TextType::OPEN_SUBTITLE:
901 asset = _subtitle_asset;
903 case TextType::CLOSED_CAPTION:
904 DCPOMATIC_ASSERT (track);
905 asset = _closed_caption_assets[*track];
908 DCPOMATIC_ASSERT (false);
912 asset = empty_text_asset (type, track, false);
916 case TextType::OPEN_SUBTITLE:
917 _subtitle_asset = asset;
919 case TextType::CLOSED_CAPTION:
920 DCPOMATIC_ASSERT (track);
921 _closed_caption_assets[*track] = asset;
924 DCPOMATIC_ASSERT (false);
927 /* timecode rate for subtitles we emit; we might as well stick to ms accuracy here, I think */
928 auto const tcr = 1000;
930 for (auto i: subs.string) {
931 i.set_in (dcp::Time(period.from.seconds() - _period.from.seconds(), tcr));
932 i.set_out (dcp::Time(period.to.seconds() - _period.from.seconds(), tcr));
933 auto sub = make_shared<dcp::SubtitleString>(i);
934 if (type == TextType::OPEN_SUBTITLE) {
935 sub->set_font(fonts.get(i.font));
940 for (auto i: subs.bitmap) {
942 make_shared<dcp::SubtitleImage>(
943 image_as_png(i.image),
944 dcp::Time(period.from.seconds() - _period.from.seconds(), tcr),
945 dcp::Time(period.to.seconds() - _period.from.seconds(), tcr),
946 i.rectangle.x, dcp::HAlign::LEFT, i.rectangle.y, dcp::VAlign::TOP,
947 dcp::Time(), dcp::Time()
955 ReelWriter::existing_picture_frame_ok (dcp::File& asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
957 LOG_GENERAL ("Checking existing picture frame %1", frame);
959 /* Read the data from the info file; for 3D we just check the left
960 frames until we find a good one.
962 auto const info = read_frame_info (info_file, frame, film()->three_d() ? Eyes::LEFT : Eyes::BOTH);
966 /* Read the data from the asset and hash it */
967 asset_file.seek(info.offset, SEEK_SET);
968 ArrayData data (info.size);
969 size_t const read = asset_file.read(data.data(), 1, data.size());
970 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
971 if (read != static_cast<size_t> (data.size ())) {
972 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
976 digester.add (data.data(), data.size());
977 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
978 if (digester.get() != info.hash) {
979 LOG_GENERAL ("Existing frame %1 failed hash check", frame);