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