Make DCP text track language properly optional and ignore bad values on imported...
[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 std::make_shared;
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         auto 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         auto const standard = film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::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 = make_shared<dcp::StereoPictureAsset>(asset);
163                 } else {
164                         _picture_asset = make_shared<dcp::MonoPictureAsset>(asset);
165                 }
166         }
167
168         if (film()->audio_channels()) {
169                 auto langs = film()->audio_languages();
170                 _sound_asset = make_shared<dcp::SoundAsset> (
171                         dcp::Fraction(film()->video_frame_rate(), 1),
172                         film()->audio_frame_rate(),
173                         film()->audio_channels(),
174                         langs.empty() ? dcp::LanguageTag("en-US") : langs.front(),
175                         standard
176                         );
177
178                 _sound_asset->set_metadata (mxf_metadata());
179
180                 if (film()->encrypted()) {
181                         _sound_asset->set_key (film()->key());
182                 }
183
184                 DCPOMATIC_ASSERT (film()->directory());
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                         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         auto 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         auto job = _job.lock ();
246
247         if (job) {
248                 job->sub (_("Checking existing image data"));
249         }
250
251         /* Try to open the existing asset */
252         auto 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         auto fin = _picture_asset_writer->write (encoded->data(), encoded->size());
309         write_frame_info (frame, eyes, fin);
310         _last_written[static_cast<int>(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         auto fin = _picture_asset_writer->write (
350                 _last_written[static_cast<int>(eyes)]->data(),
351                 _last_written[static_cast<int>(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                         auto 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                 auto 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                 auto 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                         auto 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 = make_shared<T> (
484                         asset,
485                         dcp::Fraction(film->video_frame_rate(), 1),
486                         picture_duration,
487                         0
488                         );
489         } else {
490                 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
491                 for (auto j: refs) {
492                         auto 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                 auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
525                 if (mono) {
526                         reel_asset = make_shared<dcp::ReelMonoPictureAsset>(mono, 0);
527                 }
528
529                 auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
530                 if (stereo) {
531                         reel_asset = make_shared<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                 for (auto j: refs) {
537                         auto 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 = make_shared<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                 for (auto j: refs) {
579                         auto 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         auto 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         vector<FontData> 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         auto subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (
626                 _subtitle_asset, duration, reel, refs, fonts, _default_font, 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 (auto main_language = film()->subtitle_languages().first) {
632                         subtitle->set_language (*main_language);
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(TextType::OPEN_SUBTITLE, optional<DCPTextTrack>()),
638                         duration,
639                         reel,
640                         refs,
641                         fonts,
642                         _default_font,
643                         film(),
644                         _period,
645                         output_dcp,
646                         _text_only
647                         );
648         }
649
650         for (auto const& i: _closed_caption_assets) {
651                 auto a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
652                         i.second, duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
653                         );
654                 DCPOMATIC_ASSERT (a);
655                 a->set_annotation_text (i.first.name);
656                 if (i.first.language) {
657                         a->set_language (i.first.language.get());
658                 }
659
660                 ensure_closed_captions.erase (i.first);
661         }
662
663         /* Make empty tracks for anything we've been asked to ensure but that we haven't added */
664         for (auto i: ensure_closed_captions) {
665                 auto a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
666                         empty_text_asset(TextType::CLOSED_CAPTION, i), duration, reel, refs, fonts, _default_font, film(), _period, output_dcp, _text_only
667                         );
668                 DCPOMATIC_ASSERT (a);
669                 a->set_annotation_text (i.name);
670                 if (i.language) {
671                         a->set_language (i.language.get());
672                 }
673         }
674 }
675
676
677
678 void
679 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
680 {
681         auto markers = film()->markers();
682         film()->add_ffoc_lfoc(markers);
683         Film::Markers reel_markers;
684         for (auto const& i: markers) {
685                 if (_period.contains(i.second)) {
686                         reel_markers[i.first] = i.second;
687                 }
688         }
689
690         if (!reel_markers.empty ()) {
691                 auto ma = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(film()->video_frame_rate(), 1), reel->duration(), 0);
692                 for (auto const& i: reel_markers) {
693                         DCPTime relative = i.second - _period.from;
694                         auto hmsf = relative.split (film()->video_frame_rate());
695                         ma->set (i.first, dcp::Time(hmsf.h, hmsf.m, hmsf.s, hmsf.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         vector<FontData> 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         auto reel = make_shared<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                 auto 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 (make_shared<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         auto lang = film()->subtitle_languages();
780         if (film()->interop()) {
781                 auto s = make_shared<dcp::InteropSubtitleAsset>();
782                 s->set_movie_title (film()->name());
783                 if (type == TextType::OPEN_SUBTITLE) {
784                         s->set_language (lang.first ? lang.first->to_string() : "Unknown");
785                 } else if (track->language) {
786                         s->set_language (track->language->to_string());
787                 }
788                 s->set_reel_number (raw_convert<string> (_reel_index + 1));
789                 asset = s;
790         } else {
791                 auto s = make_shared<dcp::SMPTESubtitleAsset>();
792                 s->set_content_title_text (film()->name());
793                 s->set_metadata (mxf_metadata());
794                 if (type == TextType::OPEN_SUBTITLE && lang.first) {
795                         s->set_language (*lang.first);
796                 } else if (track && track->language) {
797                         s->set_language (dcp::LanguageTag(track->language->to_string()));
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                 s->add (
807                         std::make_shared<dcp::SubtitleString>(
808                                 optional<std::string>(),
809                                 false,
810                                 false,
811                                 false,
812                                 dcp::Colour(),
813                                 42,
814                                 1.0,
815                                 dcp::Time(0, 0, 0, 0, 24),
816                                 dcp::Time(0, 0, 1, 0, 24),
817                                 0.5,
818                                 dcp::HAlign::CENTER,
819                                 0.5,
820                                 dcp::VAlign::CENTER,
821                                 dcp::Direction::LTR,
822                                 "",
823                                 dcp::Effect::NONE,
824                                 dcp::Colour(),
825                                 dcp::Time(),
826                                 dcp::Time()
827                                 )
828                        );
829                 asset = s;
830         }
831
832         return asset;
833 }
834
835
836 void
837 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
838 {
839         shared_ptr<dcp::SubtitleAsset> asset;
840
841         switch (type) {
842         case TextType::OPEN_SUBTITLE:
843                 asset = _subtitle_asset;
844                 break;
845         case TextType::CLOSED_CAPTION:
846                 DCPOMATIC_ASSERT (track);
847                 asset = _closed_caption_assets[*track];
848                 break;
849         default:
850                 DCPOMATIC_ASSERT (false);
851         }
852
853         if (!asset) {
854                 asset = empty_text_asset (type, track);
855         }
856
857         switch (type) {
858         case TextType::OPEN_SUBTITLE:
859                 _subtitle_asset = asset;
860                 break;
861         case TextType::CLOSED_CAPTION:
862                 DCPOMATIC_ASSERT (track);
863                 _closed_caption_assets[*track] = asset;
864                 break;
865         default:
866                 DCPOMATIC_ASSERT (false);
867         }
868
869         /* timecode rate for subtitles we emit; we might as well stick to ms accuracy here, I think */
870         auto const tcr = 1000;
871
872         for (auto i: subs.string) {
873                 i.set_in  (dcp::Time(period.from.seconds() - _period.from.seconds(), tcr));
874                 i.set_out (dcp::Time(period.to.seconds() - _period.from.seconds(), tcr));
875                 asset->add (make_shared<dcp::SubtitleString>(i));
876         }
877
878         for (auto i: subs.bitmap) {
879                 asset->add (
880                         make_shared<dcp::SubtitleImage>(
881                                 i.image->as_png(),
882                                 dcp::Time(period.from.seconds() - _period.from.seconds(), tcr),
883                                 dcp::Time(period.to.seconds() - _period.from.seconds(), tcr),
884                                 i.rectangle.x, dcp::HAlign::LEFT, i.rectangle.y, dcp::VAlign::TOP,
885                                 dcp::Time(), dcp::Time()
886                                 )
887                         );
888         }
889 }
890
891 bool
892 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
893 {
894         LOG_GENERAL ("Checking existing picture frame %1", frame);
895
896         /* Read the data from the info file; for 3D we just check the left
897            frames until we find a good one.
898         */
899         auto const info = read_frame_info (info_file, frame, film()->three_d() ? Eyes::LEFT : Eyes::BOTH);
900
901         bool ok = true;
902
903         /* Read the data from the asset and hash it */
904         dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
905         ArrayData data (info.size);
906         size_t const read = fread (data.data(), 1, data.size(), asset_file);
907         LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
908         if (read != static_cast<size_t> (data.size ())) {
909                 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
910                 ok = false;
911         } else {
912                 Digester digester;
913                 digester.add (data.data(), data.size());
914                 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
915                 if (digester.get() != info.hash) {
916                         LOG_GENERAL ("Existing frame %1 failed hash check", frame);
917                         ok = false;
918                 }
919         }
920
921         return ok;
922 }