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;
71 using dcp::raw_convert;
72 using namespace dcpomatic;
74 int const ReelWriter::_info_size = 48;
76 static dcp::MXFMetadata
79 dcp::MXFMetadata meta;
80 Config* config = Config::instance();
81 if (!config->dcp_company_name().empty()) {
82 meta.company_name = config->dcp_company_name ();
84 if (!config->dcp_product_name().empty()) {
85 meta.product_name = config->dcp_product_name ();
87 if (!config->dcp_product_version().empty()) {
88 meta.product_version = config->dcp_product_version ();
93 /** @param job Related job, or 0 */
94 ReelWriter::ReelWriter (
95 shared_ptr<const Film> film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, optional<string> content_summary
99 , _reel_index (reel_index)
100 , _reel_count (reel_count)
101 , _content_summary (content_summary)
104 /* Create or find our picture asset in a subdirectory, named
105 according to those film's parameters which affect the video
106 output. We will hard-link it into the DCP later.
109 dcp::Standard const standard = _film->interop() ? dcp::INTEROP : dcp::SMPTE;
111 boost::filesystem::path const asset =
112 _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period);
114 _first_nonexistant_frame = check_existing_picture_asset (asset);
116 if (_first_nonexistant_frame < period.duration().frames_round(_film->video_frame_rate())) {
117 /* We do not have a complete picture asset. If there is an
118 existing asset, break any hard links to it as we are about
119 to change its contents (if only by changing the IDs); see
122 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
124 job->sub (_("Copying old video file"));
125 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
127 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
129 boost::filesystem::remove (asset);
130 boost::filesystem::rename (asset.string() + ".tmp", asset);
134 if (_film->three_d ()) {
135 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
137 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
140 _picture_asset->set_size (_film->frame_size());
141 _picture_asset->set_metadata (mxf_metadata());
143 if (_film->encrypted ()) {
144 _picture_asset->set_key (_film->key());
145 _picture_asset->set_context_id (_film->context_id());
148 _picture_asset->set_file (asset);
149 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
151 /* We already have a complete picture asset that we can just re-use */
152 /* XXX: what about if the encryption key changes? */
153 if (_film->three_d ()) {
154 _picture_asset.reset (new dcp::StereoPictureAsset(asset));
156 _picture_asset.reset (new dcp::MonoPictureAsset(asset));
160 if (_film->audio_channels ()) {
162 new dcp::SoundAsset (dcp::Fraction(_film->video_frame_rate(), 1), _film->audio_frame_rate(), _film->audio_channels(), _film->audio_language(), standard)
165 _sound_asset->set_metadata (mxf_metadata());
167 if (_film->encrypted ()) {
168 _sound_asset->set_key (_film->key ());
171 DCPOMATIC_ASSERT (_film->directory());
173 vector<dcp::Channel> active;
174 BOOST_FOREACH (int i, _film->mapped_audio_channels()) {
175 active.push_back (static_cast<dcp::Channel>(i));
178 /* Write the sound asset into the film directory so that we leave the creation
179 of the DCP directory until the last minute.
181 _sound_asset_writer = _sound_asset->start_write (
182 _film->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
184 _film->contains_atmos_content()
189 /** @param frame reel-relative frame */
191 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
193 shared_ptr<InfoFileHandle> handle = _film->info_file_handle(_period, false);
194 dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
195 checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
196 checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
197 checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
201 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
203 dcp::FrameInfo frame_info;
204 dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
205 checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
206 checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
208 char hash_buffer[33];
209 checked_fread (hash_buffer, 32, info->get(), info->file());
210 hash_buffer[32] = '\0';
211 frame_info.hash = hash_buffer;
217 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
221 return frame * _info_size;
223 return frame * _info_size * 2;
225 return frame * _info_size * 2 + _info_size;
227 DCPOMATIC_ASSERT (false);
230 DCPOMATIC_ASSERT (false);
234 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
236 shared_ptr<Job> job = _job.lock ();
239 job->sub (_("Checking existing image data"));
242 /* Try to open the existing asset */
243 FILE* asset_file = fopen_boost (asset, "rb");
245 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
248 LOG_GENERAL ("Opened existing asset at %1", asset.string());
251 shared_ptr<InfoFileHandle> info_file;
254 info_file = _film->info_file_handle (_period, true);
255 } catch (OpenFileError &) {
256 LOG_GENERAL_NC ("Could not open film info file");
261 /* Offset of the last dcp::FrameInfo in the info file */
262 int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
263 LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
265 Frame first_nonexistant_frame;
266 if (_film->three_d ()) {
267 /* Start looking at the last left frame */
268 first_nonexistant_frame = n / 2;
270 first_nonexistant_frame = n;
273 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
274 --first_nonexistant_frame;
277 if (!_film->three_d() && first_nonexistant_frame > 0) {
278 /* If we are doing 3D we might have found a good L frame with no R, so only
279 do this if we're in 2D and we've just found a good B(oth) frame.
281 ++first_nonexistant_frame;
284 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
288 return first_nonexistant_frame;
292 ReelWriter::write (optional<Data> encoded, Frame frame, Eyes eyes)
294 if (!_picture_asset_writer) {
295 /* We're not writing any data */
299 dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data().get (), encoded->size());
300 write_frame_info (frame, eyes, fin);
301 _last_written[eyes] = encoded;
306 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
309 _atmos_asset = metadata.create (dcp::Fraction(_film->video_frame_rate(), 1));
310 if (_film->encrypted()) {
311 _atmos_asset->set_key(_film->key());
313 _atmos_asset_writer = _atmos_asset->start_write (
314 _film->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
317 _atmos_asset_writer->write (atmos);
322 ReelWriter::fake_write (int size)
324 if (!_picture_asset_writer) {
325 /* We're not writing any data */
329 _picture_asset_writer->fake_write (size);
333 ReelWriter::repeat_write (Frame frame, Eyes eyes)
335 if (!_picture_asset_writer) {
336 /* We're not writing any data */
340 dcp::FrameInfo fin = _picture_asset_writer->write (
341 _last_written[eyes]->data().get(),
342 _last_written[eyes]->size()
344 write_frame_info (frame, eyes, fin);
348 ReelWriter::finish ()
350 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
351 /* Nothing was written to the picture asset */
352 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
353 _picture_asset.reset ();
356 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
357 /* Nothing was written to the sound asset */
358 _sound_asset.reset ();
361 /* Hard-link any video asset file into the DCP */
362 if (_picture_asset) {
363 DCPOMATIC_ASSERT (_picture_asset->file());
364 boost::filesystem::path video_from = _picture_asset->file().get();
365 boost::filesystem::path video_to;
366 video_to /= _film->dir (_film->dcp_name());
367 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
368 /* There may be an existing "to" file if we are recreating a DCP in the same place without
371 boost::system::error_code ec;
372 boost::filesystem::remove (video_to, ec);
374 boost::filesystem::create_hard_link (video_from, video_to, ec);
376 LOG_WARNING_NC ("Hard-link failed; copying instead");
377 shared_ptr<Job> job = _job.lock ();
379 job->sub (_("Copying video file into DCP"));
381 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
382 } catch (exception& e) {
383 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
384 throw FileError (e.what(), video_from);
387 boost::filesystem::copy_file (video_from, video_to, ec);
389 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
390 throw FileError (ec.message(), video_from);
395 _picture_asset->set_file (video_to);
398 /* Move the audio asset into the DCP */
400 boost::filesystem::path audio_to;
401 audio_to /= _film->dir (_film->dcp_name ());
402 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
405 boost::system::error_code ec;
406 boost::filesystem::rename (_film->file (aaf), audio_to, ec);
409 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
413 _sound_asset->set_file (audio_to);
417 _atmos_asset_writer->finalize ();
418 boost::filesystem::path atmos_to;
419 atmos_to /= _film->dir (_film->dcp_name());
420 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
423 boost::system::error_code ec;
424 boost::filesystem::rename (_film->file(aaf), atmos_to, ec);
427 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
431 _atmos_asset->set_file (atmos_to);
438 shared_ptr<dcp::SubtitleAsset> asset,
439 int64_t picture_duration,
440 shared_ptr<dcp::Reel> reel,
441 list<ReferencedReelAsset> const & refs,
442 list<shared_ptr<Font> > const & fonts,
443 shared_ptr<const Film> film,
447 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
449 shared_ptr<T> reel_asset;
452 /* Add the font to the subtitle content */
453 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
454 asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
457 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
458 boost::filesystem::path directory = film->dir (film->dcp_name ()) / asset->id ();
459 boost::filesystem::create_directories (directory);
460 asset->write (directory / ("sub_" + asset->id() + ".xml"));
462 /* All our assets should be the same length; use the picture asset length here
463 as a reference to set the subtitle one. We'll use the duration rather than
464 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
465 just interested in its presentation length.
467 dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
470 film->dir(film->dcp_name()) / ("sub_" + asset->id() + ".mxf")
477 dcp::Fraction (film->video_frame_rate(), 1),
483 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
484 BOOST_FOREACH (ReferencedReelAsset j, refs) {
485 shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
486 if (k && j.period == period) {
488 /* If we have a hash for this asset in the CPL, assume that it is correct */
490 k->asset_ref()->set_hash (k->hash().get());
497 if (reel_asset->actual_duration() != period_duration) {
498 throw ProgrammingError (
500 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
503 reel->add (reel_asset);
509 shared_ptr<dcp::Reel>
510 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
512 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
514 shared_ptr<dcp::Reel> reel (new dcp::Reel ());
516 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset;
518 if (_picture_asset) {
519 /* We have made a picture asset of our own. Put it into the reel */
520 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
522 reel_picture_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
525 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
527 reel_picture_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
530 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
531 /* We don't have a picture asset of our own; hopefully we have one to reference */
532 BOOST_FOREACH (ReferencedReelAsset j, refs) {
533 shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
535 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
537 if (k && j.period == _period) {
538 reel_picture_asset = k;
543 Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
545 DCPOMATIC_ASSERT (reel_picture_asset);
546 if (reel_picture_asset->duration() != period_duration) {
547 throw ProgrammingError (
549 String::compose ("%1 vs %2", reel_picture_asset->actual_duration(), period_duration)
552 reel->add (reel_picture_asset);
554 /* If we have a hash for this asset in the CPL, assume that it is correct */
555 if (reel_picture_asset->hash()) {
556 reel_picture_asset->asset_ref()->set_hash (reel_picture_asset->hash().get());
559 shared_ptr<dcp::ReelSoundAsset> reel_sound_asset;
562 /* We have made a sound asset of our own. Put it into the reel */
563 reel_sound_asset.reset (new dcp::ReelSoundAsset (_sound_asset, 0));
565 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
566 /* We don't have a sound asset of our own; hopefully we have one to reference */
567 BOOST_FOREACH (ReferencedReelAsset j, refs) {
568 shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
570 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
572 if (k && j.period == _period) {
573 reel_sound_asset = k;
574 /* If we have a hash for this asset in the CPL, assume that it is correct */
576 k->asset_ref()->set_hash (k->hash().get());
582 DCPOMATIC_ASSERT (reel_sound_asset);
583 if (reel_sound_asset->actual_duration() != period_duration) {
585 "Reel sound asset has length %1 but reel period is %2",
586 reel_sound_asset->actual_duration(),
589 if (reel_sound_asset->actual_duration() != period_duration) {
590 throw ProgrammingError (
592 String::compose ("%1 vs %2", reel_sound_asset->actual_duration(), period_duration)
597 reel->add (reel_sound_asset);
599 maybe_add_text<dcp::ReelSubtitleAsset> (_subtitle_asset, reel_picture_asset->actual_duration(), reel, refs, fonts, _film, _period);
600 for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
601 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
602 i->second, reel_picture_asset->actual_duration(), reel, refs, fonts, _film, _period
604 a->set_annotation_text (i->first.name);
605 a->set_language (i->first.language);
608 map<dcp::Marker, DCPTime> markers = _film->markers ();
609 map<dcp::Marker, DCPTime> reel_markers;
610 for (map<dcp::Marker, DCPTime>::const_iterator i = markers.begin(); i != markers.end(); ++i) {
611 if (_period.contains(i->second)) {
612 reel_markers[i->first] = i->second;
616 if (!reel_markers.empty ()) {
617 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(_film->video_frame_rate(), 1), 0));
618 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
620 DCPTime relative = i->second - _period.from;
621 relative.split (_film->video_frame_rate(), h, m, s, f);
622 ma->set (i->first, dcp::Time(h, m, s, f, _film->video_frame_rate()));
628 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
635 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
637 if (_picture_asset) {
638 _picture_asset->hash (set_progress);
642 _sound_asset->hash (set_progress);
646 _atmos_asset->hash (set_progress);
651 ReelWriter::start () const
653 return _period.from.frames_floor (_film->video_frame_rate());
658 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
660 if (!_sound_asset_writer) {
664 DCPOMATIC_ASSERT (audio);
665 _sound_asset_writer->write (audio->data(), audio->frames());
669 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
671 shared_ptr<dcp::SubtitleAsset> asset;
674 case TEXT_OPEN_SUBTITLE:
675 asset = _subtitle_asset;
677 case TEXT_CLOSED_CAPTION:
678 DCPOMATIC_ASSERT (track);
679 asset = _closed_caption_assets[*track];
682 DCPOMATIC_ASSERT (false);
686 string lang = _film->subtitle_language ();
687 if (_film->interop ()) {
688 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
689 s->set_movie_title (_film->name ());
690 if (type == TEXT_OPEN_SUBTITLE) {
691 s->set_language (lang.empty() ? "Unknown" : lang);
693 s->set_language (track->language);
695 s->set_reel_number (raw_convert<string> (_reel_index + 1));
698 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
699 s->set_content_title_text (_film->name ());
700 s->set_metadata (mxf_metadata());
701 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
702 s->set_language (lang);
704 s->set_language (track->language);
706 s->set_edit_rate (dcp::Fraction (_film->video_frame_rate (), 1));
707 s->set_reel_number (_reel_index + 1);
708 s->set_time_code_rate (_film->video_frame_rate ());
709 s->set_start_time (dcp::Time ());
710 if (_film->encrypted ()) {
711 s->set_key (_film->key ());
718 case TEXT_OPEN_SUBTITLE:
719 _subtitle_asset = asset;
721 case TEXT_CLOSED_CAPTION:
722 DCPOMATIC_ASSERT (track);
723 _closed_caption_assets[*track] = asset;
726 DCPOMATIC_ASSERT (false);
729 BOOST_FOREACH (StringText i, subs.string) {
730 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
731 i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
732 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
733 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
736 BOOST_FOREACH (BitmapText i, subs.bitmap) {
738 shared_ptr<dcp::Subtitle>(
739 new dcp::SubtitleImage(
741 dcp::Time(period.from.seconds() - _period.from.seconds(), _film->video_frame_rate()),
742 dcp::Time(period.to.seconds() - _period.from.seconds(), _film->video_frame_rate()),
743 i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
744 dcp::Time(), dcp::Time()
752 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
754 LOG_GENERAL ("Checking existing picture frame %1", frame);
756 /* Read the data from the info file; for 3D we just check the left
757 frames until we find a good one.
759 dcp::FrameInfo const info = read_frame_info (info_file, frame, _film->three_d () ? EYES_LEFT : EYES_BOTH);
763 /* Read the data from the asset and hash it */
764 dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
765 Data data (info.size);
766 size_t const read = fread (data.data().get(), 1, data.size(), asset_file);
767 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
768 if (read != static_cast<size_t> (data.size ())) {
769 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
773 digester.add (data.data().get(), data.size());
774 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
775 if (digester.get() != info.hash) {
776 LOG_GENERAL ("Existing frame %1 failed hash check", frame);