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