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