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