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