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