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