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