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