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>
65 using boost::shared_ptr;
66 using boost::optional;
67 using boost::dynamic_pointer_cast;
68 #if BOOST_VERSION >= 106100
69 using namespace boost::placeholders;
71 using boost::weak_ptr;
74 using dcp::raw_convert;
75 using namespace dcpomatic;
77 int const ReelWriter::_info_size = 48;
79 static dcp::MXFMetadata
82 dcp::MXFMetadata meta;
83 Config* config = Config::instance();
84 if (!config->dcp_company_name().empty()) {
85 meta.company_name = config->dcp_company_name ();
87 if (!config->dcp_product_name().empty()) {
88 meta.product_name = config->dcp_product_name ();
90 if (!config->dcp_product_version().empty()) {
91 meta.product_version = config->dcp_product_version ();
96 /** @param job Related job, or 0.
97 * @param text_only true to enable a special mode where the writer will expect only subtitles and closed captions to be written
98 * (no picture nor sound) and not give errors in that case. This is used by the hints system to check the potential sizes of
99 * subtitle / closed caption files.
101 ReelWriter::ReelWriter (
102 weak_ptr<const Film> weak_film, DCPTimePeriod period, shared_ptr<Job> job, int reel_index, int reel_count, bool text_only
104 : WeakConstFilm (weak_film)
106 , _reel_index (reel_index)
107 , _reel_count (reel_count)
108 , _content_summary (film()->content_summary(period))
110 , _text_only (text_only)
112 /* Create or find our picture asset in a subdirectory, named
113 according to those film's parameters which affect the video
114 output. We will hard-link it into the DCP later.
117 dcp::Standard const standard = film()->interop() ? dcp::INTEROP : dcp::SMPTE;
119 boost::filesystem::path const asset =
120 film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
122 _first_nonexistant_frame = check_existing_picture_asset (asset);
124 if (_first_nonexistant_frame < period.duration().frames_round(film()->video_frame_rate())) {
125 /* We do not have a complete picture asset. If there is an
126 existing asset, break any hard links to it as we are about
127 to change its contents (if only by changing the IDs); see
130 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
132 job->sub (_("Copying old video file"));
133 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
135 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
137 boost::filesystem::remove (asset);
138 boost::filesystem::rename (asset.string() + ".tmp", asset);
142 if (film()->three_d()) {
143 _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
145 _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
148 _picture_asset->set_size (film()->frame_size());
149 _picture_asset->set_metadata (mxf_metadata());
151 if (film()->encrypted()) {
152 _picture_asset->set_key (film()->key());
153 _picture_asset->set_context_id (film()->context_id());
156 _picture_asset->set_file (asset);
157 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
158 } else if (!text_only) {
159 /* We already have a complete picture asset that we can just re-use */
160 /* XXX: what about if the encryption key changes? */
161 if (film()->three_d()) {
162 _picture_asset.reset (new dcp::StereoPictureAsset(asset));
164 _picture_asset.reset (new dcp::MonoPictureAsset(asset));
168 if (film()->audio_channels()) {
170 new dcp::SoundAsset (dcp::Fraction(film()->video_frame_rate(), 1), film()->audio_frame_rate(), film()->audio_channels(), film()->audio_language(), standard)
173 _sound_asset->set_metadata (mxf_metadata());
175 if (film()->encrypted()) {
176 _sound_asset->set_key (film()->key());
179 DCPOMATIC_ASSERT (film()->directory());
181 vector<dcp::Channel> active;
182 BOOST_FOREACH (int i, film()->mapped_audio_channels()) {
183 active.push_back (static_cast<dcp::Channel>(i));
186 /* Write the sound asset into the film directory so that we leave the creation
187 of the DCP directory until the last minute.
189 _sound_asset_writer = _sound_asset->start_write (
190 film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
192 film()->contains_atmos_content()
197 /** @param frame reel-relative frame */
199 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
201 shared_ptr<InfoFileHandle> handle = film()->info_file_handle(_period, false);
202 dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
203 checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
204 checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
205 checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
209 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
211 dcp::FrameInfo frame_info;
212 dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
213 checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
214 checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
216 char hash_buffer[33];
217 checked_fread (hash_buffer, 32, info->get(), info->file());
218 hash_buffer[32] = '\0';
219 frame_info.hash = hash_buffer;
225 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
229 return frame * _info_size;
231 return frame * _info_size * 2;
233 return frame * _info_size * 2 + _info_size;
235 DCPOMATIC_ASSERT (false);
238 DCPOMATIC_ASSERT (false);
242 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
244 shared_ptr<Job> job = _job.lock ();
247 job->sub (_("Checking existing image data"));
250 /* Try to open the existing asset */
251 FILE* asset_file = fopen_boost (asset, "rb");
253 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
256 LOG_GENERAL ("Opened existing asset at %1", asset.string());
259 shared_ptr<InfoFileHandle> info_file;
262 info_file = film()->info_file_handle (_period, true);
263 } catch (OpenFileError &) {
264 LOG_GENERAL_NC ("Could not open film info file");
269 /* Offset of the last dcp::FrameInfo in the info file */
270 int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
271 LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
273 Frame first_nonexistant_frame;
274 if (film()->three_d()) {
275 /* Start looking at the last left frame */
276 first_nonexistant_frame = n / 2;
278 first_nonexistant_frame = n;
281 while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
282 --first_nonexistant_frame;
285 if (!film()->three_d() && first_nonexistant_frame > 0) {
286 /* If we are doing 3D we might have found a good L frame with no R, so only
287 do this if we're in 2D and we've just found a good B(oth) frame.
289 ++first_nonexistant_frame;
292 LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
296 return first_nonexistant_frame;
300 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
302 if (!_picture_asset_writer) {
303 /* We're not writing any data */
307 dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data(), encoded->size());
308 write_frame_info (frame, eyes, fin);
309 _last_written[eyes] = encoded;
314 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
317 _atmos_asset = metadata.create (dcp::Fraction(film()->video_frame_rate(), 1));
318 if (film()->encrypted()) {
319 _atmos_asset->set_key(film()->key());
321 _atmos_asset_writer = _atmos_asset->start_write (
322 film()->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
325 _atmos_asset_writer->write (atmos);
330 ReelWriter::fake_write (int size)
332 if (!_picture_asset_writer) {
333 /* We're not writing any data */
337 _picture_asset_writer->fake_write (size);
341 ReelWriter::repeat_write (Frame frame, Eyes eyes)
343 if (!_picture_asset_writer) {
344 /* We're not writing any data */
348 dcp::FrameInfo fin = _picture_asset_writer->write (
349 _last_written[eyes]->data(),
350 _last_written[eyes]->size()
352 write_frame_info (frame, eyes, fin);
356 ReelWriter::finish (boost::filesystem::path output_dcp)
358 if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
359 /* Nothing was written to the picture asset */
360 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
361 _picture_asset.reset ();
364 if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
365 /* Nothing was written to the sound asset */
366 _sound_asset.reset ();
369 /* Hard-link any video asset file into the DCP */
370 if (_picture_asset) {
371 DCPOMATIC_ASSERT (_picture_asset->file());
372 boost::filesystem::path video_from = _picture_asset->file().get();
373 boost::filesystem::path video_to = output_dcp;
374 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
375 /* There may be an existing "to" file if we are recreating a DCP in the same place without
378 boost::system::error_code ec;
379 boost::filesystem::remove (video_to, ec);
381 boost::filesystem::create_hard_link (video_from, video_to, ec);
383 LOG_WARNING_NC ("Hard-link failed; copying instead");
384 shared_ptr<Job> job = _job.lock ();
386 job->sub (_("Copying video file into DCP"));
388 copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
389 } catch (exception& e) {
390 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
391 throw FileError (e.what(), video_from);
394 boost::filesystem::copy_file (video_from, video_to, ec);
396 LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
397 throw FileError (ec.message(), video_from);
402 _picture_asset->set_file (video_to);
405 /* Move the audio asset into the DCP */
407 boost::filesystem::path audio_to = output_dcp;
408 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
411 boost::system::error_code ec;
412 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
415 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
419 _sound_asset->set_file (audio_to);
423 _atmos_asset_writer->finalize ();
424 boost::filesystem::path atmos_to = output_dcp;
425 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
428 boost::system::error_code ec;
429 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
432 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
436 _atmos_asset->set_file (atmos_to);
443 shared_ptr<dcp::SubtitleAsset> asset,
444 int64_t picture_duration,
445 shared_ptr<dcp::Reel> reel,
446 list<ReferencedReelAsset> const & refs,
447 list<shared_ptr<Font> > const & fonts,
448 shared_ptr<const Film> film,
449 DCPTimePeriod period,
450 boost::filesystem::path output_dcp,
454 Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
456 shared_ptr<T> reel_asset;
459 /* Add the font to the subtitle content */
460 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
461 asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
464 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
465 boost::filesystem::path directory = output_dcp / asset->id ();
466 boost::filesystem::create_directories (directory);
467 asset->write (directory / ("sub_" + asset->id() + ".xml"));
469 /* All our assets should be the same length; use the picture asset length here
470 as a reference to set the subtitle one. We'll use the duration rather than
471 the intrinsic duration; we don't care if the picture asset has been trimmed, we're
472 just interested in its presentation length.
474 dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
477 output_dcp / ("sub_" + asset->id() + ".mxf")
484 dcp::Fraction (film->video_frame_rate(), 1),
490 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
491 BOOST_FOREACH (ReferencedReelAsset j, refs) {
492 shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
493 if (k && j.period == period) {
495 /* If we have a hash for this asset in the CPL, assume that it is correct */
497 k->asset_ref()->set_hash (k->hash().get());
504 if (!text_only && reel_asset->actual_duration() != period_duration) {
505 throw ProgrammingError (
507 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
510 reel->add (reel_asset);
517 shared_ptr<dcp::ReelPictureAsset>
518 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
520 shared_ptr<dcp::ReelPictureAsset> reel_asset;
522 if (_picture_asset) {
523 /* We have made a picture asset of our own. Put it into the reel */
524 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
526 reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
529 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
531 reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
534 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
535 /* We don't have a picture asset of our own; hopefully we have one to reference */
536 BOOST_FOREACH (ReferencedReelAsset j, refs) {
537 shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
539 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
541 if (k && j.period == _period) {
547 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
549 DCPOMATIC_ASSERT (reel_asset);
550 if (reel_asset->duration() != period_duration) {
551 throw ProgrammingError (
553 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
556 reel->add (reel_asset);
558 /* If we have a hash for this asset in the CPL, assume that it is correct */
559 if (reel_asset->hash()) {
560 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
568 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
570 shared_ptr<dcp::ReelSoundAsset> reel_asset;
573 /* We have made a sound asset of our own. Put it into the reel */
574 reel_asset.reset (new dcp::ReelSoundAsset(_sound_asset, 0));
576 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
577 /* We don't have a sound asset of our own; hopefully we have one to reference */
578 BOOST_FOREACH (ReferencedReelAsset j, refs) {
579 shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
581 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
583 if (k && j.period == _period) {
585 /* If we have a hash for this asset in the CPL, assume that it is correct */
587 k->asset_ref()->set_hash (k->hash().get());
593 Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
595 DCPOMATIC_ASSERT (reel_asset);
596 if (reel_asset->actual_duration() != period_duration) {
598 "Reel sound asset has length %1 but reel period is %2",
599 reel_asset->actual_duration(),
602 if (reel_asset->actual_duration() != period_duration) {
603 throw ProgrammingError (
605 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
610 reel->add (reel_asset);
615 ReelWriter::create_reel_text (
616 shared_ptr<dcp::Reel> reel,
617 list<ReferencedReelAsset> const & refs,
618 list<shared_ptr<Font> > const& fonts,
620 boost::filesystem::path output_dcp,
621 bool ensure_subtitles,
622 set<DCPTextTrack> ensure_closed_captions
625 shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (
626 _subtitle_asset, duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
630 /* We have a subtitle asset that we either made or are referencing */
631 if (!film()->subtitle_languages().empty()) {
632 subtitle->set_language (film()->subtitle_languages().front());
634 } else if (ensure_subtitles) {
635 /* We had no subtitle asset, but we've been asked to make sure there is one */
636 subtitle = maybe_add_text<dcp::ReelSubtitleAsset>(
637 empty_text_asset(TEXT_OPEN_SUBTITLE, optional<DCPTextTrack>()),
649 for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
650 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
651 i->second, duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
653 DCPOMATIC_ASSERT (a);
654 a->set_annotation_text (i->first.name);
655 if (!i->first.language.empty()) {
656 a->set_language (dcp::LanguageTag(i->first.language));
659 ensure_closed_captions.erase (i->first);
662 /* Make empty tracks for anything we've been asked to ensure but that we haven't added */
663 BOOST_FOREACH (DCPTextTrack i, ensure_closed_captions) {
664 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
665 empty_text_asset(TEXT_CLOSED_CAPTION, i), duration, reel, refs, fonts, film(), _period, output_dcp, _text_only
667 DCPOMATIC_ASSERT (a);
668 a->set_annotation_text (i.name);
669 if (!i.language.empty()) {
670 a->set_language (dcp::LanguageTag(i.language));
678 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
680 Film::Markers markers = film()->markers();
681 film()->add_ffoc_lfoc(markers);
682 Film::Markers reel_markers;
683 for (Film::Markers::const_iterator i = markers.begin(); i != markers.end(); ++i) {
684 if (_period.contains(i->second)) {
685 reel_markers[i->first] = i->second;
689 if (!reel_markers.empty ()) {
690 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(film()->video_frame_rate(), 1), 0));
691 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
693 DCPTime relative = i->second - _period.from;
694 relative.split (film()->video_frame_rate(), h, m, s, f);
695 ma->set (i->first, dcp::Time(h, m, s, f, film()->video_frame_rate()));
702 /** @param ensure_subtitles true to make sure the reel has a subtitle asset.
703 * @param ensure_closed_captions make sure the reel has these closed caption tracks.
705 shared_ptr<dcp::Reel>
706 ReelWriter::create_reel (
707 list<ReferencedReelAsset> const & refs,
708 list<shared_ptr<Font> > const & fonts,
709 boost::filesystem::path output_dcp,
710 bool ensure_subtitles,
711 set<DCPTextTrack> ensure_closed_captions
714 LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
716 shared_ptr<dcp::Reel> reel (new dcp::Reel());
718 /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
719 * how long the subtitle / CCAP assets should be. However, since we're only writing them to see
720 * how big they are, we don't care about that.
722 int64_t duration = 0;
724 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
725 duration = reel_picture_asset->actual_duration ();
726 create_reel_sound (reel, refs);
727 create_reel_markers (reel);
730 create_reel_text (reel, refs, fonts, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
733 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
740 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
742 if (_picture_asset) {
743 _picture_asset->hash (set_progress);
747 _sound_asset->hash (set_progress);
751 _atmos_asset->hash (set_progress);
756 ReelWriter::start () const
758 return _period.from.frames_floor (film()->video_frame_rate());
763 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
765 if (!_sound_asset_writer) {
769 DCPOMATIC_ASSERT (audio);
770 _sound_asset_writer->write (audio->data(), audio->frames());
774 shared_ptr<dcp::SubtitleAsset>
775 ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track) const
777 shared_ptr<dcp::SubtitleAsset> asset;
779 vector<dcp::LanguageTag> lang = film()->subtitle_languages();
780 if (film()->interop()) {
781 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
782 s->set_movie_title (film()->name());
783 if (type == TEXT_OPEN_SUBTITLE) {
784 s->set_language (lang.empty() ? "Unknown" : lang.front().to_string());
785 } else if (!track->language.empty()) {
786 s->set_language (track->language);
788 s->set_reel_number (raw_convert<string> (_reel_index + 1));
791 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
792 s->set_content_title_text (film()->name());
793 s->set_metadata (mxf_metadata());
794 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
795 s->set_language (lang.front());
796 } else if (track && !track->language.empty()) {
797 s->set_language (dcp::LanguageTag(track->language));
799 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
800 s->set_reel_number (_reel_index + 1);
801 s->set_time_code_rate (film()->video_frame_rate());
802 s->set_start_time (dcp::Time ());
803 if (film()->encrypted()) {
804 s->set_key (film()->key());
814 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
816 shared_ptr<dcp::SubtitleAsset> asset;
819 case TEXT_OPEN_SUBTITLE:
820 asset = _subtitle_asset;
822 case TEXT_CLOSED_CAPTION:
823 DCPOMATIC_ASSERT (track);
824 asset = _closed_caption_assets[*track];
827 DCPOMATIC_ASSERT (false);
831 asset = empty_text_asset (type, track);
835 case TEXT_OPEN_SUBTITLE:
836 _subtitle_asset = asset;
838 case TEXT_CLOSED_CAPTION:
839 DCPOMATIC_ASSERT (track);
840 _closed_caption_assets[*track] = asset;
843 DCPOMATIC_ASSERT (false);
846 BOOST_FOREACH (StringText i, subs.string) {
847 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
848 i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
849 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
850 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
853 BOOST_FOREACH (BitmapText i, subs.bitmap) {
855 shared_ptr<dcp::Subtitle>(
856 new dcp::SubtitleImage(
858 dcp::Time(period.from.seconds() - _period.from.seconds(), film()->video_frame_rate()),
859 dcp::Time(period.to.seconds() - _period.from.seconds(), film()->video_frame_rate()),
860 i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
861 dcp::Time(), dcp::Time()
869 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
871 LOG_GENERAL ("Checking existing picture frame %1", frame);
873 /* Read the data from the info file; for 3D we just check the left
874 frames until we find a good one.
876 dcp::FrameInfo const info = read_frame_info (info_file, frame, film()->three_d() ? EYES_LEFT : EYES_BOTH);
880 /* Read the data from the asset and hash it */
881 dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
882 ArrayData data (info.size);
883 size_t const read = fread (data.data(), 1, data.size(), asset_file);
884 LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
885 if (read != static_cast<size_t> (data.size ())) {
886 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
890 digester.add (data.data(), data.size());
891 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
892 if (digester.get() != info.hash) {
893 LOG_GENERAL ("Existing frame %1 failed hash check", frame);