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