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