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