Use a weak_ptr<Film> in ReelWriter.
[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 ()
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;
368                 video_to /= film()->dir(film()->dcp_name());
369                 video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
370                 /* There may be an existing "to" file if we are recreating a DCP in the same place without
371                    changing any video.
372                 */
373                 boost::system::error_code ec;
374                 boost::filesystem::remove (video_to, ec);
375
376                 boost::filesystem::create_hard_link (video_from, video_to, ec);
377                 if (ec) {
378                         LOG_WARNING_NC ("Hard-link failed; copying instead");
379                         shared_ptr<Job> job = _job.lock ();
380                         if (job) {
381                                 job->sub (_("Copying video file into DCP"));
382                                 try {
383                                         copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
384                                 } catch (exception& e) {
385                                         LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
386                                         throw FileError (e.what(), video_from);
387                                 }
388                         } else {
389                                 boost::filesystem::copy_file (video_from, video_to, ec);
390                                 if (ec) {
391                                         LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
392                                         throw FileError (ec.message(), video_from);
393                                 }
394                         }
395                 }
396
397                 _picture_asset->set_file (video_to);
398         }
399
400         /* Move the audio asset into the DCP */
401         if (_sound_asset) {
402                 boost::filesystem::path audio_to;
403                 audio_to /= film()->dir(film()->dcp_name ());
404                 string const aaf = audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary);
405                 audio_to /= aaf;
406
407                 boost::system::error_code ec;
408                 boost::filesystem::rename (film()->file(aaf), audio_to, ec);
409                 if (ec) {
410                         throw FileError (
411                                 String::compose (_("could not move audio asset into the DCP (%1)"), ec.value ()), aaf
412                                 );
413                 }
414
415                 _sound_asset->set_file (audio_to);
416         }
417
418         if (_atmos_asset) {
419                 _atmos_asset_writer->finalize ();
420                 boost::filesystem::path atmos_to;
421                 atmos_to /= film()->dir(film()->dcp_name());
422                 string const aaf = atmos_asset_filename (_atmos_asset, _reel_index, _reel_count, _content_summary);
423                 atmos_to /= aaf;
424
425                 boost::system::error_code ec;
426                 boost::filesystem::rename (film()->file(aaf), atmos_to, ec);
427                 if (ec) {
428                         throw FileError (
429                                 String::compose (_("could not move atmos asset into the DCP (%1)"), ec.value ()), aaf
430                                 );
431                 }
432
433                 _atmos_asset->set_file (atmos_to);
434         }
435 }
436
437 template <class T>
438 shared_ptr<T>
439 maybe_add_text (
440         shared_ptr<dcp::SubtitleAsset> asset,
441         int64_t picture_duration,
442         shared_ptr<dcp::Reel> reel,
443         list<ReferencedReelAsset> const & refs,
444         list<shared_ptr<Font> > const & fonts,
445         shared_ptr<const Film> film,
446         DCPTimePeriod period
447         )
448 {
449         Frame const period_duration = period.duration().frames_round(film->video_frame_rate());
450
451         shared_ptr<T> reel_asset;
452
453         if (asset) {
454                 /* Add the font to the subtitle content */
455                 BOOST_FOREACH (shared_ptr<Font> j, fonts) {
456                         asset->add_font (j->id(), j->file().get_value_or(default_font_file()));
457                 }
458
459                 if (dynamic_pointer_cast<dcp::InteropSubtitleAsset> (asset)) {
460                         boost::filesystem::path directory = film->dir (film->dcp_name ()) / asset->id ();
461                         boost::filesystem::create_directories (directory);
462                         asset->write (directory / ("sub_" + asset->id() + ".xml"));
463                 } else {
464                         /* All our assets should be the same length; use the picture asset length here
465                            as a reference to set the subtitle one.  We'll use the duration rather than
466                            the intrinsic duration; we don't care if the picture asset has been trimmed, we're
467                            just interested in its presentation length.
468                         */
469                         dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(asset)->set_intrinsic_duration (picture_duration);
470
471                         asset->write (
472                                 film->dir(film->dcp_name()) / ("sub_" + asset->id() + ".mxf")
473                                 );
474                 }
475
476                 reel_asset.reset (
477                         new T (
478                                 asset,
479                                 dcp::Fraction (film->video_frame_rate(), 1),
480                                 picture_duration,
481                                 0
482                                 )
483                         );
484         } else {
485                 /* We don't have a subtitle asset of our own; hopefully we have one to reference */
486                 BOOST_FOREACH (ReferencedReelAsset j, refs) {
487                         shared_ptr<T> k = dynamic_pointer_cast<T> (j.asset);
488                         if (k && j.period == period) {
489                                 reel_asset = k;
490                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
491                                 if (k->hash()) {
492                                         k->asset_ref()->set_hash (k->hash().get());
493                                 }
494                         }
495                 }
496         }
497
498         if (reel_asset) {
499                 if (reel_asset->actual_duration() != period_duration) {
500                         throw ProgrammingError (
501                                 __FILE__, __LINE__,
502                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
503                                 );
504                 }
505                 reel->add (reel_asset);
506         }
507
508         return reel_asset;
509 }
510
511
512 shared_ptr<dcp::ReelPictureAsset>
513 ReelWriter::create_reel_picture (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
514 {
515         shared_ptr<dcp::ReelPictureAsset> reel_asset;
516
517         if (_picture_asset) {
518                 /* We have made a picture asset of our own.  Put it into the reel */
519                 shared_ptr<dcp::MonoPictureAsset> mono = dynamic_pointer_cast<dcp::MonoPictureAsset> (_picture_asset);
520                 if (mono) {
521                         reel_asset.reset (new dcp::ReelMonoPictureAsset (mono, 0));
522                 }
523
524                 shared_ptr<dcp::StereoPictureAsset> stereo = dynamic_pointer_cast<dcp::StereoPictureAsset> (_picture_asset);
525                 if (stereo) {
526                         reel_asset.reset (new dcp::ReelStereoPictureAsset (stereo, 0));
527                 }
528         } else {
529                 LOG_GENERAL ("no picture asset of our own; look through %1", refs.size());
530                 /* We don't have a picture asset of our own; hopefully we have one to reference */
531                 BOOST_FOREACH (ReferencedReelAsset j, refs) {
532                         shared_ptr<dcp::ReelPictureAsset> k = dynamic_pointer_cast<dcp::ReelPictureAsset> (j.asset);
533                         if (k) {
534                                 LOG_GENERAL ("candidate picture asset period is %1-%2", j.period.from.get(), j.period.to.get());
535                         }
536                         if (k && j.period == _period) {
537                                 reel_asset = k;
538                         }
539                 }
540         }
541
542         Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
543
544         DCPOMATIC_ASSERT (reel_asset);
545         if (reel_asset->duration() != period_duration) {
546                 throw ProgrammingError (
547                         __FILE__, __LINE__,
548                         String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
549                         );
550         }
551         reel->add (reel_asset);
552
553         /* If we have a hash for this asset in the CPL, assume that it is correct */
554         if (reel_asset->hash()) {
555                 reel_asset->asset_ref()->set_hash (reel_asset->hash().get());
556         }
557
558         return reel_asset;
559 }
560
561
562 void
563 ReelWriter::create_reel_sound (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs) const
564 {
565         shared_ptr<dcp::ReelSoundAsset> reel_asset;
566
567         if (_sound_asset) {
568                 /* We have made a sound asset of our own.  Put it into the reel */
569                 reel_asset.reset (new dcp::ReelSoundAsset(_sound_asset, 0));
570         } else {
571                 LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
572                 /* We don't have a sound asset of our own; hopefully we have one to reference */
573                 BOOST_FOREACH (ReferencedReelAsset j, refs) {
574                         shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
575                         if (k) {
576                                 LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
577                         }
578                         if (k && j.period == _period) {
579                                 reel_asset = k;
580                                 /* If we have a hash for this asset in the CPL, assume that it is correct */
581                                 if (k->hash()) {
582                                         k->asset_ref()->set_hash (k->hash().get());
583                                 }
584                         }
585                 }
586         }
587
588         Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate());
589
590         DCPOMATIC_ASSERT (reel_asset);
591         if (reel_asset->actual_duration() != period_duration) {
592                 LOG_ERROR (
593                         "Reel sound asset has length %1 but reel period is %2",
594                         reel_asset->actual_duration(),
595                         period_duration
596                         );
597                 if (reel_asset->actual_duration() != period_duration) {
598                         throw ProgrammingError (
599                                 __FILE__, __LINE__,
600                                 String::compose ("%1 vs %2", reel_asset->actual_duration(), period_duration)
601                                 );
602                 }
603
604         }
605         reel->add (reel_asset);
606 }
607
608
609 void
610 ReelWriter::create_reel_text (shared_ptr<dcp::Reel> reel, list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const& fonts, int64_t duration) const
611 {
612         shared_ptr<dcp::ReelSubtitleAsset> subtitle = maybe_add_text<dcp::ReelSubtitleAsset> (_subtitle_asset, duration, reel, refs, fonts, film(), _period);
613         if (subtitle && !film()->subtitle_languages().empty()) {
614                 subtitle->set_language (film()->subtitle_languages().front());
615         }
616
617         for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
618                 shared_ptr<dcp::ReelClosedCaptionAsset> a = maybe_add_text<dcp::ReelClosedCaptionAsset> (
619                         i->second, duration, reel, refs, fonts, film(), _period
620                         );
621                 if (a) {
622                         a->set_annotation_text (i->first.name);
623                         if (!i->first.language.empty()) {
624                                 a->set_language (dcp::LanguageTag(i->first.language));
625                         }
626                 }
627         }
628 }
629
630
631
632 void
633 ReelWriter::create_reel_markers (shared_ptr<dcp::Reel> reel) const
634 {
635         Film::Markers markers = film()->markers();
636         film()->add_ffoc_lfoc(markers);
637         Film::Markers reel_markers;
638         for (Film::Markers::const_iterator i = markers.begin(); i != markers.end(); ++i) {
639                 if (_period.contains(i->second)) {
640                         reel_markers[i->first] = i->second;
641                 }
642         }
643
644         if (!reel_markers.empty ()) {
645                 shared_ptr<dcp::ReelMarkersAsset> ma (new dcp::ReelMarkersAsset(dcp::Fraction(film()->video_frame_rate(), 1), 0));
646                 for (map<dcp::Marker, DCPTime>::const_iterator i = reel_markers.begin(); i != reel_markers.end(); ++i) {
647                         int h, m, s, f;
648                         DCPTime relative = i->second - _period.from;
649                         relative.split (film()->video_frame_rate(), h, m, s, f);
650                         ma->set (i->first, dcp::Time(h, m, s, f, film()->video_frame_rate()));
651                 }
652                 reel->add (ma);
653         }
654 }
655
656
657 shared_ptr<dcp::Reel>
658 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
659 {
660         LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
661
662         shared_ptr<dcp::Reel> reel (new dcp::Reel());
663         shared_ptr<dcp::ReelPictureAsset> reel_picture_asset = create_reel_picture (reel, refs);
664         create_reel_sound (reel, refs);
665         create_reel_text (reel, refs, fonts, reel_picture_asset->actual_duration());
666         create_reel_markers (reel);
667
668         if (_atmos_asset) {
669                 reel->add (shared_ptr<dcp::ReelAtmosAsset>(new dcp::ReelAtmosAsset(_atmos_asset, 0)));
670         }
671
672         return reel;
673 }
674
675 void
676 ReelWriter::calculate_digests (boost::function<void (float)> set_progress)
677 {
678         if (_picture_asset) {
679                 _picture_asset->hash (set_progress);
680         }
681
682         if (_sound_asset) {
683                 _sound_asset->hash (set_progress);
684         }
685
686         if (_atmos_asset) {
687                 _atmos_asset->hash (set_progress);
688         }
689 }
690
691 Frame
692 ReelWriter::start () const
693 {
694         return _period.from.frames_floor (film()->video_frame_rate());
695 }
696
697
698 void
699 ReelWriter::write (shared_ptr<const AudioBuffers> audio)
700 {
701         if (!_sound_asset_writer) {
702                 return;
703         }
704
705         DCPOMATIC_ASSERT (audio);
706         _sound_asset_writer->write (audio->data(), audio->frames());
707 }
708
709 void
710 ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
711 {
712         shared_ptr<dcp::SubtitleAsset> asset;
713
714         switch (type) {
715         case TEXT_OPEN_SUBTITLE:
716                 asset = _subtitle_asset;
717                 break;
718         case TEXT_CLOSED_CAPTION:
719                 DCPOMATIC_ASSERT (track);
720                 asset = _closed_caption_assets[*track];
721                 break;
722         default:
723                 DCPOMATIC_ASSERT (false);
724         }
725
726         if (!asset) {
727                 vector<dcp::LanguageTag> lang = film()->subtitle_languages();
728                 if (film()->interop()) {
729                         shared_ptr<dcp::InteropSubtitleAsset> s (new dcp::InteropSubtitleAsset ());
730                         s->set_movie_title (film()->name());
731                         if (type == TEXT_OPEN_SUBTITLE) {
732                                 s->set_language (lang.empty() ? "Unknown" : lang.front().to_string());
733                         } else if (!track->language.empty()) {
734                                 s->set_language (track->language);
735                         }
736                         s->set_reel_number (raw_convert<string> (_reel_index + 1));
737                         asset = s;
738                 } else {
739                         shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
740                         s->set_content_title_text (film()->name());
741                         s->set_metadata (mxf_metadata());
742                         if (type == TEXT_OPEN_SUBTITLE && !lang.empty()) {
743                                 s->set_language (lang.front());
744                         } else if (track && !track->language.empty()) {
745                                 s->set_language (dcp::LanguageTag(track->language));
746                         }
747                         s->set_edit_rate (dcp::Fraction (film()->video_frame_rate(), 1));
748                         s->set_reel_number (_reel_index + 1);
749                         s->set_time_code_rate (film()->video_frame_rate());
750                         s->set_start_time (dcp::Time ());
751                         if (film()->encrypted()) {
752                                 s->set_key (film()->key());
753                         }
754                         asset = s;
755                 }
756         }
757
758         switch (type) {
759         case TEXT_OPEN_SUBTITLE:
760                 _subtitle_asset = asset;
761                 break;
762         case TEXT_CLOSED_CAPTION:
763                 DCPOMATIC_ASSERT (track);
764                 _closed_caption_assets[*track] = asset;
765                 break;
766         default:
767                 DCPOMATIC_ASSERT (false);
768         }
769
770         BOOST_FOREACH (StringText i, subs.string) {
771                 /* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
772                 i.set_in  (i.in()  - dcp::Time (_period.from.seconds(), i.in().tcr));
773                 i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
774                 asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
775         }
776
777         BOOST_FOREACH (BitmapText i, subs.bitmap) {
778                 asset->add (
779                         shared_ptr<dcp::Subtitle>(
780                                 new dcp::SubtitleImage(
781                                         i.image->as_png(),
782                                         dcp::Time(period.from.seconds() - _period.from.seconds(), film()->video_frame_rate()),
783                                         dcp::Time(period.to.seconds() - _period.from.seconds(), film()->video_frame_rate()),
784                                         i.rectangle.x, dcp::HALIGN_LEFT, i.rectangle.y, dcp::VALIGN_TOP,
785                                         dcp::Time(), dcp::Time()
786                                         )
787                                 )
788                         );
789         }
790 }
791
792 bool
793 ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
794 {
795         LOG_GENERAL ("Checking existing picture frame %1", frame);
796
797         /* Read the data from the info file; for 3D we just check the left
798            frames until we find a good one.
799         */
800         dcp::FrameInfo const info = read_frame_info (info_file, frame, film()->three_d() ? EYES_LEFT : EYES_BOTH);
801
802         bool ok = true;
803
804         /* Read the data from the asset and hash it */
805         dcpomatic_fseek (asset_file, info.offset, SEEK_SET);
806         ArrayData data (info.size);
807         size_t const read = fread (data.data(), 1, data.size(), asset_file);
808         LOG_GENERAL ("Read %1 bytes of asset data; wanted %2", read, info.size);
809         if (read != static_cast<size_t> (data.size ())) {
810                 LOG_GENERAL ("Existing frame %1 is incomplete", frame);
811                 ok = false;
812         } else {
813                 Digester digester;
814                 digester.add (data.data(), data.size());
815                 LOG_GENERAL ("Hash %1 vs %2", digester.get(), info.hash);
816                 if (digester.get() != info.hash) {
817                         LOG_GENERAL ("Existing frame %1 failed hash check", frame);
818                         ok = false;
819                 }
820         }
821
822         return ok;
823 }