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 ReelWriter::ReelWriter (
97 weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count
99 : WeakConstFilm (weak_film)
101 , _reel_index (reel_index)
102 , _reel_count (reel_count)
103 , _content_summary (film()->content_summary(period))
106 /* Create or find our picture asset in a subdirectory, named
107 according to those film's parameters which affect the video
108 output. We will hard-link it into the DCP later.
111 dcp::Standard const standard = film()->interop() ? dcp::INTEROP : dcp::SMPTE;
113 boost::filesystem::path const asset =
114 film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
116 _first_nonexistant_frame = check_existing_picture_asset (asset);
118 if (_first_nonexistant_frame < period.duration().frames_round(film()->video_frame_rate())) {
119 /* We do not have a complete picture asset. If there is an
120 existing asset, break any hard links to it as we are about
121 to change its contents (if only by changing the IDs); see
124 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
126 job->sub (_("Copying old video file"));
127 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
129 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
131 boost::filesystem::remove (asset);
132 boost::filesystem::rename (asset.string() + ".tmp", asset);
136 if (film()->three_d()) {
137 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
139 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
142 _picture_asset->set_size (film()->frame_size());
143 _picture_asset->set_metadata (mxf_metadata());
145 if (film()->encrypted()) {
146 _picture_asset->set_key (film()->key());
147 _picture_asset->set_context_id (film()->context_id());
150 _picture_asset->set_file (asset);
151 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
153 /* We already have a complete picture asset that we can just re-use */
154 /* XXX: what about if the encryption key changes? */
155 if (film()->three_d()) {
156 _picture_asset.reset (new dcp::StereoPictureAsset(asset));
158 _picture_asset.reset (new dcp::MonoPictureAsset(asset));
162 if (film()->audio_channels()) {
164 new dcp::SoundAsset (dcp::Fraction(film()->video_frame_rate(), 1), film()->audio_frame_rate(), film()->audio_channels(), film()->audio_language(), standard)
167 _sound_asset->set_metadata (mxf_metadata());
169 if (film()->encrypted()) {
170 _sound_asset->set_key (film()->key());
173 DCPOMATIC_ASSERT (film()->directory());
175 vector<dcp::Channel> active;
176 BOOST_FOREACH (int i, film()->mapped_audio_channels()) {
177 active.push_back (static_cast<dcp::Channel>(i));
180 /* Write the sound asset into the film directory so that we leave the creation
181 of the DCP directory until the last minute.
183 _sound_asset_writer = _sound_asset->start_write (
184 film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
186 film()->contains_atmos_content()
191 /** @param frame reel-relative frame */
193 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
195 shared_ptr<InfoFileHandle> handle = film()->info_file_handle(_period, false);
196 dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
197 checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
198 checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
199 checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
203 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
205 dcp::FrameInfo frame_info;
206 dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
207 checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
208 checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
210 char hash_buffer[33];
211 checked_fread (hash_buffer, 32, info->get(), info->file());
212 hash_buffer[32] = '\0';
213 frame_info.hash = hash_buffer;
219 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
223 return frame * _info_size;
225 return frame * _info_size * 2;
227 return frame * _info_size * 2 + _info_size;
229 DCPOMATIC_ASSERT (false);
232 DCPOMATIC_ASSERT (false);
236 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
238 shared_ptr<Job> job = _job.lock ();
241 job->sub (_("Checking existing image data"));
244 /* Try to open the existing asset */
245 FILE* asset_file = fopen_boost (asset, "rb");
247 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
250 LOG_GENERAL ("Opened existing asset at %1", asset.string());
253 shared_ptr<InfoFileHandle> info_file;
256 info_file = film()->info_file_handle (_period, true);
257 } catch (OpenFileError &) {
258 LOG_GENERAL_NC ("Could not open film info file");
263 /* Offset of the last dcp::FrameInfo in the info file */
264 int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
265 LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
267 Frame first_nonexistant_frame;
268 if (film()->three_d()) {
269 /* Start looking at the last left frame */
270 first_nonexistant_frame = n / 2;
272 first_nonexistant_frame = n;
275 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
276 --first_nonexistant_frame;
279 if (!film()->three_d() && first_nonexistant_frame > 0) {
280 /* If we are doing 3D we might have found a good L frame with no R, so only
281 do this if we're in 2D and we've just found a good B(oth) frame.
283 ++first_nonexistant_frame;
286 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
290 return first_nonexistant_frame;
294 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
296 if (!_picture_asset_writer) {
297 /* We're not writing any data */
301 dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data(), encoded->size());
302 write_frame_info (frame, eyes, fin);
303 _last_written[eyes] = encoded;
308 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
311 _atmos_asset = metadata.create (dcp::Fraction(film()->video_frame_rate(), 1));
312 if (film()->encrypted()) {
313 _atmos_asset->set_key(film()->key());
315 _atmos_asset_writer = _atmos_asset->start_write (
316 film()->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
319 _atmos_asset_writer->write (atmos);
324 ReelWriter::fake_write (int size)
326 if (!_picture_asset_writer) {
327 /* We're not writing any data */
331 _picture_asset_writer->fake_write (size);
335 ReelWriter::repeat_write (Frame frame, Eyes eyes)
337 if (!_picture_asset_writer) {
338 /* We're not writing any data */
342 dcp::FrameInfo fin = _picture_asset_writer->write (
343 _last_written[eyes]->data(),
344 _last_written[eyes]->size()
346 write_frame_info (frame, eyes, fin);
350 ReelWriter::finish ()
352 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
353 /* Nothing was written to the picture asset */
354 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
355 _picture_asset.reset ();
358 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
359 /* Nothing was written to the sound asset */
360 _sound_asset.reset ();
363 /* Hard-link any video asset file into the DCP */
364 if (_picture_asset) {
365 DCPOMATIC_ASSERT (_picture_asset->file());
366 boost::filesystem::path video_from = _picture_asset->file().get();
367 boost::filesystem::path video_to;
368 video_to /= film()->dir(film()->dcp_name());
369 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
370 /* There may be an existing "to" file if we are recreating a DCP in the same place without
373 boost::system::error_code ec;
374 boost::filesystem::remove (video_to, ec);
376 boost::filesystem::create_hard_link (video_from, video_to, ec);
378 LOG_WARNING_NC ("Hard-link failed; copying instead");
379 shared_ptr<Job> job = _job.lock ();
381 job->sub (_("Copying video file into DCP"));
383 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
384 } catch (exception& e) {
385 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
386 throw FileError (e.what(), video_from);
389 boost::filesystem::copy_file (video_from, video_to, ec);
391 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
392 throw FileError (ec.message(), video_from);
397 _picture_asset->set_file (video_to);
400 /* Move the audio asset into the DCP */
402 boost::filesystem::path audio_to;
403 audio_to /= film()->dir(film()->dcp_name ());
404 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
407 boost::system::error_code ec;
408 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
411 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
415 _sound_asset->set_file (audio_to);
419 _atmos_asset_writer->finalize ();
420 boost::filesystem::path atmos_to;
421 atmos_to /= film()->dir(film()->dcp_name());
422 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
425 boost::system::error_code ec;
426 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
429 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
433 _atmos_asset->set_file (atmos_to);
440 shared_ptr<dcp::SubtitleAsset> asset,
441 int64_t picture_duration,
442 shared_ptr<dcp::Reel> reel,
443 list<ReferencedReelAsset> const & refs,
444 list<shared_ptr<Font> > const & fonts,
445 shared_ptr<const Film> film,
449 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
451 shared_ptr<T> reel_asset;
454 /* Add the font to the subtitle content */
455 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
456 asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
459 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
460 boost::filesystem::path directory = film->dir (film->dcp_name ()) / asset->id ();
461 boost::filesystem::create_directories (directory);
462 asset->write (directory / ("sub_" + asset->id() + ".xml"));
464 /* All our assets should be the same length; use the picture asset length here
465 as a reference to set the subtitle one. We'll use the duration rather than
466 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
467 just interested in its presentation length.
469 dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
472 film->dir(film->dcp_name()) / ("sub_" + asset->id() + ".mxf")
479 dcp::Fraction (film->video_frame_rate(), 1),
485 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
486 BOOST_FOREACH (ReferencedReelAsset j, refs) {
487 shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
488 if (k && j.period == period) {
490 /* If we have a hash for this asset in the CPL, assume that it is correct */
492 k->asset_ref()->set_hash (k->hash().get());
499 if (reel_asset->actual_duration() != period_duration) {
500 throw ProgrammingError (
502 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
505 reel->add (reel_asset);
512 shared_ptr<dcp::ReelPictureAsset>
513 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
515 shared_ptr<dcp::ReelPictureAsset> reel_asset;
517 if (_picture_asset) {
518 /* We have made a picture asset of our own. Put it into the reel */
519 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
521 reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
524 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
526 reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
529 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
530 /* We don't have a picture asset of our own; hopefully we have one to reference */
531 BOOST_FOREACH (ReferencedReelAsset j, refs) {
532 shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
534 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
536 if (k && j.period == _period) {
542 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
544 DCPOMATIC_ASSERT (reel_asset);
545 if (reel_asset->duration() != period_duration) {
546 throw ProgrammingError (
548 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
551 reel->add (reel_asset);
553 /* If we have a hash for this asset in the CPL, assume that it is correct */
554 if (reel_asset->hash()) {
555 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
563 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
565 shared_ptr<dcp::ReelSoundAsset> reel_asset;
568 /* We have made a sound asset of our own. Put it into the reel */
569 reel_asset.reset (new dcp::ReelSoundAsset(_sound_asset, 0));
571 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
572 /* We don't have a sound asset of our own; hopefully we have one to reference */
573 BOOST_FOREACH (ReferencedReelAsset j, refs) {
574 shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
576 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
578 if (k && j.period == _period) {
580 /* If we have a hash for this asset in the CPL, assume that it is correct */
582 k->asset_ref()->set_hash (k->hash().get());
588 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
590 DCPOMATIC_ASSERT (reel_asset);
591 if (reel_asset->actual_duration() != period_duration) {
593 "Reel sound asset has length %1 but reel period is %2",
594 reel_asset->actual_duration(),
597 if (reel_asset->actual_duration() != period_duration) {
598 throw ProgrammingError (
600 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
605 reel->add (reel_asset);
610 ReelWriter::create_reel_text (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const& fonts, int64_t duration) const
612 shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (_subtitle_asset, duration, reel, refs, fonts, film(), _period);
613 if (subtitle && !film()->subtitle_languages().empty()) {
614 subtitle->set_language (film()->subtitle_languages().front());
617 for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
618 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
619 i->second, duration, reel, refs, fonts, film(), _period
622 a->set_annotation_text (i->first.name);
623 if (!i->first.language.empty()) {
624 a->set_language (dcp::LanguageTag(i->first.language));
633 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
635 Film::Markers markers = film()->markers();
636 film()->add_ffoc_lfoc(markers);
637 Film::Markers reel_markers;
638 for (Film::Markers::const_iterator i = markers.begin(); i != markers.end(); ++i) {
639 if (_period.contains(i->second)) {
640 reel_markers[i->first] = i->second;
644 if (!reel_markers.empty ()) {
645 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(film()->video_frame_rate(), 1), 0));
646 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
648 DCPTime relative = i->second - _period.from;
649 relative.split (film()->video_frame_rate(), h, m, s, f);
650 ma->set (i->first, dcp::Time(h, m, s, f, film()->video_frame_rate()));
657 shared_ptr<dcp::Reel>
658 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
660 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
662 shared_ptr<dcp::Reel> reel (new dcp::Reel());
663 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
664 create_reel_sound (reel, refs);
665 create_reel_text (reel, refs, fonts, reel_picture_asset->actual_duration());
666 create_reel_markers (reel);
669 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
676 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
678 if (_picture_asset) {
679 _picture_asset->hash (set_progress);
683 _sound_asset->hash (set_progress);
687 _atmos_asset->hash (set_progress);
692 ReelWriter::start () const
694 return _period.from.frames_floor (film()->video_frame_rate());
699 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
701 if (!_sound_asset_writer) {
705 DCPOMATIC_ASSERT (audio);
706 _sound_asset_writer->write (audio->data(), audio->frames());
710 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
712 shared_ptr<dcp::SubtitleAsset> asset;
715 case TEXT_OPEN_SUBTITLE:
716 asset = _subtitle_asset;
718 case TEXT_CLOSED_CAPTION:
719 DCPOMATIC_ASSERT (track);
720 asset = _closed_caption_assets[*track];
723 DCPOMATIC_ASSERT (false);
727 vector<dcp::LanguageTag> lang = film()->subtitle_languages();
728 if (film()->interop()) {
729 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
730 s->set_movie_title (film()->name());
731 if (type == TEXT_OPEN_SUBTITLE) {
732 s->set_language (lang.empty() ? "Unknown" : lang.front().to_string());
733 } else if (!track->language.empty()) {
734 s->set_language (track->language);
736 s->set_reel_number (raw_convert<string> (_reel_index + 1));
739 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
740 s->set_content_title_text (film()->name());
741 s->set_metadata (mxf_metadata());
742 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
743 s->set_language (lang.front());
744 } else if (track && !track->language.empty()) {
745 s->set_language (dcp::LanguageTag(track->language));
747 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
748 s->set_reel_number (_reel_index + 1);
749 s->set_time_code_rate (film()->video_frame_rate());
750 s->set_start_time (dcp::Time ());
751 if (film()->encrypted()) {
752 s->set_key (film()->key());
759 case TEXT_OPEN_SUBTITLE:
760 _subtitle_asset = asset;
762 case TEXT_CLOSED_CAPTION:
763 DCPOMATIC_ASSERT (track);
764 _closed_caption_assets[*track] = asset;
767 DCPOMATIC_ASSERT (false);
770 BOOST_FOREACH (StringText i, subs.string) {
771 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
772 i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
773 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
774 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
777 BOOST_FOREACH (BitmapText i, subs.bitmap) {
779 shared_ptr<dcp::Subtitle>(
780 new dcp::SubtitleImage(
782 dcp::Time(period.from.seconds() - _period.from.seconds(), film()->video_frame_rate()),
783 dcp::Time(period.to.seconds() - _period.from.seconds(), film()->video_frame_rate()),
784 i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
785 dcp::Time(), dcp::Time()
793 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
795 LOG_GENERAL ("Checking existing picture frame %1", frame);
797 /* Read the data from the info file; for 3D we just check the left
798 frames until we find a good one.
800 dcp::FrameInfo const info = read_frame_info (info_file, frame, film()->three_d() ? EYES_LEFT : EYES_BOTH);
804 /* Read the data from the asset and hash it */
805 dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
806 ArrayData data (info.size);
807 size_t const read = fread (data.data(), 1, data.size(), asset_file);
808 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
809 if (read != static_cast<size_t> (data.size ())) {
810 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
814 digester.add (data.data(), data.size());
815 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
816 if (digester.get() != info.hash) {
817 LOG_GENERAL ("Existing frame %1 failed hash check", frame);