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