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_nonexistent_frame = check_existing_picture_asset (asset);
130 if (_first_nonexistent_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_nonexistent_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_nonexistent_frame;
284 if (film()->three_d()) {
285 /* Start looking at the last left frame */
286 first_nonexistent_frame = n / 2;
288 first_nonexistent_frame = n;
291 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistent_frame) && first_nonexistent_frame > 0) {
292 --first_nonexistent_frame;
295 if (!film()->three_d() && first_nonexistent_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_nonexistent_frame;
302 LOG_GENERAL ("Proceeding with first nonexistent frame %1", first_nonexistent_frame);
304 return first_nonexistent_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[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(_last_written[eyes]->data(), _last_written[eyes]->size());
359 write_frame_info (frame, eyes, fin);
364 ReelWriter::finish (boost::filesystem::path output_dcp)
366 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
367 /* Nothing was written to the picture asset */
368 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
369 _picture_asset.reset ();
372 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
373 /* Nothing was written to the sound asset */
374 _sound_asset.reset ();
377 /* Hard-link any video asset file into the DCP */
378 if (_picture_asset) {
379 DCPOMATIC_ASSERT (_picture_asset->file());
380 boost::filesystem::path video_from = _picture_asset->file().get();
381 boost::filesystem::path video_to = output_dcp;
382 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
383 /* There may be an existing "to" file if we are recreating a DCP in the same place without
386 boost::system::error_code ec;
387 boost::filesystem::remove (video_to, ec);
389 boost::filesystem::create_hard_link (video_from, video_to, ec);
391 LOG_WARNING("Hard-link failed (%1); copying instead", error_details(ec));
392 auto job = _job.lock ();
394 job->sub (_("Copying video file into DCP"));
396 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
397 } catch (exception& e) {
398 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
399 throw FileError (e.what(), video_from);
402 boost::filesystem::copy_file (video_from, video_to, ec);
404 LOG_ERROR("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), error_details(ec));
405 throw FileError (ec.message(), video_from);
410 _picture_asset->set_file (video_to);
413 /* Move the audio asset into the DCP */
415 boost::filesystem::path audio_to = output_dcp;
416 auto const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
419 boost::system::error_code ec;
420 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
423 String::compose(_("could not move audio asset into the DCP (%1)"), error_details(ec)), aaf
427 _sound_asset->set_file (audio_to);
431 _atmos_asset_writer->finalize ();
432 boost::filesystem::path atmos_to = output_dcp;
433 auto const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
436 boost::system::error_code ec;
437 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
440 String::compose(_("could not move atmos asset into the DCP (%1)"), error_details(ec)), aaf
444 _atmos_asset->set_file (atmos_to);
449 /** Try to make a ReelAsset for a subtitles or closed captions in a given period in the DCP.
450 * A SubtitleAsset can be provided, or we will use one from @ref refs if not.
452 template <class Interop, class SMPTE, class Result>
455 shared_ptr<dcp::SubtitleAsset> asset,
456 int64_t picture_duration,
457 shared_ptr<dcp::Reel> reel,
460 optional<string> content_summary,
461 list<ReferencedReelAsset> const & refs,
462 FontIdMap const& fonts,
463 shared_ptr<dcpomatic::Font> chosen_interop_font,
464 dcp::ArrayData default_font,
465 shared_ptr<const Film> film,
466 DCPTimePeriod period,
467 boost::filesystem::path output_dcp,
471 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
473 shared_ptr<Result> reel_asset;
476 if (film->interop()) {
477 if (chosen_interop_font) {
478 /* We only add one font, as Interop will ignore subsequent ones (and some validators will
479 * complain if they are even present)
481 asset->add_font(fonts.get(chosen_interop_font), chosen_interop_font->data().get_value_or(default_font));
484 for (auto const& font: fonts.map()) {
485 asset->add_font(font.second, font.first->data().get_value_or(default_font));
489 if (auto interop = dynamic_pointer_cast<dcp::InteropSubtitleAsset>(asset)) {
490 auto directory = output_dcp / interop->id ();
491 boost::filesystem::create_directories (directory);
492 interop->write (directory / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".xml"));
493 reel_asset = make_shared<Interop> (
495 dcp::Fraction(film->video_frame_rate(), 1),
499 } else if (auto smpte = dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)) {
500 /* All our assets should be the same length; use the picture asset length here
501 as a reference to set the subtitle one. We'll use the duration rather than
502 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
503 just interested in its presentation length.
505 smpte->set_intrinsic_duration(picture_duration);
507 output_dcp / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".mxf")
509 reel_asset = make_shared<SMPTE> (
511 dcp::Fraction(film->video_frame_rate(), 1),
518 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
520 auto k = dynamic_pointer_cast<Result> (j.asset);
521 if (k && j.period == period) {
523 /* If we have a hash for this asset in the CPL, assume that it is correct */
525 k->asset_ref()->set_hash (k->hash().get());
532 if (!text_only && reel_asset->actual_duration() != period_duration) {
533 throw ProgrammingError (
535 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
538 reel->add (reel_asset);
545 shared_ptr<dcp::ReelPictureAsset>
546 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
548 shared_ptr<dcp::ReelPictureAsset> reel_asset;
550 if (_picture_asset) {
551 /* We have made a picture asset of our own. Put it into the reel */
552 auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
554 reel_asset = make_shared<dcp::ReelMonoPictureAsset>(mono, 0);
557 auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
559 reel_asset = make_shared<dcp::ReelStereoPictureAsset>(stereo, 0);
562 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
563 /* We don't have a picture asset of our own; hopefully we have one to reference */
565 auto k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
567 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
569 if (k && j.period == _period) {
575 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
577 DCPOMATIC_ASSERT (reel_asset);
578 if (reel_asset->duration() != period_duration) {
579 throw ProgrammingError (
581 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
584 reel->add (reel_asset);
586 /* If we have a hash for this asset in the CPL, assume that it is correct */
587 if (reel_asset->hash()) {
588 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
596 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
598 shared_ptr<dcp::ReelSoundAsset> reel_asset;
601 /* We have made a sound asset of our own. Put it into the reel */
602 reel_asset = make_shared<dcp::ReelSoundAsset>(_sound_asset, 0);
604 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
605 /* We don't have a sound asset of our own; hopefully we have one to reference */
607 auto k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
609 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
611 if (k && j.period == _period) {
613 /* If we have a hash for this asset in the CPL, assume that it is correct */
615 k->asset_ref()->set_hash (k->hash().get());
621 auto const period_duration = _period.duration().frames_round(film()->video_frame_rate());
623 DCPOMATIC_ASSERT (reel_asset);
624 if (reel_asset->actual_duration() != period_duration) {
626 "Reel sound asset has length %1 but reel period is %2",
627 reel_asset->actual_duration(),
630 if (reel_asset->actual_duration() != period_duration) {
631 throw ProgrammingError (
633 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
638 reel->add (reel_asset);
643 ReelWriter::create_reel_text (
644 shared_ptr<dcp::Reel> reel,
645 list<ReferencedReelAsset> const & refs,
646 FontIdMap const& fonts,
647 shared_ptr<dcpomatic::Font> chosen_interop_font,
649 boost::filesystem::path output_dcp,
650 bool ensure_subtitles,
651 set<DCPTextTrack> ensure_closed_captions
654 auto subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
655 _subtitle_asset, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
659 /* We have a subtitle asset that we either made or are referencing */
660 if (auto main_language = film()->subtitle_languages().first) {
661 subtitle->set_language (*main_language);
663 } else if (ensure_subtitles) {
664 /* We had no subtitle asset, but we've been asked to make sure there is one */
665 subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
666 empty_text_asset(TextType::OPEN_SUBTITLE, optional<DCPTextTrack>(), true),
683 for (auto const& i: _closed_caption_assets) {
684 auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
685 i.second, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
687 DCPOMATIC_ASSERT (a);
688 a->set_annotation_text (i.first.name);
689 if (i.first.language) {
690 a->set_language (i.first.language.get());
693 ensure_closed_captions.erase (i.first);
696 /* Make empty tracks for anything we've been asked to ensure but that we haven't added */
697 for (auto i: ensure_closed_captions) {
698 auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
699 empty_text_asset(TextType::CLOSED_CAPTION, i, true),
714 DCPOMATIC_ASSERT (a);
715 a->set_annotation_text (i.name);
717 a->set_language (i.language.get());
724 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
726 auto markers = film()->markers();
727 film()->add_ffoc_lfoc(markers);
728 Film::Markers reel_markers;
729 for (auto const& i: markers) {
730 if (_period.contains(i.second)) {
731 reel_markers[i.first] = i.second;
735 if (!reel_markers.empty ()) {
736 auto ma = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(film()->video_frame_rate(), 1), reel->duration());
737 for (auto const& i: reel_markers) {
738 DCPTime relative = i.second - _period.from;
739 auto hmsf = relative.split (film()->video_frame_rate());
740 ma->set (i.first, dcp::Time(hmsf.h, hmsf.m, hmsf.s, hmsf.f, film()->video_frame_rate()));
747 /** @param ensure_subtitles true to make sure the reel has a subtitle asset.
748 * @param ensure_closed_captions make sure the reel has these closed caption tracks.
750 shared_ptr<dcp::Reel>
751 ReelWriter::create_reel (
752 list<ReferencedReelAsset> const & refs,
753 FontIdMap const & fonts,
754 shared_ptr<dcpomatic::Font> chosen_interop_font,
755 boost::filesystem::path output_dcp,
756 bool ensure_subtitles,
757 set<DCPTextTrack> ensure_closed_captions
760 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
762 auto reel = make_shared<dcp::Reel>();
764 /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
765 * how long the subtitle / CCAP assets should be. However, since we're only writing them to see
766 * how big they are, we don't care about that.
768 int64_t duration = 0;
770 auto reel_picture_asset = create_reel_picture (reel, refs);
771 duration = reel_picture_asset->actual_duration ();
772 create_reel_sound (reel, refs);
773 create_reel_markers (reel);
776 create_reel_text (reel, refs, fonts, chosen_interop_font, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
779 reel->add (make_shared<dcp::ReelAtmosAsset>(_atmos_asset, 0));
786 ReelWriter::calculate_digests (std::function<void (float)> set_progress)
789 if (_picture_asset) {
790 _picture_asset->hash (set_progress);
794 _sound_asset->hash (set_progress);
798 _atmos_asset->hash (set_progress);
800 } catch (boost::thread_interrupted) {
801 /* set_progress contains an interruption_point, so any of these methods
802 * may throw thread_interrupted, at which point we just give up.
808 ReelWriter::start () const
810 return _period.from.frames_floor (film()->video_frame_rate());
815 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
817 if (!_sound_asset_writer) {
821 DCPOMATIC_ASSERT (audio);
822 _sound_asset_writer->write (audio->data(), audio->frames());
826 shared_ptr<dcp::SubtitleAsset>
827 ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track, bool with_dummy) const
829 shared_ptr<dcp::SubtitleAsset> asset;
831 auto lang = film()->subtitle_languages();
832 if (film()->interop()) {
833 auto s = make_shared<dcp::InteropSubtitleAsset>();
834 s->set_movie_title (film()->name());
835 if (type == TextType::OPEN_SUBTITLE) {
836 s->set_language (lang.first ? lang.first->to_string() : "Unknown");
837 } else if (track->language) {
838 s->set_language (track->language->to_string());
840 s->set_reel_number (raw_convert<string> (_reel_index + 1));
843 auto s = make_shared<dcp::SMPTESubtitleAsset>();
844 s->set_content_title_text (film()->name());
845 s->set_metadata (mxf_metadata());
846 if (type == TextType::OPEN_SUBTITLE && lang.first) {
847 s->set_language (*lang.first);
848 } else if (track && track->language) {
849 s->set_language (dcp::LanguageTag(track->language->to_string()));
851 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
852 s->set_reel_number (_reel_index + 1);
853 s->set_time_code_rate (film()->video_frame_rate());
854 s->set_start_time (dcp::Time ());
855 if (film()->encrypted()) {
856 s->set_key (film()->key());
860 std::make_shared<dcp::SubtitleString>(
861 optional<std::string>(),
868 dcp::Time(0, 0, 0, 0, 24),
869 dcp::Time(0, 0, 1, 0, 24),
892 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period, FontIdMap const& fonts)
894 shared_ptr<dcp::SubtitleAsset> asset;
897 case TextType::OPEN_SUBTITLE:
898 asset = _subtitle_asset;
900 case TextType::CLOSED_CAPTION:
901 DCPOMATIC_ASSERT (track);
902 asset = _closed_caption_assets[*track];
905 DCPOMATIC_ASSERT (false);
909 asset = empty_text_asset (type, track, false);
913 case TextType::OPEN_SUBTITLE:
914 _subtitle_asset = asset;
916 case TextType::CLOSED_CAPTION:
917 DCPOMATIC_ASSERT (track);
918 _closed_caption_assets[*track] = asset;
921 DCPOMATIC_ASSERT (false);
924 /* timecode rate for subtitles we emit; we might as well stick to ms accuracy here, I think */
925 auto const tcr = 1000;
927 for (auto i: subs.string) {
928 i.set_in (dcp::Time(period.from.seconds() - _period.from.seconds(), tcr));
929 i.set_out (dcp::Time(period.to.seconds() - _period.from.seconds(), tcr));
930 auto sub = make_shared<dcp::SubtitleString>(i);
931 if (type == TextType::OPEN_SUBTITLE) {
932 sub->set_font(fonts.get(i.font));
937 for (auto i: subs.bitmap) {
939 make_shared<dcp::SubtitleImage>(
940 image_as_png(i.image),
941 dcp::Time(period.from.seconds() - _period.from.seconds(), tcr),
942 dcp::Time(period.to.seconds() - _period.from.seconds(), tcr),
943 i.rectangle.x, dcp::HAlign::LEFT, i.rectangle.y, dcp::VAlign::TOP,
944 dcp::Time(), dcp::Time()
952 ReelWriter::existing_picture_frame_ok (dcp::File& asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
954 LOG_GENERAL ("Checking existing picture frame %1", frame);
956 /* Read the data from the info file; for 3D we just check the left
957 frames until we find a good one.
959 auto const info = read_frame_info (info_file, frame, film()->three_d() ? Eyes::LEFT : Eyes::BOTH);
963 /* Read the data from the asset and hash it */
964 asset_file.seek(info.offset, SEEK_SET);
965 ArrayData data (info.size);
966 size_t const read = asset_file.read(data.data(), 1, data.size());
967 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
968 if (read != static_cast<size_t> (data.size ())) {
969 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
973 digester.add (data.data(), data.size());
974 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
975 if (digester.get() != info.hash) {
976 LOG_GENERAL ("Existing frame %1 failed hash check", frame);