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/>.
21 #include "reel_writer.h"
26 #include "dcpomatic_log.h"
29 #include "compose.hpp"
31 #include "audio_buffers.h"
33 #include <dcp/atmos_asset.h>
34 #include <dcp/atmos_asset_writer.h>
35 #include <dcp/mono_picture_asset.h>
36 #include <dcp/stereo_picture_asset.h>
37 #include <dcp/sound_asset.h>
38 #include <dcp/sound_asset_writer.h>
40 #include <dcp/reel_atmos_asset.h>
41 #include <dcp/reel_mono_picture_asset.h>
42 #include <dcp/reel_stereo_picture_asset.h>
43 #include <dcp/reel_sound_asset.h>
44 #include <dcp/reel_subtitle_asset.h>
45 #include <dcp/reel_closed_caption_asset.h>
46 #include <dcp/reel_markers_asset.h>
49 #include <dcp/certificate_chain.h>
50 #include <dcp/interop_subtitle_asset.h>
51 #include <dcp/smpte_subtitle_asset.h>
52 #include <dcp/raw_convert.h>
53 #include <dcp/subtitle_image.h>
54 #include <boost/foreach.hpp>
64 using boost::shared_ptr;
65 using boost::optional;
66 using boost::dynamic_pointer_cast;
67 #if BOOST_VERSION >= 106100
68 using namespace boost::placeholders;
70 using boost::weak_ptr;
73 using dcp::raw_convert;
74 using namespace dcpomatic;
76 int const ReelWriter::_info_size = 48;
78 static dcp::MXFMetadata
81 dcp::MXFMetadata meta;
82 Config* config = Config::instance();
83 if (!config->dcp_company_name().empty()) {
84 meta.company_name = config->dcp_company_name ();
86 if (!config->dcp_product_name().empty()) {
87 meta.product_name = config->dcp_product_name ();
89 if (!config->dcp_product_version().empty()) {
90 meta.product_version = config->dcp_product_version ();
95 /** @param job Related job, or 0.
96 * @param text_only true to enable a special mode where the writer will expect only subtitles and closed captions to be written
97 * (no picture nor sound) and not give errors in that case. This is used by the hints system to check the potential sizes of
98 * subtitle / closed caption files.
100 ReelWriter::ReelWriter (
101 weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, bool text_only
103 : WeakConstFilm (weak_film)
105 , _reel_index (reel_index)
106 , _reel_count (reel_count)
107 , _content_summary (film()->content_summary(period))
109 , _text_only (text_only)
111 /* Create or find our picture asset in a subdirectory, named
112 according to those film's parameters which affect the video
113 output. We will hard-link it into the DCP later.
116 dcp::Standard const standard = film()->interop() ? dcp::INTEROP : dcp::SMPTE;
118 boost::filesystem::path const asset =
119 film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
121 _first_nonexistant_frame = check_existing_picture_asset (asset);
123 if (_first_nonexistant_frame < period.duration().frames_round(film()->video_frame_rate())) {
124 /* We do not have a complete picture asset. If there is an
125 existing asset, break any hard links to it as we are about
126 to change its contents (if only by changing the IDs); see
129 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
131 job->sub (_("Copying old video file"));
132 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
134 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
136 boost::filesystem::remove (asset);
137 boost::filesystem::rename (asset.string() + ".tmp", asset);
141 if (film()->three_d()) {
142 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
144 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
147 _picture_asset->set_size (film()->frame_size());
148 _picture_asset->set_metadata (mxf_metadata());
150 if (film()->encrypted()) {
151 _picture_asset->set_key (film()->key());
152 _picture_asset->set_context_id (film()->context_id());
155 _picture_asset->set_file (asset);
156 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
158 /* We already have a complete picture asset that we can just re-use */
159 /* XXX: what about if the encryption key changes? */
160 if (film()->three_d()) {
161 _picture_asset.reset (new dcp::StereoPictureAsset(asset));
163 _picture_asset.reset (new dcp::MonoPictureAsset(asset));
167 if (film()->audio_channels()) {
169 new dcp::SoundAsset (dcp::Fraction(film()->video_frame_rate(), 1), film()->audio_frame_rate(), film()->audio_channels(), film()->audio_language(), standard)
172 _sound_asset->set_metadata (mxf_metadata());
174 if (film()->encrypted()) {
175 _sound_asset->set_key (film()->key());
178 DCPOMATIC_ASSERT (film()->directory());
180 vector<dcp::Channel> active;
181 BOOST_FOREACH (int i, film()->mapped_audio_channels()) {
182 active.push_back (static_cast<dcp::Channel>(i));
185 /* Write the sound asset into the film directory so that we leave the creation
186 of the DCP directory until the last minute.
188 _sound_asset_writer = _sound_asset->start_write (
189 film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
191 film()->contains_atmos_content()
196 /** @param frame reel-relative frame */
198 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
200 shared_ptr<InfoFileHandle> handle = film()->info_file_handle(_period, false);
201 dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
202 checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
203 checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
204 checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
208 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
210 dcp::FrameInfo frame_info;
211 dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
212 checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
213 checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
215 char hash_buffer[33];
216 checked_fread (hash_buffer, 32, info->get(), info->file());
217 hash_buffer[32] = '\0';
218 frame_info.hash = hash_buffer;
224 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
228 return frame * _info_size;
230 return frame * _info_size * 2;
232 return frame * _info_size * 2 + _info_size;
234 DCPOMATIC_ASSERT (false);
237 DCPOMATIC_ASSERT (false);
241 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
243 shared_ptr<Job> job = _job.lock ();
246 job->sub (_("Checking existing image data"));
249 /* Try to open the existing asset */
250 FILE* asset_file = fopen_boost (asset, "rb");
252 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
255 LOG_GENERAL ("Opened existing asset at %1", asset.string());
258 shared_ptr<InfoFileHandle> info_file;
261 info_file = film()->info_file_handle (_period, true);
262 } catch (OpenFileError &) {
263 LOG_GENERAL_NC ("Could not open film info file");
268 /* Offset of the last dcp::FrameInfo in the info file */
269 int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
270 LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
272 Frame first_nonexistant_frame;
273 if (film()->three_d()) {
274 /* Start looking at the last left frame */
275 first_nonexistant_frame = n / 2;
277 first_nonexistant_frame = n;
280 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
281 --first_nonexistant_frame;
284 if (!film()->three_d() && first_nonexistant_frame > 0) {
285 /* If we are doing 3D we might have found a good L frame with no R, so only
286 do this if we're in 2D and we've just found a good B(oth) frame.
288 ++first_nonexistant_frame;
291 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
295 return first_nonexistant_frame;
299 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
301 if (!_picture_asset_writer) {
302 /* We're not writing any data */
306 dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data(), encoded->size());
307 write_frame_info (frame, eyes, fin);
308 _last_written[eyes] = encoded;
313 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
316 _atmos_asset = metadata.create (dcp::Fraction(film()->video_frame_rate(), 1));
317 if (film()->encrypted()) {
318 _atmos_asset->set_key(film()->key());
320 _atmos_asset_writer = _atmos_asset->start_write (
321 film()->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
324 _atmos_asset_writer->write (atmos);
329 ReelWriter::fake_write (int size)
331 if (!_picture_asset_writer) {
332 /* We're not writing any data */
336 _picture_asset_writer->fake_write (size);
340 ReelWriter::repeat_write (Frame frame, Eyes eyes)
342 if (!_picture_asset_writer) {
343 /* We're not writing any data */
347 dcp::FrameInfo fin = _picture_asset_writer->write (
348 _last_written[eyes]->data(),
349 _last_written[eyes]->size()
351 write_frame_info (frame, eyes, fin);
355 ReelWriter::finish (boost::filesystem::path output_dcp)
357 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
358 /* Nothing was written to the picture asset */
359 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
360 _picture_asset.reset ();
363 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
364 /* Nothing was written to the sound asset */
365 _sound_asset.reset ();
368 /* Hard-link any video asset file into the DCP */
369 if (_picture_asset) {
370 DCPOMATIC_ASSERT (_picture_asset->file());
371 boost::filesystem::path video_from = _picture_asset->file().get();
372 boost::filesystem::path video_to = output_dcp;
373 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
374 /* There may be an existing "to" file if we are recreating a DCP in the same place without
377 boost::system::error_code ec;
378 boost::filesystem::remove (video_to, ec);
380 boost::filesystem::create_hard_link (video_from, video_to, ec);
382 LOG_WARNING_NC ("Hard-link failed; copying instead");
383 shared_ptr<Job> job = _job.lock ();
385 job->sub (_("Copying video file into DCP"));
387 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
388 } catch (exception& e) {
389 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
390 throw FileError (e.what(), video_from);
393 boost::filesystem::copy_file (video_from, video_to, ec);
395 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
396 throw FileError (ec.message(), video_from);
401 _picture_asset->set_file (video_to);
404 /* Move the audio asset into the DCP */
406 boost::filesystem::path audio_to = output_dcp;
407 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
410 boost::system::error_code ec;
411 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
414 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
418 _sound_asset->set_file (audio_to);
422 _atmos_asset_writer->finalize ();
423 boost::filesystem::path atmos_to = output_dcp;
424 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
427 boost::system::error_code ec;
428 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
431 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
435 _atmos_asset->set_file (atmos_to);
442 shared_ptr<dcp::SubtitleAsset> asset,
443 int64_t picture_duration,
444 shared_ptr<dcp::Reel> reel,
445 list<ReferencedReelAsset> const & refs,
446 list<shared_ptr<Font> > const & fonts,
447 shared_ptr<const Film> film,
448 DCPTimePeriod period,
449 boost::filesystem::path output_dcp,
453 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
455 shared_ptr<T> reel_asset;
458 /* Add the font to the subtitle content */
459 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
460 asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
463 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
464 boost::filesystem::path directory = output_dcp / asset->id ();
465 boost::filesystem::create_directories (directory);
466 asset->write (directory / ("sub_" + asset->id() + ".xml"));
468 /* All our assets should be the same length; use the picture asset length here
469 as a reference to set the subtitle one. We'll use the duration rather than
470 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
471 just interested in its presentation length.
473 dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
476 output_dcp / ("sub_" + asset->id() + ".mxf")
483 dcp::Fraction (film->video_frame_rate(), 1),
489 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
490 BOOST_FOREACH (ReferencedReelAsset j, refs) {
491 shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
492 if (k && j.period == period) {
494 /* If we have a hash for this asset in the CPL, assume that it is correct */
496 k->asset_ref()->set_hash (k->hash().get());
503 if (!text_only && reel_asset->actual_duration() != period_duration) {
504 throw ProgrammingError (
506 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
509 reel->add (reel_asset);
516 shared_ptr<dcp::ReelPictureAsset>
517 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
519 shared_ptr<dcp::ReelPictureAsset> reel_asset;
521 if (_picture_asset) {
522 /* We have made a picture asset of our own. Put it into the reel */
523 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
525 reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
528 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
530 reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
533 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
534 /* We don't have a picture asset of our own; hopefully we have one to reference */
535 BOOST_FOREACH (ReferencedReelAsset j, refs) {
536 shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
538 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
540 if (k && j.period == _period) {
546 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
548 DCPOMATIC_ASSERT (reel_asset);
549 if (reel_asset->duration() != period_duration) {
550 throw ProgrammingError (
552 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
555 reel->add (reel_asset);
557 /* If we have a hash for this asset in the CPL, assume that it is correct */
558 if (reel_asset->hash()) {
559 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
567 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
569 shared_ptr<dcp::ReelSoundAsset> reel_asset;
572 /* We have made a sound asset of our own. Put it into the reel */
573 reel_asset.reset (new dcp::ReelSoundAsset(_sound_asset, 0));
575 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
576 /* We don't have a sound asset of our own; hopefully we have one to reference */
577 BOOST_FOREACH (ReferencedReelAsset j, refs) {
578 shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
580 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
582 if (k && j.period == _period) {
584 /* If we have a hash for this asset in the CPL, assume that it is correct */
586 k->asset_ref()->set_hash (k->hash().get());
592 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
594 DCPOMATIC_ASSERT (reel_asset);
595 if (reel_asset->actual_duration() != period_duration) {
597 "Reel sound asset has length %1 but reel period is %2",
598 reel_asset->actual_duration(),
601 if (reel_asset->actual_duration() != period_duration) {
602 throw ProgrammingError (
604 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
609 reel->add (reel_asset);
614 ReelWriter::create_reel_text (
615 shared_ptr<dcp::Reel> reel,
616 list<ReferencedReelAsset> const & refs,
617 list<shared_ptr<Font> > const& fonts,
619 boost::filesystem::path output_dcp
622 shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (
623 _subtitle_asset, duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
625 if (subtitle && !film()->subtitle_languages().empty()) {
626 subtitle->set_language (film()->subtitle_languages().front());
629 for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
630 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
631 i->second, duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
634 a->set_annotation_text (i->first.name);
635 if (!i->first.language.empty()) {
636 a->set_language (dcp::LanguageTag(i->first.language));
645 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
647 Film::Markers markers = film()->markers();
648 film()->add_ffoc_lfoc(markers);
649 Film::Markers reel_markers;
650 for (Film::Markers::const_iterator i = markers.begin(); i != markers.end(); ++i) {
651 if (_period.contains(i->second)) {
652 reel_markers[i->first] = i->second;
656 if (!reel_markers.empty ()) {
657 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(film()->video_frame_rate(), 1), 0));
658 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
660 DCPTime relative = i->second - _period.from;
661 relative.split (film()->video_frame_rate(), h, m, s, f);
662 ma->set (i->first, dcp::Time(h, m, s, f, film()->video_frame_rate()));
669 shared_ptr<dcp::Reel>
670 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts, boost::filesystem::path output_dcp)
672 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
674 shared_ptr<dcp::Reel> reel (new dcp::Reel());
676 /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
677 * how long the subtitle / CCAP assets should be. However, since we're only writing them to see
678 * how big they are, we don't care about that.
680 int64_t duration = 0;
682 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
683 duration = reel_picture_asset->actual_duration ();
684 create_reel_sound (reel, refs);
685 create_reel_markers (reel);
688 create_reel_text (reel, refs, fonts, duration, output_dcp);
691 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
698 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
700 if (_picture_asset) {
701 _picture_asset->hash (set_progress);
705 _sound_asset->hash (set_progress);
709 _atmos_asset->hash (set_progress);
714 ReelWriter::start () const
716 return _period.from.frames_floor (film()->video_frame_rate());
721 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
723 if (!_sound_asset_writer) {
727 DCPOMATIC_ASSERT (audio);
728 _sound_asset_writer->write (audio->data(), audio->frames());
732 shared_ptr<dcp::SubtitleAsset>
733 ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track) const
735 shared_ptr<dcp::SubtitleAsset> asset;
737 vector<dcp::LanguageTag> lang = film()->subtitle_languages();
738 if (film()->interop()) {
739 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
740 s->set_movie_title (film()->name());
741 if (type == TEXT_OPEN_SUBTITLE) {
742 s->set_language (lang.empty() ? "Unknown" : lang.front().to_string());
743 } else if (!track->language.empty()) {
744 s->set_language (track->language);
746 s->set_reel_number (raw_convert<string> (_reel_index + 1));
749 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
750 s->set_content_title_text (film()->name());
751 s->set_metadata (mxf_metadata());
752 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
753 s->set_language (lang.front());
754 } else if (track && !track->language.empty()) {
755 s->set_language (dcp::LanguageTag(track->language));
757 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
758 s->set_reel_number (_reel_index + 1);
759 s->set_time_code_rate (film()->video_frame_rate());
760 s->set_start_time (dcp::Time ());
761 if (film()->encrypted()) {
762 s->set_key (film()->key());
772 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
774 shared_ptr<dcp::SubtitleAsset> asset;
777 case TEXT_OPEN_SUBTITLE:
778 asset = _subtitle_asset;
780 case TEXT_CLOSED_CAPTION:
781 DCPOMATIC_ASSERT (track);
782 asset = _closed_caption_assets[*track];
785 DCPOMATIC_ASSERT (false);
789 asset = empty_text_asset (type, track);
793 case TEXT_OPEN_SUBTITLE:
794 _subtitle_asset = asset;
796 case TEXT_CLOSED_CAPTION:
797 DCPOMATIC_ASSERT (track);
798 _closed_caption_assets[*track] = asset;
801 DCPOMATIC_ASSERT (false);
804 BOOST_FOREACH (StringText i, subs.string) {
805 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
806 i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
807 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
808 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
811 BOOST_FOREACH (BitmapText i, subs.bitmap) {
813 shared_ptr<dcp::Subtitle>(
814 new dcp::SubtitleImage(
816 dcp::Time(period.from.seconds() - _period.from.seconds(), film()->video_frame_rate()),
817 dcp::Time(period.to.seconds() - _period.from.seconds(), film()->video_frame_rate()),
818 i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
819 dcp::Time(), dcp::Time()
827 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
829 LOG_GENERAL ("Checking existing picture frame %1", frame);
831 /* Read the data from the info file; for 3D we just check the left
832 frames until we find a good one.
834 dcp::FrameInfo const info = read_frame_info (info_file, frame, film()->three_d() ? EYES_LEFT : EYES_BOTH);
838 /* Read the data from the asset and hash it */
839 dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
840 ArrayData data (info.size);
841 size_t const read = fread (data.data(), 1, data.size(), asset_file);
842 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
843 if (read != static_cast<size_t> (data.size ())) {
844 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
848 digester.add (data.data(), data.size());
849 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
850 if (digester.get() != info.hash) {
851 LOG_GENERAL ("Existing frame %1 failed hash check", frame);