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