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