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