110102b7061f0890e5eb0044ff68e13c307f3dce
[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_data.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::set;
64 using std::vector;
65 using std::shared_ptr;
66 using boost::optional;
67 using std::dynamic_pointer_cast;
68 #if BOOST_VERSION >= 106100
69 using namespace boost::placeholders;
70 #endif
71 using std::weak_ptr;
72 using dcp::ArrayData;
73 using dcp::Data;
74 using dcp::raw_convert;
75 using namespace dcpomatic;
76
77 int const ReelWriter::_info_size = 48;
78
79 static dcp::MXFMetadata
80 mxf_metadata ()
81 {
82         dcp::MXFMetadata meta;
83         Config* config = Config::instance();
84         if (!config->dcp_company_name().empty()) {
85                 meta.company_name = config->dcp_company_name ();
86         }
87         if (!config->dcp_product_name().empty()) {
88                 meta.product_name = config->dcp_product_name ();
89         }
90         if (!config->dcp_product_version().empty()) {
91                 meta.product_version = config->dcp_product_version ();
92         }
93         return meta;
94 }
95
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.
100  */
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
103         )
104         : WeakConstFilm (weak_film)
105         , _period (period)
106         , _reel_index (reel_index)
107         , _reel_count (reel_count)
108         , _content_summary (film()->content_summary(period))
109         , _job (job)
110         , _text_only (text_only)
111 {
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.
115         */
116
117         dcp::Standard const standard = film()->interop() ? dcp::INTEROP : dcp::SMPTE;
118
119         boost::filesystem::path const asset =
120                 film()->internal_video_asset_dir() / film()->internal_video_asset_filename(_period);
121
122         _first_nonexistant_frame = check_existing_picture_asset (asset);
123
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
128                    #1126.
129                 */
130                 if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
131                         if (job) {
132                                 job->sub (_("Copying old video file"));
133                                 copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
134                         } else {
135                                 boost::filesystem::copy_file (asset, asset.string() + ".tmp");
136                         }
137                         boost::filesystem::remove (asset);
138                         boost::filesystem::rename (asset.string() + ".tmp", asset);
139                 }
140
141
142                 if (film()->three_d()) {
143                         _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
144                 } else {
145                         _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(film()->video_frame_rate(), 1), standard));
146                 }
147
148                 _picture_asset->set_size (film()->frame_size());
149                 _picture_asset->set_metadata (mxf_metadata());
150
151                 if (film()->encrypted()) {
152                         _picture_asset->set_key (film()->key());
153                         _picture_asset->set_context_id (film()->context_id());
154                 }
155
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));
163                 } else {
164                         _picture_asset.reset (new dcp::MonoPictureAsset(asset));
165                 }
166         }
167
168         if (film()->audio_channels()) {
169                 _sound_asset.reset (
170                         new dcp::SoundAsset (dcp::Fraction(film()->video_frame_rate(), 1), film()->audio_frame_rate(), film()->audio_channels(), film()->audio_language(), standard)
171                         );
172
173                 _sound_asset->set_metadata (mxf_metadata());
174
175                 if (film()->encrypted()) {
176                         _sound_asset->set_key (film()->key());
177                 }
178
179                 DCPOMATIC_ASSERT (film()->directory());
180
181                 vector<dcp::Channel> active;
182                 BOOST_FOREACH (int i, film()->mapped_audio_channels()) {
183                         active.push_back (static_cast<dcp::Channel>(i));
184                 }
185
186                 /* Write the sound asset into the film directory so that we leave the creation
187                    of the DCP directory until the last minute.
188                 */
189                 _sound_asset_writer = _sound_asset->start_write (
190                         film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
191                         active,
192                         film()->contains_atmos_content()
193                         );
194         }
195
196         _default_font = dcp::ArrayData(default_font_file());
197 }
198
199 /** @param frame reel-relative frame */
200 void
201 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
202 {
203         shared_ptr<InfoFileHandle> handle = film()->info_file_handle(_period, false);
204         dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
205         checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
206         checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
207         checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
208 }
209
210 dcp::FrameInfo
211 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
212 {
213         dcp::FrameInfo frame_info;
214         dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
215         checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
216         checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
217
218         char hash_buffer[33];
219         checked_fread (hash_buffer, 32, info->get(), info->file());
220         hash_buffer[32] = '\0';
221         frame_info.hash = hash_buffer;
222
223         return frame_info;
224 }
225
226 long
227 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
228 {
229         switch (eyes) {
230         case EYES_BOTH:
231                 return frame * _info_size;
232         case EYES_LEFT:
233                 return frame * _info_size * 2;
234         case EYES_RIGHT:
235                 return frame * _info_size * 2 + _info_size;
236         default:
237                 DCPOMATIC_ASSERT (false);
238         }
239
240         DCPOMATIC_ASSERT (false);
241 }
242
243 Frame
244 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
245 {
246         shared_ptr<Job> job = _job.lock ();
247
248         if (job) {
249                 job->sub (_("Checking existing image data"));
250         }
251
252         /* Try to open the existing asset */
253         FILE* asset_file = fopen_boost (asset, "rb");
254         if (!asset_file) {
255                 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
256                 return 0;
257         } else {
258                 LOG_GENERAL ("Opened existing asset at %1", asset.string());
259         }
260
261         shared_ptr<InfoFileHandle> info_file;
262
263         try {
264                 info_file = film()->info_file_handle (_period, true);
265         } catch (OpenFileError &) {
266                 LOG_GENERAL_NC ("Could not open film info file");
267                 fclose (asset_file);
268                 return 0;
269         }
270
271         /* Offset of the last dcp::FrameInfo in the info file */
272         int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
273         LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
274
275         Frame first_nonexistant_frame;
276         if (film()->three_d()) {
277                 /* Start looking at the last left frame */
278                 first_nonexistant_frame = n / 2;
279         } else {
280                 first_nonexistant_frame = n;
281         }
282
283         while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistant_frame) && first_nonexistant_frame > 0) {
284                 --first_nonexistant_frame;
285         }
286
287         if (!film()->three_d() && first_nonexistant_frame > 0) {
288                 /* If we are doing 3D we might have found a good L frame with no R, so only
289                    do this if we're in 2D and we've just found a good B(oth) frame.
290                 */
291                 ++first_nonexistant_frame;
292         }
293
294         LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
295
296         fclose (asset_file);
297
298         return first_nonexistant_frame;
299 }
300
301 void
302 ReelWriter::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
303 {
304         if (!_picture_asset_writer) {
305                 /* We're not writing any data */
306                 return;
307         }
308
309         dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data(), encoded->size());
310         write_frame_info (frame, eyes, fin);
311         _last_written[eyes] = encoded;
312 }
313
314
315 void
316 ReelWriter::write (shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
317 {
318         if (!_atmos_asset) {
319                 _atmos_asset = metadata.create (dcp::Fraction(film()->video_frame_rate(), 1));
320                 if (film()->encrypted()) {
321                         _atmos_asset->set_key(film()->key());
322                 }
323                 _atmos_asset_writer = _atmos_asset->start_write (
324                         film()->directory().get() / atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary)
325                         );
326         }
327         _atmos_asset_writer->write (atmos);
328 }
329
330
331 void
332 ReelWriter::fake_write (int size)
333 {
334         if (!_picture_asset_writer) {
335                 /* We're not writing any data */
336                 return;
337         }
338
339         _picture_asset_writer->fake_write (size);
340 }
341
342 void
343 ReelWriter::repeat_write (Frame frame, Eyes eyes)
344 {
345         if (!_picture_asset_writer) {
346                 /* We're not writing any data */
347                 return;
348         }
349
350         dcp::FrameInfo fin = _picture_asset_writer->write (
351                 _last_written[eyes]->data(),
352                 _last_written[eyes]->size()
353                 );
354         write_frame_info (frame, eyes, fin);
355 }
356
357 void
358 ReelWriter::finish (boost::filesystem::path output_dcp)
359 {
360         if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
361                 /* Nothing was written to the picture asset */
362                 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
363                 _picture_asset.reset ();
364         }
365
366         if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
367                 /* Nothing was written to the sound asset */
368                 _sound_asset.reset ();
369         }
370
371         /* Hard-link any video asset file into the DCP */
372         if (_picture_asset) {
373                 DCPOMATIC_ASSERT (_picture_asset->file());
374                 boost::filesystem::path video_from = _picture_asset->file().get();
375                 boost::filesystem::path video_to = output_dcp;
376                 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
377                 /* There may be an existing "to" file if we are recreating a DCP in the same place without
378                    changing any video.
379                 */
380                 boost::system::error_code ec;
381                 boost::filesystem::remove (video_to, ec);
382
383                 boost::filesystem::create_hard_link (video_from, video_to, ec);
384                 if (ec) {
385                         LOG_WARNING_NC ("Hard-link failed; copying instead");
386                         shared_ptr<Job> job = _job.lock ();
387                         if (job) {
388                                 job->sub (_("Copying video file into DCP"));
389                                 try {
390                                         copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
391                                 } catch (exception& e) {
392                                         LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
393                                         throw FileError (e.what(), video_from);
394                                 }
395                         } else {
396                                 boost::filesystem::copy_file (video_from, video_to, ec);
397                                 if (ec) {
398                                         LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
399                                         throw FileError (ec.message(), video_from);
400                                 }
401                         }
402                 }
403
404                 _picture_asset->set_file (video_to);
405         }
406
407         /* Move the audio asset into the DCP */
408         if (_sound_asset) {
409                 boost::filesystem::path audio_to = output_dcp;
410                 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
411                 audio_to /= aaf;
412
413                 boost::system::error_code ec;
414                 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
415                 if (ec) {
416                         throw FileError (
417                                 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
418                                 );
419                 }
420
421                 _sound_asset->set_file (audio_to);
422         }
423
424         if (_atmos_asset) {
425                 _atmos_asset_writer->finalize ();
426                 boost::filesystem::path atmos_to = output_dcp;
427                 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
428                 atmos_to /= aaf;
429
430                 boost::system::error_code ec;
431                 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
432                 if (ec) {
433                         throw FileError (
434                                 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
435                                 );
436                 }
437
438                 _atmos_asset->set_file (atmos_to);
439         }
440 }
441
442 template <class T>
443 shared_ptr<T>
444 maybe_add_text (
445         shared_ptr<dcp::SubtitleAsset> asset,
446         int64_t picture_duration,
447         shared_ptr<dcp::Reel> reel,
448         list<ReferencedReelAsset> const & refs,
449         vector<FontData> const & fonts,
450         dcp::ArrayData default_font,
451         shared_ptr<const Film> film,
452         DCPTimePeriod period,
453         boost::filesystem::path output_dcp,
454         bool text_only
455         )
456 {
457         Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
458
459         shared_ptr<T> reel_asset;
460
461         if (asset) {
462                 /* Add the font to the subtitle content */
463                 BOOST_FOREACH (FontData const& j, fonts) {
464                         asset->add_font (j.id, j.data.get_value_or(default_font));
465                 }
466
467                 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
468                         boost::filesystem::path directory = output_dcp / asset->id ();
469                         boost::filesystem::create_directories (directory);
470                         asset->write (directory / ("sub_" + asset->id() + ".xml"));
471                 } else {
472                         /* All our assets should be the same length; use the picture asset length here
473                            as a reference to set the subtitle one.  We'll use the duration rather than
474                            the intrinsic duration; we don't care if the picture asset has been trimmed, we're
475                            just interested in its presentation length.
476                         */
477                         dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
478
479                         asset->write (
480                                 output_dcp / ("sub_" + asset->id() + ".mxf")
481                                 );
482                 }
483
484                 reel_asset.reset (
485                         new T (
486                                 asset,
487                                 dcp::Fraction (film->video_frame_rate(), 1),
488                                 picture_duration,
489                                 0
490                                 )
491                         );
492         } else {
493                 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
494                 BOOST_FOREACH (ReferencedReelAsset j, refs) {
495                         shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
496                         if (k && j.period == period) {
497                                 reel_asset = k;
498                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
499                                 if (k->hash()) {
500                                         k->asset_ref()->set_hash (k->hash().get());
501                                 }
502                         }
503                 }
504         }
505
506         if (reel_asset) {
507                 if (!text_only && reel_asset->actual_duration() != period_duration) {
508                         throw ProgrammingError (
509                                 __FILE__, __LINE__,
510                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
511                                 );
512                 }
513                 reel->add (reel_asset);
514         }
515
516         return reel_asset;
517 }
518
519
520 shared_ptr<dcp::ReelPictureAsset>
521 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
522 {
523         shared_ptr<dcp::ReelPictureAsset> reel_asset;
524
525         if (_picture_asset) {
526                 /* We have made a picture asset of our own.  Put it into the reel */
527                 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
528                 if (mono) {
529                         reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
530                 }
531
532                 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
533                 if (stereo) {
534                         reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
535                 }
536         } else {
537                 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
538                 /* We don't have a picture asset of our own; hopefully we have one to reference */
539                 BOOST_FOREACH (ReferencedReelAsset j, refs) {
540                         shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
541                         if (k) {
542                                 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
543                         }
544                         if (k && j.period == _period) {
545                                 reel_asset = k;
546                         }
547                 }
548         }
549
550         Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
551
552         DCPOMATIC_ASSERT (reel_asset);
553         if (reel_asset->duration() != period_duration) {
554                 throw ProgrammingError (
555                         __FILE__, __LINE__,
556                         String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
557                         );
558         }
559         reel->add (reel_asset);
560
561         /* If we have a hash for this asset in the CPL, assume that it is correct */
562         if (reel_asset->hash()) {
563                 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
564         }
565
566         return reel_asset;
567 }
568
569
570 void
571 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
572 {
573         shared_ptr<dcp::ReelSoundAsset> reel_asset;
574
575         if (_sound_asset) {
576                 /* We have made a sound asset of our own.  Put it into the reel */
577                 reel_asset.reset (new dcp::ReelSoundAsset(_sound_asset, 0));
578         } else {
579                 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
580                 /* We don't have a sound asset of our own; hopefully we have one to reference */
581                 BOOST_FOREACH (ReferencedReelAsset j, refs) {
582                         shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
583                         if (k) {
584                                 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
585                         }
586                         if (k && j.period == _period) {
587                                 reel_asset = k;
588                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
589                                 if (k->hash()) {
590                                         k->asset_ref()->set_hash (k->hash().get());
591                                 }
592                         }
593                 }
594         }
595
596         Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
597
598         DCPOMATIC_ASSERT (reel_asset);
599         if (reel_asset->actual_duration() != period_duration) {
600                 LOG_ERROR (
601                         "Reel sound asset has length %1 but reel period is %2",
602                         reel_asset->actual_duration(),
603                         period_duration
604                         );
605                 if (reel_asset->actual_duration() != period_duration) {
606                         throw ProgrammingError (
607                                 __FILE__, __LINE__,
608                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
609                                 );
610                 }
611
612         }
613         reel->add (reel_asset);
614 }
615
616
617 void
618 ReelWriter::create_reel_text (
619         shared_ptr<dcp::Reel> reel,
620         list<ReferencedReelAsset> const & refs,
621         vector<FontData> const& fonts,
622         int64_t duration,
623         boost::filesystem::path output_dcp,
624         bool ensure_subtitles,
625         set<DCPTextTrack> ensure_closed_captions
626         ) const
627 {
628         shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (
629                 _subtitle_asset, duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
630                 );
631
632         if (subtitle) {
633                 /* We have a subtitle asset that we either made or are referencing */
634                 if (!film()->subtitle_languages().empty()) {
635                         subtitle->set_language (film()->subtitle_languages().front());
636                 }
637         } else if (ensure_subtitles) {
638                 /* We had no subtitle asset, but we've been asked to make sure there is one */
639                 subtitle = maybe_add_text<dcp::ReelSubtitleAsset>(
640                         empty_text_asset(TEXT_OPEN_SUBTITLE, optional<DCPTextTrack>()),
641                         duration,
642                         reel,
643                         refs,
644                         fonts,
645                         _default_font,
646                         film(),
647                         _period,
648                         output_dcp,
649                         _text_only
650                         );
651         }
652
653         for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
654                 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
655                         i->second, duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
656                         );
657                 DCPOMATIC_ASSERT (a);
658                 a->set_annotation_text (i->first.name);
659                 if (!i->first.language.empty()) {
660                         a->set_language (dcp::LanguageTag(i->first.language));
661                 }
662
663                 ensure_closed_captions.erase (i->first);
664         }
665
666         /* Make empty tracks for anything we've been asked to ensure but that we haven't added */
667         BOOST_FOREACH (DCPTextTrack i, ensure_closed_captions) {
668                 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
669                         empty_text_asset(TEXT_CLOSED_CAPTION, i), duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
670                         );
671                 DCPOMATIC_ASSERT (a);
672                 a->set_annotation_text (i.name);
673                 if (!i.language.empty()) {
674                         a->set_language (dcp::LanguageTag(i.language));
675                 }
676         }
677 }
678
679
680
681 void
682 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
683 {
684         Film::Markers markers = film()->markers();
685         film()->add_ffoc_lfoc(markers);
686         Film::Markers reel_markers;
687         for (Film::Markers::const_iterator i = markers.begin(); i != markers.end(); ++i) {
688                 if (_period.contains(i->second)) {
689                         reel_markers[i->first] = i->second;
690                 }
691         }
692
693         if (!reel_markers.empty ()) {
694                 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(film()->video_frame_rate(), 1), 0));
695                 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
696                         int h, m, s, f;
697                         DCPTime relative = i->second - _period.from;
698                         relative.split (film()->video_frame_rate(), h, m, s, f);
699                         ma->set (i->first, dcp::Time(h, m, s, f, film()->video_frame_rate()));
700                 }
701                 reel->add (ma);
702         }
703 }
704
705
706 /** @param ensure_subtitles true to make sure the reel has a subtitle asset.
707  *  @param ensure_closed_captions make sure the reel has these closed caption tracks.
708  */
709 shared_ptr<dcp::Reel>
710 ReelWriter::create_reel (
711         list<ReferencedReelAsset> const & refs,
712         vector<FontData> const & fonts,
713         boost::filesystem::path output_dcp,
714         bool ensure_subtitles,
715         set<DCPTextTrack> ensure_closed_captions
716         )
717 {
718         LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
719
720         shared_ptr<dcp::Reel> reel (new dcp::Reel());
721
722         /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
723          * how long the subtitle / CCAP assets should be.  However, since we're only writing them to see
724          * how big they are, we don't care about that.
725          */
726         int64_t duration = 0;
727         if (!_text_only) {
728                 shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
729                 duration = reel_picture_asset->actual_duration ();
730                 create_reel_sound (reel, refs);
731                 create_reel_markers (reel);
732         }
733
734         create_reel_text (reel, refs, fonts, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
735
736         if (_atmos_asset) {
737                 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
738         }
739
740         return reel;
741 }
742
743 void
744 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
745 {
746         if (_picture_asset) {
747                 _picture_asset->hash (set_progress);
748         }
749
750         if (_sound_asset) {
751                 _sound_asset->hash (set_progress);
752         }
753
754         if (_atmos_asset) {
755                 _atmos_asset->hash (set_progress);
756         }
757 }
758
759 Frame
760 ReelWriter::start () const
761 {
762         return _period.from.frames_floor (film()->video_frame_rate());
763 }
764
765
766 void
767 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
768 {
769         if (!_sound_asset_writer) {
770                 return;
771         }
772
773         DCPOMATIC_ASSERT (audio);
774         _sound_asset_writer->write (audio->data(), audio->frames());
775 }
776
777
778 shared_ptr<dcp::SubtitleAsset>
779 ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track) const
780 {
781         shared_ptr<dcp::SubtitleAsset> asset;
782
783         vector<dcp::LanguageTag> lang = film()->subtitle_languages();
784         if (film()->interop()) {
785                 shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
786                 s->set_movie_title (film()->name());
787                 if (type == TEXT_OPEN_SUBTITLE) {
788                         s->set_language (lang.empty() ? "Unknown" : lang.front().to_string());
789                 } else if (!track->language.empty()) {
790                         s->set_language (track->language);
791                 }
792                 s->set_reel_number (raw_convert<string> (_reel_index + 1));
793                 asset = s;
794         } else {
795                 shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
796                 s->set_content_title_text (film()->name());
797                 s->set_metadata (mxf_metadata());
798                 if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
799                         s->set_language (lang.front());
800                 } else if (track && !track->language.empty()) {
801                         s->set_language (dcp::LanguageTag(track->language));
802                 }
803                 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
804                 s->set_reel_number (_reel_index + 1);
805                 s->set_time_code_rate (film()->video_frame_rate());
806                 s->set_start_time (dcp::Time ());
807                 if (film()->encrypted()) {
808                         s->set_key (film()->key());
809                 }
810                 asset = s;
811         }
812
813         return asset;
814 }
815
816
817 void
818 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
819 {
820         shared_ptr<dcp::SubtitleAsset> asset;
821
822         switch (type) {
823         case TEXT_OPEN_SUBTITLE:
824                 asset = _subtitle_asset;
825                 break;
826         case TEXT_CLOSED_CAPTION:
827                 DCPOMATIC_ASSERT (track);
828                 asset = _closed_caption_assets[*track];
829                 break;
830         default:
831                 DCPOMATIC_ASSERT (false);
832         }
833
834         if (!asset) {
835                 asset = empty_text_asset (type, track);
836         }
837
838         switch (type) {
839         case TEXT_OPEN_SUBTITLE:
840                 _subtitle_asset = asset;
841                 break;
842         case TEXT_CLOSED_CAPTION:
843                 DCPOMATIC_ASSERT (track);
844                 _closed_caption_assets[*track] = asset;
845                 break;
846         default:
847                 DCPOMATIC_ASSERT (false);
848         }
849
850         BOOST_FOREACH (StringText i, subs.string) {
851                 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
852                 i.set_in  (i.in()  - dcp::Time (_period.from.seconds(), i.in().tcr));
853                 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
854                 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
855         }
856
857         BOOST_FOREACH (BitmapText i, subs.bitmap) {
858                 asset->add (
859                         shared_ptr<dcp::Subtitle>(
860                                 new dcp::SubtitleImage(
861                                         i.image->as_png(),
862                                         dcp::Time(period.from.seconds() - _period.from.seconds(), film()->video_frame_rate()),
863                                         dcp::Time(period.to.seconds() - _period.from.seconds(), film()->video_frame_rate()),
864                                         i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
865                                         dcp::Time(), dcp::Time()
866                                         )
867                                 )
868                         );
869         }
870 }
871
872 bool
873 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
874 {
875         LOG_GENERAL ("Checking existing picture frame %1", frame);
876
877         /* Read the data from the info file; for 3D we just check the left
878            frames until we find a good one.
879         */
880         dcp::FrameInfo const info = read_frame_info (info_file, frame, film()->three_d() ? EYES_LEFT : EYES_BOTH);
881
882         bool ok = true;
883
884         /* Read the data from the asset and hash it */
885         dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
886         ArrayData data (info.size);
887         size_t const read = fread (data.data(), 1, data.size(), asset_file);
888         LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
889         if (read != static_cast<size_t> (data.size ())) {
890                 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
891                 ok = false;
892         } else {
893                 Digester digester;
894                 digester.add (data.data(), data.size());
895                 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
896                 if (digester.get() != info.hash) {
897                         LOG_GENERAL ("Existing frame %1 failed hash check", frame);
898                         ok = false;
899                 }
900         }
901
902         return ok;
903 }