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