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;
72 using dcp::raw_convert;
73 using namespace dcpomatic;
75 int const ReelWriter::_info_size = 48;
77 static dcp::MXFMetadata
80 dcp::MXFMetadata meta;
81 Config* config = Config::instance();
82 if (!config->dcp_company_name().empty()) {
83 meta.company_name = config->dcp_company_name ();
85 if (!config->dcp_product_name().empty()) {
86 meta.product_name = config->dcp_product_name ();
88 if (!config->dcp_product_version().empty()) {
89 meta.product_version = config->dcp_product_version ();
94 /** @param job Related job, or 0 */
95 ReelWriter::ReelWriter (
96 shared_ptr<const Film> film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count
100 , _reel_index (reel_index)
101 , _reel_count (reel_count)
102 , _content_summary (film->content_summary(period))
105 /* Create or find our picture asset in a subdirectory, named
106 according to those film's parameters which affect the video
107 output. We will hard-link it into the DCP later.
110 dcp::Standard const standard = _film->interop() ? dcp::INTEROP : dcp::SMPTE;
112 boost::filesystem::path const asset =
113 _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period);
115 _first_nonexistant_frame = check_existing_picture_asset (asset);
117 if (_first_nonexistant_frame < period.duration().frames_round(_film->video_frame_rate())) {
118 /* We do not have a complete picture asset. If there is an
119 existing asset, break any hard links to it as we are about
120 to change its contents (if only by changing the IDs); see
123 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
125 job->sub (_("Copying old video file"));
126 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
128 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
130 boost::filesystem::remove (asset);
131 boost::filesystem::rename (asset.string() + ".tmp", asset);
135 if (_film->three_d ()) {
136 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
138 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
141 _picture_asset->set_size (_film->frame_size());
142 _picture_asset->set_metadata (mxf_metadata());
144 if (_film->encrypted ()) {
145 _picture_asset->set_key (_film->key());
146 _picture_asset->set_context_id (_film->context_id());
149 _picture_asset->set_file (asset);
150 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
152 /* We already have a complete picture asset that we can just re-use */
153 /* XXX: what about if the encryption key changes? */
154 if (_film->three_d ()) {
155 _picture_asset.reset (new dcp::StereoPictureAsset(asset));
157 _picture_asset.reset (new dcp::MonoPictureAsset(asset));
161 if (_film->audio_channels ()) {
163 new dcp::SoundAsset (dcp::Fraction(_film->video_frame_rate(), 1), _film->audio_frame_rate(), _film->audio_channels(), _film->audio_language(), standard)
166 _sound_asset->set_metadata (mxf_metadata());
168 if (_film->encrypted ()) {
169 _sound_asset->set_key (_film->key ());
172 DCPOMATIC_ASSERT (_film->directory());
174 vector<dcp::Channel> active;
175 BOOST_FOREACH (int i, _film->mapped_audio_channels()) {
176 active.push_back (static_cast<dcp::Channel>(i));
179 /* Write the sound asset into the film directory so that we leave the creation
180 of the DCP directory until the last minute.
182 _sound_asset_writer = _sound_asset->start_write (
183 _film->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
185 _film->contains_atmos_content()
190 /** @param frame reel-relative frame */
192 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
194 shared_ptr<InfoFileHandle> handle = _film->info_file_handle(_period, false);
195 dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
196 checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
197 checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
198 checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
202 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
204 dcp::FrameInfo frame_info;
205 dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
206 checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
207 checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
209 char hash_buffer[33];
210 checked_fread (hash_buffer, 32, info->get(), info->file());
211 hash_buffer[32] = '\0';
212 frame_info.hash = hash_buffer;
218 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
222 return frame * _info_size;
224 return frame * _info_size * 2;
226 return frame * _info_size * 2 + _info_size;
228 DCPOMATIC_ASSERT (false);
231 DCPOMATIC_ASSERT (false);
235 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
237 shared_ptr<Job> job = _job.lock ();
240 job->sub (_("Checking existing image data"));
243 /* Try to open the existing asset */
244 FILE* asset_file = fopen_boost (asset, "rb");
246 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
249 LOG_GENERAL ("Opened existing asset at %1", asset.string());
252 shared_ptr<InfoFileHandle> info_file;
255 info_file = _film->info_file_handle (_period, true);
256 } catch (OpenFileError &) {
257 LOG_GENERAL_NC ("Could not open film info file");
262 /* Offset of the last dcp::FrameInfo in the info file */
263 int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
264 LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
266 Frame first_nonexistant_frame;
267 if (_film->three_d ()) {
268 /* Start looking at the last left frame */
269 first_nonexistant_frame = n / 2;
271 first_nonexistant_frame = n;
274 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
275 --first_nonexistant_frame;
278 if (!_film->three_d() && first_nonexistant_frame > 0) {
279 /* If we are doing 3D we might have found a good L frame with no R, so only
280 do this if we're in 2D and we've just found a good B(oth) frame.
282 ++first_nonexistant_frame;
285 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
289 return first_nonexistant_frame;
293 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
295 if (!_picture_asset_writer) {
296 /* We're not writing any data */
300 dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data(), encoded->size());
301 write_frame_info (frame, eyes, fin);
302 _last_written[eyes] = encoded;
307 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
310 _atmos_asset = metadata.create (dcp::Fraction(_film->video_frame_rate(), 1));
311 if (_film->encrypted()) {
312 _atmos_asset->set_key(_film->key());
314 _atmos_asset_writer = _atmos_asset->start_write (
315 _film->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
318 _atmos_asset_writer->write (atmos);
323 ReelWriter::fake_write (int size)
325 if (!_picture_asset_writer) {
326 /* We're not writing any data */
330 _picture_asset_writer->fake_write (size);
334 ReelWriter::repeat_write (Frame frame, Eyes eyes)
336 if (!_picture_asset_writer) {
337 /* We're not writing any data */
341 dcp::FrameInfo fin = _picture_asset_writer->write (
342 _last_written[eyes]->data(),
343 _last_written[eyes]->size()
345 write_frame_info (frame, eyes, fin);
349 ReelWriter::finish ()
351 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
352 /* Nothing was written to the picture asset */
353 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
354 _picture_asset.reset ();
357 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
358 /* Nothing was written to the sound asset */
359 _sound_asset.reset ();
362 /* Hard-link any video asset file into the DCP */
363 if (_picture_asset) {
364 DCPOMATIC_ASSERT (_picture_asset->file());
365 boost::filesystem::path video_from = _picture_asset->file().get();
366 boost::filesystem::path video_to;
367 video_to /= _film->dir (_film->dcp_name());
368 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
369 /* There may be an existing "to" file if we are recreating a DCP in the same place without
372 boost::system::error_code ec;
373 boost::filesystem::remove (video_to, ec);
375 boost::filesystem::create_hard_link (video_from, video_to, ec);
377 LOG_WARNING_NC ("Hard-link failed; copying instead");
378 shared_ptr<Job> job = _job.lock ();
380 job->sub (_("Copying video file into DCP"));
382 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
383 } catch (exception& e) {
384 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
385 throw FileError (e.what(), video_from);
388 boost::filesystem::copy_file (video_from, video_to, ec);
390 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
391 throw FileError (ec.message(), video_from);
396 _picture_asset->set_file (video_to);
399 /* Move the audio asset into the DCP */
401 boost::filesystem::path audio_to;
402 audio_to /= _film->dir (_film->dcp_name ());
403 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
406 boost::system::error_code ec;
407 boost::filesystem::rename (_film->file (aaf), audio_to, ec);
410 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
414 _sound_asset->set_file (audio_to);
418 _atmos_asset_writer->finalize ();
419 boost::filesystem::path atmos_to;
420 atmos_to /= _film->dir (_film->dcp_name());
421 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
424 boost::system::error_code ec;
425 boost::filesystem::rename (_film->file(aaf), atmos_to, ec);
428 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
432 _atmos_asset->set_file (atmos_to);
439 shared_ptr<dcp::SubtitleAsset> asset,
440 int64_t picture_duration,
441 shared_ptr<dcp::Reel> reel,
442 list<ReferencedReelAsset> const & refs,
443 list<shared_ptr<Font> > const & fonts,
444 shared_ptr<const Film> film,
448 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
450 shared_ptr<T> reel_asset;
453 /* Add the font to the subtitle content */
454 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
455 asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
458 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
459 boost::filesystem::path directory = film->dir (film->dcp_name ()) / asset->id ();
460 boost::filesystem::create_directories (directory);
461 asset->write (directory / ("sub_" + asset->id() + ".xml"));
463 /* All our assets should be the same length; use the picture asset length here
464 as a reference to set the subtitle one. We'll use the duration rather than
465 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
466 just interested in its presentation length.
468 dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
471 film->dir(film->dcp_name()) / ("sub_" + asset->id() + ".mxf")
478 dcp::Fraction (film->video_frame_rate(), 1),
484 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
485 BOOST_FOREACH (ReferencedReelAsset j, refs) {
486 shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
487 if (k && j.period == period) {
489 /* If we have a hash for this asset in the CPL, assume that it is correct */
491 k->asset_ref()->set_hash (k->hash().get());
498 if (reel_asset->actual_duration() != period_duration) {
499 throw ProgrammingError (
501 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
504 reel->add (reel_asset);
511 shared_ptr<dcp::ReelPictureAsset>
512 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
514 shared_ptr<dcp::ReelPictureAsset> reel_asset;
516 if (_picture_asset) {
517 /* We have made a picture asset of our own. Put it into the reel */
518 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
520 reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
523 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
525 reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
528 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
529 /* We don't have a picture asset of our own; hopefully we have one to reference */
530 BOOST_FOREACH (ReferencedReelAsset j, refs) {
531 shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
533 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
535 if (k && j.period == _period) {
541 Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
543 DCPOMATIC_ASSERT (reel_asset);
544 if (reel_asset->duration() != period_duration) {
545 throw ProgrammingError (
547 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
550 reel->add (reel_asset);
552 /* If we have a hash for this asset in the CPL, assume that it is correct */
553 if (reel_asset->hash()) {
554 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
562 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
564 shared_ptr<dcp::ReelSoundAsset> reel_asset;
567 /* We have made a sound asset of our own. Put it into the reel */
568 reel_asset.reset (new dcp::ReelSoundAsset(_sound_asset, 0));
570 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
571 /* We don't have a sound asset of our own; hopefully we have one to reference */
572 BOOST_FOREACH (ReferencedReelAsset j, refs) {
573 shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
575 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
577 if (k && j.period == _period) {
579 /* If we have a hash for this asset in the CPL, assume that it is correct */
581 k->asset_ref()->set_hash (k->hash().get());
587 Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
589 DCPOMATIC_ASSERT (reel_asset);
590 if (reel_asset->actual_duration() != period_duration) {
592 "Reel sound asset has length %1 but reel period is %2",
593 reel_asset->actual_duration(),
596 if (reel_asset->actual_duration() != period_duration) {
597 throw ProgrammingError (
599 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
604 reel->add (reel_asset);
609 ReelWriter::create_reel_text (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const& fonts, int64_t duration) const
611 shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (_subtitle_asset, duration, reel, refs, fonts, _film, _period);
612 if (subtitle && !_film->subtitle_languages().empty()) {
613 subtitle->set_language (_film->subtitle_languages().front());
616 for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
617 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
618 i->second, duration, reel, refs, fonts, _film, _period
621 a->set_annotation_text (i->first.name);
622 if (!i->first.language.empty()) {
623 a->set_language (dcp::LanguageTag(i->first.language));
632 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
634 Film::Markers markers = _film->markers ();
635 _film->add_ffoc_lfoc (markers);
636 Film::Markers reel_markers;
637 for (Film::Markers::const_iterator i = markers.begin(); i != markers.end(); ++i) {
638 if (_period.contains(i->second)) {
639 reel_markers[i->first] = i->second;
643 if (!reel_markers.empty ()) {
644 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(_film->video_frame_rate(), 1), 0));
645 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
647 DCPTime relative = i->second - _period.from;
648 relative.split (_film->video_frame_rate(), h, m, s, f);
649 ma->set (i->first, dcp::Time(h, m, s, f, _film->video_frame_rate()));
656 shared_ptr<dcp::Reel>
657 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
659 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
661 shared_ptr<dcp::Reel> reel (new dcp::Reel());
662 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
663 create_reel_sound (reel, refs);
664 create_reel_text (reel, refs, fonts, reel_picture_asset->actual_duration());
665 create_reel_markers (reel);
668 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
675 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
677 if (_picture_asset) {
678 _picture_asset->hash (set_progress);
682 _sound_asset->hash (set_progress);
686 _atmos_asset->hash (set_progress);
691 ReelWriter::start () const
693 return _period.from.frames_floor (_film->video_frame_rate());
698 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
700 if (!_sound_asset_writer) {
704 DCPOMATIC_ASSERT (audio);
705 _sound_asset_writer->write (audio->data(), audio->frames());
709 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
711 shared_ptr<dcp::SubtitleAsset> asset;
714 case TEXT_OPEN_SUBTITLE:
715 asset = _subtitle_asset;
717 case TEXT_CLOSED_CAPTION:
718 DCPOMATIC_ASSERT (track);
719 asset = _closed_caption_assets[*track];
722 DCPOMATIC_ASSERT (false);
726 vector<dcp::LanguageTag> lang = _film->subtitle_languages ();
727 if (_film->interop ()) {
728 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
729 s->set_movie_title (_film->name ());
730 if (type == TEXT_OPEN_SUBTITLE) {
731 s->set_language (lang.empty() ? "Unknown" : lang.front().to_string());
732 } else if (!track->language.empty()) {
733 s->set_language (track->language);
735 s->set_reel_number (raw_convert<string> (_reel_index + 1));
738 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
739 s->set_content_title_text (_film->name ());
740 s->set_metadata (mxf_metadata());
741 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
742 s->set_language (lang.front());
743 } else if (track && !track->language.empty()) {
744 s->set_language (dcp::LanguageTag(track->language));
746 s->set_edit_rate (dcp::Fraction (_film->video_frame_rate (), 1));
747 s->set_reel_number (_reel_index + 1);
748 s->set_time_code_rate (_film->video_frame_rate ());
749 s->set_start_time (dcp::Time ());
750 if (_film->encrypted ()) {
751 s->set_key (_film->key ());
758 case TEXT_OPEN_SUBTITLE:
759 _subtitle_asset = asset;
761 case TEXT_CLOSED_CAPTION:
762 DCPOMATIC_ASSERT (track);
763 _closed_caption_assets[*track] = asset;
766 DCPOMATIC_ASSERT (false);
769 BOOST_FOREACH (StringText i, subs.string) {
770 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
771 i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
772 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
773 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
776 BOOST_FOREACH (BitmapText i, subs.bitmap) {
778 shared_ptr<dcp::Subtitle>(
779 new dcp::SubtitleImage(
781 dcp::Time(period.from.seconds() - _period.from.seconds(), _film->video_frame_rate()),
782 dcp::Time(period.to.seconds() - _period.from.seconds(), _film->video_frame_rate()),
783 i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
784 dcp::Time(), dcp::Time()
792 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
794 LOG_GENERAL ("Checking existing picture frame %1", frame);
796 /* Read the data from the info file; for 3D we just check the left
797 frames until we find a good one.
799 dcp::FrameInfo const info = read_frame_info (info_file, frame, _film->three_d () ? EYES_LEFT : EYES_BOTH);
803 /* Read the data from the asset and hash it */
804 dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
805 ArrayData data (info.size);
806 size_t const read = fread (data.data(), 1, data.size(), asset_file);
807 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
808 if (read != static_cast<size_t> (data.size ())) {
809 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
813 digester.add (data.data(), data.size());
814 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
815 if (digester.get() != info.hash) {
816 LOG_GENERAL ("Existing frame %1 failed hash check", frame);