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;
68 using dcp::raw_convert;
69 using namespace dcpomatic;
71 int const ReelWriter::_info_size = 48;
73 static dcp::MXFMetadata
76 dcp::MXFMetadata meta;
77 Config* config = Config::instance();
78 if (!config->dcp_company_name().empty()) {
79 meta.company_name = config->dcp_company_name ();
81 if (!config->dcp_product_name().empty()) {
82 meta.product_name = config->dcp_product_name ();
84 if (!config->dcp_product_version().empty()) {
85 meta.product_version = config->dcp_product_version ();
90 /** @param job Related job, or 0 */
91 ReelWriter::ReelWriter (
92 shared_ptr<const Film> film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, optional<string> content_summary
96 , _reel_index (reel_index)
97 , _reel_count (reel_count)
98 , _content_summary (content_summary)
101 /* Create or find our picture asset in a subdirectory, named
102 according to those film's parameters which affect the video
103 output. We will hard-link it into the DCP later.
106 dcp::Standard const standard = _film->interop() ? dcp::INTEROP : dcp::SMPTE;
108 boost::filesystem::path const asset =
109 _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period);
111 _first_nonexistant_frame = check_existing_picture_asset (asset);
113 if (_first_nonexistant_frame < period.duration().frames_round(_film->video_frame_rate())) {
114 /* We do not have a complete picture asset. If there is an
115 existing asset, break any hard links to it as we are about
116 to change its contents (if only by changing the IDs); see
119 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
121 job->sub (_("Copying old video file"));
122 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
124 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
126 boost::filesystem::remove (asset);
127 boost::filesystem::rename (asset.string() + ".tmp", asset);
131 if (_film->three_d ()) {
132 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
134 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
137 _picture_asset->set_size (_film->frame_size());
138 _picture_asset->set_metadata (mxf_metadata());
140 if (_film->encrypted ()) {
141 _picture_asset->set_key (_film->key());
142 _picture_asset->set_context_id (_film->context_id());
145 _picture_asset->set_file (asset);
146 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
148 /* We already have a complete picture asset that we can just re-use */
149 /* XXX: what about if the encryption key changes? */
150 if (_film->three_d ()) {
151 _picture_asset.reset (new dcp::StereoPictureAsset(asset));
153 _picture_asset.reset (new dcp::MonoPictureAsset(asset));
157 if (_film->audio_channels ()) {
159 new dcp::SoundAsset (dcp::Fraction(_film->video_frame_rate(), 1), _film->audio_frame_rate(), _film->audio_channels(), _film->audio_language(), standard)
162 _sound_asset->set_metadata (mxf_metadata());
164 if (_film->encrypted ()) {
165 _sound_asset->set_key (_film->key ());
168 DCPOMATIC_ASSERT (_film->directory());
170 vector<dcp::Channel> active;
171 BOOST_FOREACH (int i, _film->mapped_audio_channels()) {
172 active.push_back (static_cast<dcp::Channel>(i));
175 /* Write the sound asset into the film directory so that we leave the creation
176 of the DCP directory until the last minute.
178 _sound_asset_writer = _sound_asset->start_write (
179 _film->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
181 _film->contains_atmos_content()
186 /** @param frame reel-relative frame */
188 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
190 shared_ptr<InfoFileHandle> handle = _film->info_file_handle(_period, false);
191 dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
192 checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
193 checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
194 checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
198 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
200 dcp::FrameInfo frame_info;
201 dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
202 checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
203 checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
205 char hash_buffer[33];
206 checked_fread (hash_buffer, 32, info->get(), info->file());
207 hash_buffer[32] = '\0';
208 frame_info.hash = hash_buffer;
214 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
218 return frame * _info_size;
220 return frame * _info_size * 2;
222 return frame * _info_size * 2 + _info_size;
224 DCPOMATIC_ASSERT (false);
227 DCPOMATIC_ASSERT (false);
231 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
233 shared_ptr<Job> job = _job.lock ();
236 job->sub (_("Checking existing image data"));
239 /* Try to open the existing asset */
240 FILE* asset_file = fopen_boost (asset, "rb");
242 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
245 LOG_GENERAL ("Opened existing asset at %1", asset.string());
248 shared_ptr<InfoFileHandle> info_file;
251 info_file = _film->info_file_handle (_period, true);
252 } catch (OpenFileError &) {
253 LOG_GENERAL_NC ("Could not open film info file");
258 /* Offset of the last dcp::FrameInfo in the info file */
259 int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
260 LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
262 Frame first_nonexistant_frame;
263 if (_film->three_d ()) {
264 /* Start looking at the last left frame */
265 first_nonexistant_frame = n / 2;
267 first_nonexistant_frame = n;
270 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
271 --first_nonexistant_frame;
274 if (!_film->three_d() && first_nonexistant_frame > 0) {
275 /* If we are doing 3D we might have found a good L frame with no R, so only
276 do this if we're in 2D and we've just found a good B(oth) frame.
278 ++first_nonexistant_frame;
281 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
285 return first_nonexistant_frame;
289 ReelWriter::write (optional<Data> encoded, Frame frame, Eyes eyes)
291 if (!_picture_asset_writer) {
292 /* We're not writing any data */
296 dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data().get (), encoded->size());
297 write_frame_info (frame, eyes, fin);
298 _last_written[eyes] = encoded;
303 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
306 _atmos_asset = metadata.create (dcp::Fraction(_film->video_frame_rate(), 1));
307 if (_film->encrypted()) {
308 _atmos_asset->set_key(_film->key());
310 _atmos_asset_writer = _atmos_asset->start_write (
311 _film->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
314 _atmos_asset_writer->write (atmos);
319 ReelWriter::fake_write (int size)
321 if (!_picture_asset_writer) {
322 /* We're not writing any data */
326 _picture_asset_writer->fake_write (size);
330 ReelWriter::repeat_write (Frame frame, Eyes eyes)
332 if (!_picture_asset_writer) {
333 /* We're not writing any data */
337 dcp::FrameInfo fin = _picture_asset_writer->write (
338 _last_written[eyes]->data().get(),
339 _last_written[eyes]->size()
341 write_frame_info (frame, eyes, fin);
345 ReelWriter::finish ()
347 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
348 /* Nothing was written to the picture asset */
349 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
350 _picture_asset.reset ();
353 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
354 /* Nothing was written to the sound asset */
355 _sound_asset.reset ();
358 /* Hard-link any video asset file into the DCP */
359 if (_picture_asset) {
360 DCPOMATIC_ASSERT (_picture_asset->file());
361 boost::filesystem::path video_from = _picture_asset->file().get();
362 boost::filesystem::path video_to;
363 video_to /= _film->dir (_film->dcp_name());
364 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
365 /* There may be an existing "to" file if we are recreating a DCP in the same place without
368 boost::system::error_code ec;
369 boost::filesystem::remove (video_to, ec);
371 boost::filesystem::create_hard_link (video_from, video_to, ec);
373 LOG_WARNING_NC ("Hard-link failed; copying instead");
374 shared_ptr<Job> job = _job.lock ();
376 job->sub (_("Copying video file into DCP"));
378 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
379 } catch (exception& e) {
380 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
381 throw FileError (e.what(), video_from);
384 boost::filesystem::copy_file (video_from, video_to, ec);
386 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
387 throw FileError (ec.message(), video_from);
392 _picture_asset->set_file (video_to);
395 /* Move the audio asset into the DCP */
397 boost::filesystem::path audio_to;
398 audio_to /= _film->dir (_film->dcp_name ());
399 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
402 boost::system::error_code ec;
403 boost::filesystem::rename (_film->file (aaf), audio_to, ec);
406 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
410 _sound_asset->set_file (audio_to);
414 _atmos_asset_writer->finalize ();
415 boost::filesystem::path atmos_to;
416 atmos_to /= _film->dir (_film->dcp_name());
417 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
420 boost::system::error_code ec;
421 boost::filesystem::rename (_film->file(aaf), atmos_to, ec);
424 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
428 _atmos_asset->set_file (atmos_to);
435 shared_ptr<dcp::SubtitleAsset> asset,
436 int64_t picture_duration,
437 shared_ptr<dcp::Reel> reel,
438 list<ReferencedReelAsset> const & refs,
439 list<shared_ptr<Font> > const & fonts,
440 shared_ptr<const Film> film,
444 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
446 shared_ptr<T> reel_asset;
449 /* Add the font to the subtitle content */
450 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
451 asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
454 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
455 boost::filesystem::path directory = film->dir (film->dcp_name ()) / asset->id ();
456 boost::filesystem::create_directories (directory);
457 asset->write (directory / ("sub_" + asset->id() + ".xml"));
459 /* All our assets should be the same length; use the picture asset length here
460 as a reference to set the subtitle one. We'll use the duration rather than
461 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
462 just interested in its presentation length.
464 dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
467 film->dir(film->dcp_name()) / ("sub_" + asset->id() + ".mxf")
474 dcp::Fraction (film->video_frame_rate(), 1),
480 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
481 BOOST_FOREACH (ReferencedReelAsset j, refs) {
482 shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
483 if (k && j.period == period) {
485 /* If we have a hash for this asset in the CPL, assume that it is correct */
487 k->asset_ref()->set_hash (k->hash().get());
494 if (reel_asset->actual_duration() != period_duration) {
495 throw ProgrammingError (
497 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
500 reel->add (reel_asset);
506 shared_ptr<dcp::Reel>
507 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
509 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
511 shared_ptr<dcp::Reel> reel (new dcp::Reel ());
513 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset;
515 if (_picture_asset) {
516 /* We have made a picture asset of our own. Put it into the reel */
517 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
519 reel_picture_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
522 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
524 reel_picture_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
527 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
528 /* We don't have a picture asset of our own; hopefully we have one to reference */
529 BOOST_FOREACH (ReferencedReelAsset j, refs) {
530 shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
532 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
534 if (k && j.period == _period) {
535 reel_picture_asset = k;
540 Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
542 DCPOMATIC_ASSERT (reel_picture_asset);
543 if (reel_picture_asset->duration() != period_duration) {
544 throw ProgrammingError (
546 String::compose ("%1 vs %2", reel_picture_asset->actual_duration(), period_duration)
549 reel->add (reel_picture_asset);
551 /* If we have a hash for this asset in the CPL, assume that it is correct */
552 if (reel_picture_asset->hash()) {
553 reel_picture_asset->asset_ref()->set_hash (reel_picture_asset->hash().get());
556 shared_ptr<dcp::ReelSoundAsset> reel_sound_asset;
559 /* We have made a sound asset of our own. Put it into the reel */
560 reel_sound_asset.reset (new dcp::ReelSoundAsset (_sound_asset, 0));
562 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
563 /* We don't have a sound asset of our own; hopefully we have one to reference */
564 BOOST_FOREACH (ReferencedReelAsset j, refs) {
565 shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
567 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
569 if (k && j.period == _period) {
570 reel_sound_asset = k;
571 /* If we have a hash for this asset in the CPL, assume that it is correct */
573 k->asset_ref()->set_hash (k->hash().get());
579 DCPOMATIC_ASSERT (reel_sound_asset);
580 if (reel_sound_asset->actual_duration() != period_duration) {
582 "Reel sound asset has length %1 but reel period is %2",
583 reel_sound_asset->actual_duration(),
586 if (reel_sound_asset->actual_duration() != period_duration) {
587 throw ProgrammingError (
589 String::compose ("%1 vs %2", reel_sound_asset->actual_duration(), period_duration)
594 reel->add (reel_sound_asset);
596 maybe_add_text<dcp::ReelSubtitleAsset> (_subtitle_asset, reel_picture_asset->actual_duration(), reel, refs, fonts, _film, _period);
597 for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
598 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
599 i->second, reel_picture_asset->actual_duration(), reel, refs, fonts, _film, _period
601 a->set_annotation_text (i->first.name);
602 a->set_language (i->first.language);
605 map<dcp::Marker, DCPTime> markers = _film->markers ();
606 map<dcp::Marker, DCPTime> reel_markers;
607 for (map<dcp::Marker, DCPTime>::const_iterator i = markers.begin(); i != markers.end(); ++i) {
608 if (_period.contains(i->second)) {
609 reel_markers[i->first] = i->second;
613 if (!reel_markers.empty ()) {
614 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(_film->video_frame_rate(), 1), 0));
615 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
617 DCPTime relative = i->second - _period.from;
618 relative.split (_film->video_frame_rate(), h, m, s, f);
619 ma->set (i->first, dcp::Time(h, m, s, f, _film->video_frame_rate()));
625 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
632 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
634 if (_picture_asset) {
635 _picture_asset->hash (set_progress);
639 _sound_asset->hash (set_progress);
643 _atmos_asset->hash (set_progress);
648 ReelWriter::start () const
650 return _period.from.frames_floor (_film->video_frame_rate());
655 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
657 if (!_sound_asset_writer) {
661 DCPOMATIC_ASSERT (audio);
662 _sound_asset_writer->write (audio->data(), audio->frames());
666 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
668 shared_ptr<dcp::SubtitleAsset> asset;
671 case TEXT_OPEN_SUBTITLE:
672 asset = _subtitle_asset;
674 case TEXT_CLOSED_CAPTION:
675 DCPOMATIC_ASSERT (track);
676 asset = _closed_caption_assets[*track];
679 DCPOMATIC_ASSERT (false);
683 string lang = _film->subtitle_language ();
684 if (_film->interop ()) {
685 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
686 s->set_movie_title (_film->name ());
687 if (type == TEXT_OPEN_SUBTITLE) {
688 s->set_language (lang.empty() ? "Unknown" : lang);
690 s->set_language (track->language);
692 s->set_reel_number (raw_convert<string> (_reel_index + 1));
695 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
696 s->set_content_title_text (_film->name ());
697 s->set_metadata (mxf_metadata());
698 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
699 s->set_language (lang);
701 s->set_language (track->language);
703 s->set_edit_rate (dcp::Fraction (_film->video_frame_rate (), 1));
704 s->set_reel_number (_reel_index + 1);
705 s->set_time_code_rate (_film->video_frame_rate ());
706 s->set_start_time (dcp::Time ());
707 if (_film->encrypted ()) {
708 s->set_key (_film->key ());
715 case TEXT_OPEN_SUBTITLE:
716 _subtitle_asset = asset;
718 case TEXT_CLOSED_CAPTION:
719 DCPOMATIC_ASSERT (track);
720 _closed_caption_assets[*track] = asset;
723 DCPOMATIC_ASSERT (false);
726 BOOST_FOREACH (StringText i, subs.string) {
727 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
728 i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
729 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
730 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
733 BOOST_FOREACH (BitmapText i, subs.bitmap) {
735 shared_ptr<dcp::Subtitle>(
736 new dcp::SubtitleImage(
738 dcp::Time(period.from.seconds() - _period.from.seconds(), _film->video_frame_rate()),
739 dcp::Time(period.to.seconds() - _period.from.seconds(), _film->video_frame_rate()),
740 i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
741 dcp::Time(), dcp::Time()
749 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
751 LOG_GENERAL ("Checking existing picture frame %1", frame);
753 /* Read the data from the info file; for 3D we just check the left
754 frames until we find a good one.
756 dcp::FrameInfo const info = read_frame_info (info_file, frame, _film->three_d () ? EYES_LEFT : EYES_BOTH);
760 /* Read the data from the asset and hash it */
761 dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
762 Data data (info.size);
763 size_t const read = fread (data.data().get(), 1, data.size(), asset_file);
764 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
765 if (read != static_cast<size_t> (data.size ())) {
766 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
770 digester.add (data.data().get(), data.size());
771 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
772 if (digester.get() != info.hash) {
773 LOG_GENERAL ("Existing frame %1 failed hash check", frame);