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