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