Always make 16-channel MXFs.
[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 "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::dynamic_pointer_cast;
63 using std::exception;
64 using std::list;
65 using std::make_shared;
66 using std::map;
67 using std::set;
68 using std::shared_ptr;
69 using std::string;
70 using std::vector;
71 using std::weak_ptr;
72 using boost::optional;
73 #if BOOST_VERSION >= 106100
74 using namespace boost::placeholders;
75 #endif
76 using dcp::ArrayData;
77 using dcp::Data;
78 using dcp::raw_convert;
79 using namespace dcpomatic;
80
81
82 int const ReelWriter::_info_size = 48;
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 (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_nonexistent_frame > 0 ? dcp::PictureAsset::Behaviour::OVERWRITE_EXISTING : dcp::PictureAsset::Behaviour::MAKE_NEW);
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                         /* Always make 16-channel sound assets for SMPTE; libdcp will pad unused channels for us */
182                         standard == dcp::Standard::SMPTE ? MAX_DCP_AUDIO_CHANNELS : film()->audio_channels(),
183                         lang ? *lang : dcp::LanguageTag("en-US"),
184                         standard
185                         );
186
187                 _sound_asset->set_metadata (mxf_metadata());
188
189                 if (film()->encrypted()) {
190                         _sound_asset->set_key (film()->key());
191                 }
192
193                 DCPOMATIC_ASSERT (film()->directory());
194
195                 /* Write the sound asset into the film directory so that we leave the creation
196                    of the DCP directory until the last minute.
197                 */
198                 _sound_asset_writer = _sound_asset->start_write (
199                         film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary),
200                         film()->audio_channels(),
201                         film()->contains_atmos_content() ? dcp::SoundAsset::AtmosSync::ENABLED : dcp::SoundAsset::AtmosSync::DISABLED,
202                         film()->limit_to_smpte_bv20() ? dcp::SoundAsset::MCASubDescriptors::DISABLED : dcp::SoundAsset::MCASubDescriptors::ENABLED
203                         );
204         }
205
206         _default_font = dcp::ArrayData(default_font_file());
207 }
208
209
210 /** @param frame reel-relative frame */
211 void
212 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
213 {
214         auto handle = film()->info_file_handle(_period, false);
215         handle->get().seek(frame_info_position(frame, eyes), SEEK_SET);
216         handle->get().checked_write(&info.offset, sizeof(info.offset));
217         handle->get().checked_write(&info.size, sizeof(info.size));
218         handle->get().checked_write(info.hash.c_str(), info.hash.size());
219 }
220
221
222 dcp::FrameInfo
223 ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
224 {
225         dcp::FrameInfo frame_info;
226         info->get().seek(frame_info_position(frame, eyes), SEEK_SET);
227         info->get().checked_read(&frame_info.offset, sizeof(frame_info.offset));
228         info->get().checked_read(&frame_info.size, sizeof(frame_info.size));
229
230         char hash_buffer[33];
231         info->get().checked_read(hash_buffer, 32);
232         hash_buffer[32] = '\0';
233         frame_info.hash = hash_buffer;
234
235         return frame_info;
236 }
237
238
239 long
240 ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
241 {
242         switch (eyes) {
243         case Eyes::BOTH:
244                 return frame * _info_size;
245         case Eyes::LEFT:
246                 return frame * _info_size * 2;
247         case Eyes::RIGHT:
248                 return frame * _info_size * 2 + _info_size;
249         default:
250                 DCPOMATIC_ASSERT (false);
251         }
252
253         DCPOMATIC_ASSERT (false);
254 }
255
256
257 Frame
258 ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
259 {
260         auto job = _job.lock ();
261
262         if (job) {
263                 job->sub (_("Checking existing image data"));
264         }
265
266         /* Try to open the existing asset */
267         dcp::File asset_file(asset, "rb");
268         if (!asset_file) {
269                 LOG_GENERAL ("Could not open existing asset at %1 (errno=%2)", asset.string(), errno);
270                 return 0;
271         } else {
272                 LOG_GENERAL ("Opened existing asset at %1", asset.string());
273         }
274
275         shared_ptr<InfoFileHandle> info_file;
276
277         try {
278                 info_file = film()->info_file_handle (_period, true);
279         } catch (OpenFileError &) {
280                 LOG_GENERAL_NC ("Could not open film info file");
281                 return 0;
282         }
283
284         /* Offset of the last dcp::FrameInfo in the info file */
285         int const n = (boost::filesystem::file_size(info_file->get().path()) / _info_size) - 1;
286         LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->get().path()), _info_size);
287
288         Frame first_nonexistent_frame;
289         if (film()->three_d()) {
290                 /* Start looking at the last left frame */
291                 first_nonexistent_frame = n / 2;
292         } else {
293                 first_nonexistent_frame = n;
294         }
295
296         while (!existing_picture_frame_ok(asset_file, info_file, first_nonexistent_frame) && first_nonexistent_frame > 0) {
297                 --first_nonexistent_frame;
298         }
299
300         if (!film()->three_d() && first_nonexistent_frame > 0) {
301                 /* If we are doing 3D we might have found a good L frame with no R, so only
302                    do this if we're in 2D and we've just found a good B(oth) frame.
303                 */
304                 ++first_nonexistent_frame;
305         }
306
307         LOG_GENERAL ("Proceeding with first nonexistent frame %1", first_nonexistent_frame);
308
309         return first_nonexistent_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[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(_last_written[eyes]->data(), _last_written[eyes]->size());
364         write_frame_info (frame, eyes, fin);
365 }
366
367
368 void
369 ReelWriter::finish (boost::filesystem::path output_dcp)
370 {
371         if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
372                 /* Nothing was written to the picture asset */
373                 LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
374                 _picture_asset.reset ();
375         }
376
377         if (_sound_asset_writer && !_sound_asset_writer->finalize ()) {
378                 /* Nothing was written to the sound asset */
379                 _sound_asset.reset ();
380         }
381
382         /* Hard-link any video asset file into the DCP */
383         if (_picture_asset) {
384                 DCPOMATIC_ASSERT (_picture_asset->file());
385                 boost::filesystem::path video_from = _picture_asset->file().get();
386                 boost::filesystem::path video_to = output_dcp;
387                 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
388                 /* There may be an existing "to" file if we are recreating a DCP in the same place without
389                    changing any video.
390                 */
391                 boost::system::error_code ec;
392                 boost::filesystem::remove (video_to, ec);
393
394                 boost::filesystem::create_hard_link (video_from, video_to, ec);
395                 if (ec) {
396                         LOG_WARNING("Hard-link failed (%1); copying instead", error_details(ec));
397                         auto job = _job.lock ();
398                         if (job) {
399                                 job->sub (_("Copying video file into DCP"));
400                                 try {
401                                         copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
402                                 } catch (exception& e) {
403                                         LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
404                                         throw FileError (e.what(), video_from);
405                                 }
406                         } else {
407                                 boost::filesystem::copy_file (video_from, video_to, ec);
408                                 if (ec) {
409                                         LOG_ERROR("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), error_details(ec));
410                                         throw FileError (ec.message(), video_from);
411                                 }
412                         }
413                 }
414
415                 _picture_asset->set_file (video_to);
416         }
417
418         /* Move the audio asset into the DCP */
419         if (_sound_asset) {
420                 boost::filesystem::path audio_to = output_dcp;
421                 auto const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
422                 audio_to /= aaf;
423
424                 boost::system::error_code ec;
425                 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
426                 if (ec) {
427                         throw FileError (
428                                 String::compose(_("could not move audio asset into the DCP (%1)"), error_details(ec)), aaf
429                                 );
430                 }
431
432                 _sound_asset->set_file (audio_to);
433         }
434
435         if (_atmos_asset) {
436                 _atmos_asset_writer->finalize ();
437                 boost::filesystem::path atmos_to = output_dcp;
438                 auto const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
439                 atmos_to /= aaf;
440
441                 boost::system::error_code ec;
442                 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
443                 if (ec) {
444                         throw FileError (
445                                 String::compose(_("could not move atmos asset into the DCP (%1)"), error_details(ec)), aaf
446                                 );
447                 }
448
449                 _atmos_asset->set_file (atmos_to);
450         }
451 }
452
453
454 /** Try to make a ReelAsset for a subtitles or closed captions in a given period in the DCP.
455  *  A SubtitleAsset can be provided, or we will use one from @ref refs if not.
456  */
457 template <class Interop, class SMPTE, class Result>
458 shared_ptr<Result>
459 maybe_add_text (
460         shared_ptr<dcp::SubtitleAsset> asset,
461         int64_t picture_duration,
462         shared_ptr<dcp::Reel> reel,
463         int reel_index,
464         int reel_count,
465         optional<string> content_summary,
466         list<ReferencedReelAsset> const & refs,
467         FontIdMap const& fonts,
468         shared_ptr<dcpomatic::Font> chosen_interop_font,
469         dcp::ArrayData default_font,
470         shared_ptr<const Film> film,
471         DCPTimePeriod period,
472         boost::filesystem::path output_dcp,
473         bool text_only
474         )
475 {
476         Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
477
478         shared_ptr<Result> reel_asset;
479
480         if (asset) {
481                 if (film->interop()) {
482                         if (chosen_interop_font) {
483                                 /* We only add one font, as Interop will ignore subsequent ones (and some validators will
484                                  * complain if they are even present)
485                                  */
486                                 asset->add_font(fonts.get(chosen_interop_font), chosen_interop_font->data().get_value_or(default_font));
487                         }
488                 } else {
489                         for (auto const& font: fonts.map()) {
490                                 asset->add_font(font.second, font.first->data().get_value_or(default_font));
491                         }
492                 }
493
494                 if (auto interop = dynamic_pointer_cast<dcp::InteropSubtitleAsset>(asset)) {
495                         auto directory = output_dcp / interop->id ();
496                         boost::filesystem::create_directories (directory);
497                         interop->write (directory / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".xml"));
498                         reel_asset = make_shared<Interop> (
499                                 interop,
500                                 dcp::Fraction(film->video_frame_rate(), 1),
501                                 picture_duration,
502                                 0
503                                 );
504                 } else if (auto smpte = dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)) {
505                         /* All our assets should be the same length; use the picture asset length here
506                            as a reference to set the subtitle one.  We'll use the duration rather than
507                            the intrinsic duration; we don't care if the picture asset has been trimmed, we're
508                            just interested in its presentation length.
509                         */
510                         smpte->set_intrinsic_duration(picture_duration);
511                         smpte->write (
512                                 output_dcp / subtitle_asset_filename(asset, reel_index, reel_count, content_summary, ".mxf")
513                                 );
514                         reel_asset = make_shared<SMPTE> (
515                                 smpte,
516                                 dcp::Fraction(film->video_frame_rate(), 1),
517                                 picture_duration,
518                                 0
519                                 );
520                 }
521
522         } else {
523                 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
524                 for (auto j: refs) {
525                         auto k = dynamic_pointer_cast<Result> (j.asset);
526                         if (k && j.period == period) {
527                                 reel_asset = k;
528                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
529                                 if (k->hash()) {
530                                         k->asset_ref()->set_hash (k->hash().get());
531                                 }
532                         }
533                 }
534         }
535
536         if (reel_asset) {
537                 if (!text_only && reel_asset->actual_duration() != period_duration) {
538                         throw ProgrammingError (
539                                 __FILE__, __LINE__,
540                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
541                                 );
542                 }
543                 reel->add (reel_asset);
544         }
545
546         return reel_asset;
547 }
548
549
550 shared_ptr<dcp::ReelPictureAsset>
551 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
552 {
553         shared_ptr<dcp::ReelPictureAsset> reel_asset;
554
555         if (_picture_asset) {
556                 /* We have made a picture asset of our own.  Put it into the reel */
557                 auto mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
558                 if (mono) {
559                         reel_asset = make_shared<dcp::ReelMonoPictureAsset>(mono, 0);
560                 }
561
562                 auto stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
563                 if (stereo) {
564                         reel_asset = make_shared<dcp::ReelStereoPictureAsset>(stereo, 0);
565                 }
566         } else {
567                 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
568                 /* We don't have a picture asset of our own; hopefully we have one to reference */
569                 for (auto j: refs) {
570                         auto k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
571                         if (k) {
572                                 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
573                         }
574                         if (k && j.period == _period) {
575                                 reel_asset = k;
576                         }
577                 }
578         }
579
580         Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
581
582         DCPOMATIC_ASSERT (reel_asset);
583         if (reel_asset->duration() != period_duration) {
584                 throw ProgrammingError (
585                         __FILE__, __LINE__,
586                         String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
587                         );
588         }
589         reel->add (reel_asset);
590
591         /* If we have a hash for this asset in the CPL, assume that it is correct */
592         if (reel_asset->hash()) {
593                 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
594         }
595
596         return reel_asset;
597 }
598
599
600 void
601 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
602 {
603         shared_ptr<dcp::ReelSoundAsset> reel_asset;
604
605         if (_sound_asset) {
606                 /* We have made a sound asset of our own.  Put it into the reel */
607                 reel_asset = make_shared<dcp::ReelSoundAsset>(_sound_asset, 0);
608         } else {
609                 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
610                 /* We don't have a sound asset of our own; hopefully we have one to reference */
611                 for (auto j: refs) {
612                         auto k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
613                         if (k) {
614                                 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
615                         }
616                         if (k && j.period == _period) {
617                                 reel_asset = k;
618                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
619                                 if (k->hash()) {
620                                         k->asset_ref()->set_hash (k->hash().get());
621                                 }
622                         }
623                 }
624         }
625
626         auto const period_duration = _period.duration().frames_round(film()->video_frame_rate());
627
628         DCPOMATIC_ASSERT (reel_asset);
629         if (reel_asset->actual_duration() != period_duration) {
630                 LOG_ERROR (
631                         "Reel sound asset has length %1 but reel period is %2",
632                         reel_asset->actual_duration(),
633                         period_duration
634                         );
635                 if (reel_asset->actual_duration() != period_duration) {
636                         throw ProgrammingError (
637                                 __FILE__, __LINE__,
638                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
639                                 );
640                 }
641
642         }
643         reel->add (reel_asset);
644 }
645
646
647 void
648 ReelWriter::create_reel_text (
649         shared_ptr<dcp::Reel> reel,
650         list<ReferencedReelAsset> const & refs,
651         FontIdMap const& fonts,
652         shared_ptr<dcpomatic::Font> chosen_interop_font,
653         int64_t duration,
654         boost::filesystem::path output_dcp,
655         bool ensure_subtitles,
656         set<DCPTextTrack> ensure_closed_captions
657         ) const
658 {
659         auto subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
660                 _subtitle_asset, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
661                 );
662
663         if (subtitle) {
664                 /* We have a subtitle asset that we either made or are referencing */
665                 if (auto main_language = film()->subtitle_languages().first) {
666                         subtitle->set_language (*main_language);
667                 }
668         } else if (ensure_subtitles) {
669                 /* We had no subtitle asset, but we've been asked to make sure there is one */
670                 subtitle = maybe_add_text<dcp::ReelInteropSubtitleAsset, dcp::ReelSMPTESubtitleAsset, dcp::ReelSubtitleAsset> (
671                         empty_text_asset(TextType::OPEN_SUBTITLE, optional<DCPTextTrack>(), true),
672                         duration,
673                         reel,
674                         _reel_index,
675                         _reel_count,
676                         _content_summary,
677                         refs,
678                         fonts,
679                         chosen_interop_font,
680                         _default_font,
681                         film(),
682                         _period,
683                         output_dcp,
684                         _text_only
685                         );
686         }
687
688         for (auto const& i: _closed_caption_assets) {
689                 auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
690                         i.second, duration, reel, _reel_index, _reel_count, _content_summary, refs, fonts, chosen_interop_font, _default_font, film(), _period, output_dcp, _text_only
691                         );
692                 DCPOMATIC_ASSERT (a);
693                 a->set_annotation_text (i.first.name);
694                 if (i.first.language) {
695                         a->set_language (i.first.language.get());
696                 }
697
698                 ensure_closed_captions.erase (i.first);
699         }
700
701         /* Make empty tracks for anything we've been asked to ensure but that we haven't added */
702         for (auto i: ensure_closed_captions) {
703                 auto a = maybe_add_text<dcp::ReelInteropClosedCaptionAsset, dcp::ReelSMPTEClosedCaptionAsset, dcp::ReelClosedCaptionAsset> (
704                         empty_text_asset(TextType::CLOSED_CAPTION, i, true),
705                         duration,
706                         reel,
707                         _reel_index,
708                         _reel_count,
709                         _content_summary,
710                         refs,
711                         fonts,
712                         chosen_interop_font,
713                         _default_font,
714                         film(),
715                         _period,
716                         output_dcp,
717                         _text_only
718                         );
719                 DCPOMATIC_ASSERT (a);
720                 a->set_annotation_text (i.name);
721                 if (i.language) {
722                         a->set_language (i.language.get());
723                 }
724         }
725 }
726
727
728 void
729 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
730 {
731         auto markers = film()->markers();
732         film()->add_ffoc_lfoc(markers);
733         Film::Markers reel_markers;
734         for (auto const& i: markers) {
735                 if (_period.contains(i.second)) {
736                         reel_markers[i.first] = i.second;
737                 }
738         }
739
740         if (!reel_markers.empty ()) {
741                 auto ma = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(film()->video_frame_rate(), 1), reel->duration());
742                 for (auto const& i: reel_markers) {
743                         DCPTime relative = i.second - _period.from;
744                         auto hmsf = relative.split (film()->video_frame_rate());
745                         ma->set (i.first, dcp::Time(hmsf.h, hmsf.m, hmsf.s, hmsf.f, film()->video_frame_rate()));
746                 }
747                 reel->add (ma);
748         }
749 }
750
751
752 /** @param ensure_subtitles true to make sure the reel has a subtitle asset.
753  *  @param ensure_closed_captions make sure the reel has these closed caption tracks.
754  */
755 shared_ptr<dcp::Reel>
756 ReelWriter::create_reel (
757         list<ReferencedReelAsset> const & refs,
758         FontIdMap const & fonts,
759         shared_ptr<dcpomatic::Font> chosen_interop_font,
760         boost::filesystem::path output_dcp,
761         bool ensure_subtitles,
762         set<DCPTextTrack> ensure_closed_captions
763         )
764 {
765         LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
766
767         auto reel = make_shared<dcp::Reel>();
768
769         /* This is a bit of a hack; in the strange `_text_only' mode we have no picture, so we don't know
770          * how long the subtitle / CCAP assets should be.  However, since we're only writing them to see
771          * how big they are, we don't care about that.
772          */
773         int64_t duration = 0;
774         if (!_text_only) {
775                 auto reel_picture_asset = create_reel_picture (reel, refs);
776                 duration = reel_picture_asset->actual_duration ();
777                 create_reel_sound (reel, refs);
778                 create_reel_markers (reel);
779         }
780
781         create_reel_text (reel, refs, fonts, chosen_interop_font, duration, output_dcp, ensure_subtitles, ensure_closed_captions);
782
783         if (_atmos_asset) {
784                 reel->add (make_shared<dcp::ReelAtmosAsset>(_atmos_asset, 0));
785         }
786
787         return reel;
788 }
789
790 void
791 ReelWriter::calculate_digests (std::function<void (float)> set_progress)
792 try
793 {
794         if (_picture_asset) {
795                 _picture_asset->hash (set_progress);
796         }
797
798         if (_sound_asset) {
799                 _sound_asset->hash (set_progress);
800         }
801
802         if (_atmos_asset) {
803                 _atmos_asset->hash (set_progress);
804         }
805 } catch (boost::thread_interrupted) {
806         /* set_progress contains an interruption_point, so any of these methods
807          * may throw thread_interrupted, at which point we just give up.
808          */
809 }
810
811
812 Frame
813 ReelWriter::start () const
814 {
815         return _period.from.frames_floor (film()->video_frame_rate());
816 }
817
818
819 void
820 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
821 {
822         if (!_sound_asset_writer) {
823                 return;
824         }
825
826         DCPOMATIC_ASSERT (audio);
827         _sound_asset_writer->write(audio->data(), audio->channels(), audio->frames());
828 }
829
830
831 shared_ptr<dcp::SubtitleAsset>
832 ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track, bool with_dummy) const
833 {
834         shared_ptr<dcp::SubtitleAsset> asset;
835
836         auto lang = film()->subtitle_languages();
837         if (film()->interop()) {
838                 auto s = make_shared<dcp::InteropSubtitleAsset>();
839                 s->set_movie_title (film()->name());
840                 if (type == TextType::OPEN_SUBTITLE) {
841                         s->set_language (lang.first ? lang.first->to_string() : "Unknown");
842                 } else if (track->language) {
843                         s->set_language (track->language->to_string());
844                 }
845                 s->set_reel_number (raw_convert<string> (_reel_index + 1));
846                 asset = s;
847         } else {
848                 auto s = make_shared<dcp::SMPTESubtitleAsset>();
849                 s->set_content_title_text (film()->name());
850                 s->set_metadata (mxf_metadata());
851                 if (type == TextType::OPEN_SUBTITLE && lang.first) {
852                         s->set_language (*lang.first);
853                 } else if (track && track->language) {
854                         s->set_language (dcp::LanguageTag(track->language->to_string()));
855                 }
856                 s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
857                 s->set_reel_number (_reel_index + 1);
858                 s->set_time_code_rate (film()->video_frame_rate());
859                 s->set_start_time (dcp::Time ());
860                 if (film()->encrypted()) {
861                         s->set_key (film()->key());
862                 }
863                 asset = s;
864         }
865
866         if (with_dummy) {
867                 asset->add(
868                         std::make_shared<dcp::SubtitleString>(
869                                 optional<std::string>(),
870                                 false,
871                                 false,
872                                 false,
873                                 dcp::Colour(),
874                                 42,
875                                 1.0,
876                                 dcp::Time(0, 0, 0, 0, 24),
877                                 dcp::Time(0, 0, 1, 0, 24),
878                                 0.5,
879                                 dcp::HAlign::CENTER,
880                                 0.5,
881                                 dcp::VAlign::CENTER,
882                                 0,
883                                 dcp::Direction::LTR,
884                                 " ",
885                                 dcp::Effect::NONE,
886                                 dcp::Colour(),
887                                 dcp::Time(),
888                                 dcp::Time(),
889                                 0
890                                 )
891                        );
892         }
893
894         return asset;
895 }
896
897
898 float
899 ReelWriter::convert_vertical_position(StringText const& subtitle, dcp::SubtitleStandard to) const
900 {
901         if (dcp::uses_baseline(subtitle.valign_standard) == dcp::uses_baseline(to)) {
902                 /* The from and to standards use the same alignment reference */
903                 return subtitle.v_position();
904         }
905
906         auto const baseline_to_bottom = _font_metrics.baseline_to_bottom(subtitle);
907         auto const height = _font_metrics.height(subtitle);
908
909         float correction = 0;
910         switch (subtitle.v_align()) {
911         case dcp::VAlign::TOP:
912                 correction = height - baseline_to_bottom;
913                 break;
914         case dcp::VAlign::CENTER:
915                 correction = (height / 2) - baseline_to_bottom;
916                 break;
917         case dcp::VAlign::BOTTOM:
918                 correction = baseline_to_bottom;
919                 break;
920         }
921
922         return subtitle.v_position() + (dcp::uses_bounding_box(subtitle.valign_standard) ? correction : -correction);
923 }
924
925
926 void
927 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period, FontIdMap const& fonts)
928 {
929         shared_ptr<dcp::SubtitleAsset> asset;
930
931         switch (type) {
932         case TextType::OPEN_SUBTITLE:
933                 asset = _subtitle_asset;
934                 break;
935         case TextType::CLOSED_CAPTION:
936                 DCPOMATIC_ASSERT (track);
937                 asset = _closed_caption_assets[*track];
938                 break;
939         default:
940                 DCPOMATIC_ASSERT (false);
941         }
942
943         if (!asset) {
944                 asset = empty_text_asset (type, track, false);
945         }
946
947         switch (type) {
948         case TextType::OPEN_SUBTITLE:
949                 _subtitle_asset = asset;
950                 break;
951         case TextType::CLOSED_CAPTION:
952                 DCPOMATIC_ASSERT (track);
953                 _closed_caption_assets[*track] = asset;
954                 break;
955         default:
956                 DCPOMATIC_ASSERT (false);
957         }
958
959         /* timecode rate for subtitles we emit; we might as well stick to ms accuracy here, I think */
960         auto const tcr = 1000;
961
962         for (auto i: subs.string) {
963                 i.set_in  (dcp::Time(period.from.seconds() - _period.from.seconds(), tcr));
964                 i.set_out (dcp::Time(period.to.seconds() - _period.from.seconds(), tcr));
965                 i.set_v_position(convert_vertical_position(i, film()->interop() ? dcp::SubtitleStandard::INTEROP : dcp::SubtitleStandard::SMPTE_2014));
966                 auto sub = make_shared<dcp::SubtitleString>(i);
967                 if (type == TextType::OPEN_SUBTITLE) {
968                         sub->set_font(fonts.get(i.font));
969                 }
970                 asset->add(sub);
971         }
972
973         for (auto i: subs.bitmap) {
974                 asset->add (
975                         make_shared<dcp::SubtitleImage>(
976                                 image_as_png(i.image),
977                                 dcp::Time(period.from.seconds() - _period.from.seconds(), tcr),
978                                 dcp::Time(period.to.seconds() - _period.from.seconds(), tcr),
979                                 i.rectangle.x, dcp::HAlign::LEFT, i.rectangle.y, dcp::VAlign::TOP, 0,
980                                 dcp::Time(), dcp::Time()
981                                 )
982                         );
983         }
984 }
985
986
987 bool
988 ReelWriter::existing_picture_frame_ok (dcp::File& asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
989 {
990         LOG_GENERAL ("Checking existing picture frame %1", frame);
991
992         /* Read the data from the info file; for 3D we just check the left
993            frames until we find a good one.
994         */
995         auto const info = read_frame_info (info_file, frame, film()->three_d() ? Eyes::LEFT : Eyes::BOTH);
996
997         bool ok = true;
998
999         /* Read the data from the asset and hash it */
1000         asset_file.seek(info.offset, SEEK_SET);
1001         ArrayData data (info.size);
1002         size_t const read = asset_file.read(data.data(), 1, data.size());
1003         LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
1004         if (read != static_cast<size_t> (data.size ())) {
1005                 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
1006                 ok = false;
1007         } else {
1008                 Digester digester;
1009                 digester.add (data.data(), data.size());
1010                 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
1011                 if (digester.get() != info.hash) {
1012                         LOG_GENERAL ("Existing frame %1 failed hash check", frame);
1013                         ok = false;
1014                 }
1015         }
1016
1017         return ok;
1018 }