Split create_reel() up into some separate bits.
[dcpomatic.git] / src / lib / reel_writer.cc
1 /*
2     Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21 #include "reel_writer.h"
22 #include "film.h"
23 #include "cross.h"
24 #include "job.h"
25 #include "log.h"
26 #include "dcpomatic_log.h"
27 #include "digester.h"
28 #include "font.h"
29 #include "compose.hpp"
30 #include "config.h"
31 #include "audio_buffers.h"
32 #include "image.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>
39 #include <dcp/reel.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>
47 #include <dcp/dcp.h>
48 #include <dcp/cpl.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>
55
56 #include "i18n.h"
57
58 using std::list;
59 using std::string;
60 using std::cout;
61 using std::exception;
62 using std::map;
63 using std::vector;
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;
69 #endif
70 using dcp::ArrayData;
71 using dcp::Data;
72 using dcp::raw_convert;
73 using namespace dcpomatic;
74
75 int const ReelWriter::_info_size = 48;
76
77 static dcp::MXFMetadata
78 mxf_metadata ()
79 {
80         dcp::MXFMetadata meta;
81         Config* config = Config::instance();
82         if (!config->dcp_company_name().empty()) {
83                 meta.company_name = config->dcp_company_name ();
84         }
85         if (!config->dcp_product_name().empty()) {
86                 meta.product_name = config->dcp_product_name ();
87         }
88         if (!config->dcp_product_version().empty()) {
89                 meta.product_version = config->dcp_product_version ();
90         }
91         return meta;
92 }
93
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
97         )
98         : _film (film)
99         , _period (period)
100         , _reel_index (reel_index)
101         , _reel_count (reel_count)
102         , _content_summary (film->content_summary(period))
103         , _job (job)
104 {
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.
108         */
109
110         dcp::Standard const standard = _film->interop() ? dcp::INTEROP : dcp::SMPTE;
111
112         boost::filesystem::path const asset =
113                 _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period);
114
115         _first_nonexistant_frame = check_existing_picture_asset (asset);
116
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
121                    #1126.
122                 */
123                 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
124                         if (job) {
125                                 job->sub (_("Copying old video file"));
126                                 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
127                         } else {
128                                 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
129                         }
130                         boost::filesystem::remove (asset);
131                         boost::filesystem::rename (asset.string() + ".tmp", asset);
132                 }
133
134
135                 if (_film->three_d ()) {
136                         _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
137                 } else {
138                         _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
139                 }
140
141                 _picture_asset->set_size (_film->frame_size());
142                 _picture_asset->set_metadata (mxf_metadata());
143
144                 if (_film->encrypted ()) {
145                         _picture_asset->set_key (_film->key());
146                         _picture_asset->set_context_id (_film->context_id());
147                 }
148
149                 _picture_asset->set_file (asset);
150                 _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
151         } else {
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));
156                 } else {
157                         _picture_asset.reset (new dcp::MonoPictureAsset(asset));
158                 }
159         }
160
161         if (_film->audio_channels ()) {
162                 _sound_asset.reset (
163                         new dcp::SoundAsset (dcp::Fraction(_film->video_frame_rate(), 1), _film->audio_frame_rate(), _film->audio_channels(), _film->audio_language(), standard)
164                         );
165
166                 _sound_asset->set_metadata (mxf_metadata());
167
168                 if (_film->encrypted ()) {
169                         _sound_asset->set_key (_film->key ());
170                 }
171
172                 DCPOMATIC_ASSERT (_film->directory());
173
174                 vector<dcp::Channel> active;
175                 BOOST_FOREACH (int i, _film->mapped_audio_channels()) {
176                         active.push_back (static_cast<dcp::Channel>(i));
177                 }
178
179                 /* Write the sound asset into the film directory so that we leave the creation
180                    of the DCP directory until the last minute.
181                 */
182                 _sound_asset_writer = _sound_asset->start_write (
183                         _film->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
184                         active,
185                         _film->contains_atmos_content()
186                         );
187         }
188 }
189
190 /** @param frame reel-relative frame */
191 void
192 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
193 {
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());
199 }
200
201 dcp::FrameInfo
202 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
203 {
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());
208
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;
213
214         return frame_info;
215 }
216
217 long
218 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
219 {
220         switch (eyes) {
221         case EYES_BOTH:
222                 return frame * _info_size;
223         case EYES_LEFT:
224                 return frame * _info_size * 2;
225         case EYES_RIGHT:
226                 return frame * _info_size * 2 + _info_size;
227         default:
228                 DCPOMATIC_ASSERT (false);
229         }
230
231         DCPOMATIC_ASSERT (false);
232 }
233
234 Frame
235 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
236 {
237         shared_ptr<Job> job = _job.lock ();
238
239         if (job) {
240                 job->sub (_("Checking existing image data"));
241         }
242
243         /* Try to open the existing asset */
244         FILE* asset_file = fopen_boost (asset, "rb");
245         if (!asset_file) {
246                 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
247                 return 0;
248         } else {
249                 LOG_GENERAL ("Opened existing asset at %1", asset.string());
250         }
251
252         shared_ptr<InfoFileHandle> info_file;
253
254         try {
255                 info_file = _film->info_file_handle (_period, true);
256         } catch (OpenFileError &) {
257                 LOG_GENERAL_NC ("Could not open film info file");
258                 fclose (asset_file);
259                 return 0;
260         }
261
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);
265
266         Frame first_nonexistant_frame;
267         if (_film->three_d ()) {
268                 /* Start looking at the last left frame */
269                 first_nonexistant_frame = n / 2;
270         } else {
271                 first_nonexistant_frame = n;
272         }
273
274         while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
275                 --first_nonexistant_frame;
276         }
277
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.
281                 */
282                 ++first_nonexistant_frame;
283         }
284
285         LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
286
287         fclose (asset_file);
288
289         return first_nonexistant_frame;
290 }
291
292 void
293 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
294 {
295         if (!_picture_asset_writer) {
296                 /* We're not writing any data */
297                 return;
298         }
299
300         dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data(), encoded->size());
301         write_frame_info (frame, eyes, fin);
302         _last_written[eyes] = encoded;
303 }
304
305
306 void
307 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
308 {
309         if (!_atmos_asset) {
310                 _atmos_asset = metadata.create (dcp::Fraction(_film->video_frame_rate(), 1));
311                 if (_film->encrypted()) {
312                         _atmos_asset->set_key(_film->key());
313                 }
314                 _atmos_asset_writer = _atmos_asset->start_write (
315                         _film->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
316                         );
317         }
318         _atmos_asset_writer->write (atmos);
319 }
320
321
322 void
323 ReelWriter::fake_write (int size)
324 {
325         if (!_picture_asset_writer) {
326                 /* We're not writing any data */
327                 return;
328         }
329
330         _picture_asset_writer->fake_write (size);
331 }
332
333 void
334 ReelWriter::repeat_write (Frame frame, Eyes eyes)
335 {
336         if (!_picture_asset_writer) {
337                 /* We're not writing any data */
338                 return;
339         }
340
341         dcp::FrameInfo fin = _picture_asset_writer->write (
342                 _last_written[eyes]->data(),
343                 _last_written[eyes]->size()
344                 );
345         write_frame_info (frame, eyes, fin);
346 }
347
348 void
349 ReelWriter::finish ()
350 {
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 ();
355         }
356
357         if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
358                 /* Nothing was written to the sound asset */
359                 _sound_asset.reset ();
360         }
361
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
370                    changing any video.
371                 */
372                 boost::system::error_code ec;
373                 boost::filesystem::remove (video_to, ec);
374
375                 boost::filesystem::create_hard_link (video_from, video_to, ec);
376                 if (ec) {
377                         LOG_WARNING_NC ("Hard-link failed; copying instead");
378                         shared_ptr<Job> job = _job.lock ();
379                         if (job) {
380                                 job->sub (_("Copying video file into DCP"));
381                                 try {
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);
386                                 }
387                         } else {
388                                 boost::filesystem::copy_file (video_from, video_to, ec);
389                                 if (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);
392                                 }
393                         }
394                 }
395
396                 _picture_asset->set_file (video_to);
397         }
398
399         /* Move the audio asset into the DCP */
400         if (_sound_asset) {
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);
404                 audio_to /= aaf;
405
406                 boost::system::error_code ec;
407                 boost::filesystem::rename (_film->file (aaf), audio_to, ec);
408                 if (ec) {
409                         throw FileError (
410                                 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
411                                 );
412                 }
413
414                 _sound_asset->set_file (audio_to);
415         }
416
417         if (_atmos_asset) {
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);
422                 atmos_to /= aaf;
423
424                 boost::system::error_code ec;
425                 boost::filesystem::rename (_film->file(aaf), atmos_to, ec);
426                 if (ec) {
427                         throw FileError (
428                                 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
429                                 );
430                 }
431
432                 _atmos_asset->set_file (atmos_to);
433         }
434 }
435
436 template <class T>
437 shared_ptr<T>
438 maybe_add_text (
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,
445         DCPTimePeriod period
446         )
447 {
448         Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
449
450         shared_ptr<T> reel_asset;
451
452         if (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()));
456                 }
457
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"));
462                 } else {
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.
467                         */
468                         dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
469
470                         asset->write (
471                                 film->dir(film->dcp_name()) / ("sub_" + asset->id() + ".mxf")
472                                 );
473                 }
474
475                 reel_asset.reset (
476                         new T (
477                                 asset,
478                                 dcp::Fraction (film->video_frame_rate(), 1),
479                                 picture_duration,
480                                 0
481                                 )
482                         );
483         } else {
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) {
488                                 reel_asset = k;
489                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
490                                 if (k->hash()) {
491                                         k->asset_ref()->set_hash (k->hash().get());
492                                 }
493                         }
494                 }
495         }
496
497         if (reel_asset) {
498                 if (reel_asset->actual_duration() != period_duration) {
499                         throw ProgrammingError (
500                                 __FILE__, __LINE__,
501                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
502                                 );
503                 }
504                 reel->add (reel_asset);
505         }
506
507         return reel_asset;
508 }
509
510
511 shared_ptr<dcp::ReelPictureAsset>
512 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
513 {
514         shared_ptr<dcp::ReelPictureAsset> reel_asset;
515
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);
519                 if (mono) {
520                         reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
521                 }
522
523                 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
524                 if (stereo) {
525                         reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
526                 }
527         } else {
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);
532                         if (k) {
533                                 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
534                         }
535                         if (k && j.period == _period) {
536                                 reel_asset = k;
537                         }
538                 }
539         }
540
541         Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
542
543         DCPOMATIC_ASSERT (reel_asset);
544         if (reel_asset->duration() != period_duration) {
545                 throw ProgrammingError (
546                         __FILE__, __LINE__,
547                         String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
548                         );
549         }
550         reel->add (reel_asset);
551
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());
555         }
556
557         return reel_asset;
558 }
559
560
561 void
562 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
563 {
564         shared_ptr<dcp::ReelSoundAsset> reel_asset;
565
566         if (_sound_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));
569         } else {
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);
574                         if (k) {
575                                 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
576                         }
577                         if (k && j.period == _period) {
578                                 reel_asset = k;
579                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
580                                 if (k->hash()) {
581                                         k->asset_ref()->set_hash (k->hash().get());
582                                 }
583                         }
584                 }
585         }
586
587         Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
588
589         DCPOMATIC_ASSERT (reel_asset);
590         if (reel_asset->actual_duration() != period_duration) {
591                 LOG_ERROR (
592                         "Reel sound asset has length %1 but reel period is %2",
593                         reel_asset->actual_duration(),
594                         period_duration
595                         );
596                 if (reel_asset->actual_duration() != period_duration) {
597                         throw ProgrammingError (
598                                 __FILE__, __LINE__,
599                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
600                                 );
601                 }
602
603         }
604         reel->add (reel_asset);
605 }
606
607
608 void
609 ReelWriter::create_reel_text (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const& fonts, int64_t duration) const
610 {
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());
614         }
615
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
619                         );
620                 if (a) {
621                         a->set_annotation_text (i->first.name);
622                         if (!i->first.language.empty()) {
623                                 a->set_language (dcp::LanguageTag(i->first.language));
624                         }
625                 }
626         }
627 }
628
629
630
631 void
632 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
633 {
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;
640                 }
641         }
642
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) {
646                         int h, m, s, f;
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()));
650                 }
651                 reel->add (ma);
652         }
653 }
654
655
656 shared_ptr<dcp::Reel>
657 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
658 {
659         LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
660
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);
666
667         if (_atmos_asset) {
668                 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
669         }
670
671         return reel;
672 }
673
674 void
675 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
676 {
677         if (_picture_asset) {
678                 _picture_asset->hash (set_progress);
679         }
680
681         if (_sound_asset) {
682                 _sound_asset->hash (set_progress);
683         }
684
685         if (_atmos_asset) {
686                 _atmos_asset->hash (set_progress);
687         }
688 }
689
690 Frame
691 ReelWriter::start () const
692 {
693         return _period.from.frames_floor (_film->video_frame_rate());
694 }
695
696
697 void
698 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
699 {
700         if (!_sound_asset_writer) {
701                 return;
702         }
703
704         DCPOMATIC_ASSERT (audio);
705         _sound_asset_writer->write (audio->data(), audio->frames());
706 }
707
708 void
709 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
710 {
711         shared_ptr<dcp::SubtitleAsset> asset;
712
713         switch (type) {
714         case TEXT_OPEN_SUBTITLE:
715                 asset = _subtitle_asset;
716                 break;
717         case TEXT_CLOSED_CAPTION:
718                 DCPOMATIC_ASSERT (track);
719                 asset = _closed_caption_assets[*track];
720                 break;
721         default:
722                 DCPOMATIC_ASSERT (false);
723         }
724
725         if (!asset) {
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);
734                         }
735                         s->set_reel_number (raw_convert<string> (_reel_index + 1));
736                         asset = s;
737                 } else {
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));
745                         }
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 ());
752                         }
753                         asset = s;
754                 }
755         }
756
757         switch (type) {
758         case TEXT_OPEN_SUBTITLE:
759                 _subtitle_asset = asset;
760                 break;
761         case TEXT_CLOSED_CAPTION:
762                 DCPOMATIC_ASSERT (track);
763                 _closed_caption_assets[*track] = asset;
764                 break;
765         default:
766                 DCPOMATIC_ASSERT (false);
767         }
768
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)));
774         }
775
776         BOOST_FOREACH (BitmapText i, subs.bitmap) {
777                 asset->add (
778                         shared_ptr<dcp::Subtitle>(
779                                 new dcp::SubtitleImage(
780                                         i.image->as_png(),
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()
785                                         )
786                                 )
787                         );
788         }
789 }
790
791 bool
792 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
793 {
794         LOG_GENERAL ("Checking existing picture frame %1", frame);
795
796         /* Read the data from the info file; for 3D we just check the left
797            frames until we find a good one.
798         */
799         dcp::FrameInfo const info = read_frame_info (info_file, frame, _film->three_d () ? EYES_LEFT : EYES_BOTH);
800
801         bool ok = true;
802
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);
810                 ok = false;
811         } else {
812                 Digester digester;
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);
817                         ok = false;
818                 }
819         }
820
821         return ok;
822 }