Another macOS std::list boost::thread SNAFU.
[dcpomatic.git] / src / lib / reel_writer.cc
index 2631489028f22875aa26f5620f036277f49314df..49e09663ff1fed01d9caf5932eb90aeb6e63a158 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -54,6 +54,7 @@
 using std::list;
 using std::string;
 using std::cout;
+using std::exception;
 using std::map;
 using boost::shared_ptr;
 using boost::optional;
@@ -75,40 +76,62 @@ ReelWriter::ReelWriter (
        , _reel_index (reel_index)
        , _reel_count (reel_count)
        , _content_summary (content_summary)
+       , _job (job)
 {
-       /* Create our picture asset in a subdirectory, named according to those
-          film's parameters which affect the video output.  We will hard-link
-          it into the DCP later.
+       /* Create or find our picture asset in a subdirectory, named
+          according to those film's parameters which affect the video
+          output.  We will hard-link it into the DCP later.
        */
 
        dcp::Standard const standard = _film->interop() ? dcp::INTEROP : dcp::SMPTE;
 
-       if (_film->three_d ()) {
-               _picture_asset.reset (new dcp::StereoPictureAsset (dcp::Fraction (_film->video_frame_rate(), 1), standard));
-       } else {
-               _picture_asset.reset (new dcp::MonoPictureAsset (dcp::Fraction (_film->video_frame_rate(), 1), standard));
-       }
+       boost::filesystem::path const asset =
+               _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period);
 
-       _picture_asset->set_size (_film->frame_size ());
+       _first_nonexistant_frame = check_existing_picture_asset (asset);
 
-       if (_film->encrypted ()) {
-               _picture_asset->set_key (_film->key ());
-               _picture_asset->set_context_id (_film->context_id ());
-       }
+       if (_first_nonexistant_frame < period.duration().frames_round(_film->video_frame_rate())) {
+               /* We do not have a complete picture asset.  If there is an
+                  existing asset, break any hard links to it as we are about
+                  to change its contents (if only by changing the IDs); see
+                  #1126.
+               */
+               if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
+                       if (job) {
+                               job->sub (_("Copying old video file"));
+                               copy_in_bits (asset, asset.string() + ".tmp", bind(&Job::set_progress, job.get(), _1, false));
+                       } else {
+                               boost::filesystem::copy_file (asset, asset.string() + ".tmp");
+                       }
+                       boost::filesystem::remove (asset);
+                       boost::filesystem::rename (asset.string() + ".tmp", asset);
+               }
 
-       _picture_asset->set_file (
-               _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period)
-               );
 
-       if (job) {
-               job->sub (_("Checking existing image data"));
-       }
-       _first_nonexistant_frame = check_existing_picture_asset ();
+               if (_film->three_d ()) {
+                       _picture_asset.reset (new dcp::StereoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
+               } else {
+                       _picture_asset.reset (new dcp::MonoPictureAsset(dcp::Fraction(_film->video_frame_rate(), 1), standard));
+               }
 
-       _picture_asset_writer = _picture_asset->start_write (
-               _film->internal_video_asset_dir() / _film->internal_video_asset_filename(_period),
-               _first_nonexistant_frame > 0
-               );
+               _picture_asset->set_size (_film->frame_size());
+
+               if (_film->encrypted ()) {
+                       _picture_asset->set_key (_film->key());
+                       _picture_asset->set_context_id (_film->context_id());
+               }
+
+               _picture_asset->set_file (asset);
+               _picture_asset_writer = _picture_asset->start_write (asset, _first_nonexistant_frame > 0);
+       } else {
+               /* We already have a complete picture asset that we can just re-use */
+               /* XXX: what about if the encryption key changes? */
+               if (_film->three_d ()) {
+                       _picture_asset.reset (new dcp::StereoPictureAsset(asset));
+               } else {
+                       _picture_asset.reset (new dcp::MonoPictureAsset(asset));
+               }
+       }
 
        if (_film->audio_channels ()) {
                _sound_asset.reset (
@@ -134,41 +157,27 @@ ReelWriter::ReelWriter (
 void
 ReelWriter::write_frame_info (Frame frame, Eyes eyes, dcp::FrameInfo info) const
 {
-       FILE* file = 0;
-       boost::filesystem::path info_file = _film->info_file (_period);
-
-       bool const read = boost::filesystem::exists (info_file);
-
-       if (read) {
-               file = fopen_boost (info_file, "r+b");
-       } else {
-               file = fopen_boost (info_file, "wb");
-       }
-
-       if (!file) {
-               throw OpenFileError (info_file, errno, read ? OpenFileError::READ_WRITE : OpenFileError::WRITE);
-       }
-       dcpomatic_fseek (file, frame_info_position (frame, eyes), SEEK_SET);
-       checked_fwrite (&info.offset, sizeof (info.offset), file, info_file);
-       checked_fwrite (&info.size, sizeof (info.size), file, info_file);
-       checked_fwrite (info.hash.c_str(), info.hash.size(), file, info_file);
-       fclose (file);
+       shared_ptr<InfoFileHandle> handle = _film->info_file_handle(_period, false);
+       dcpomatic_fseek (handle->get(), frame_info_position(frame, eyes), SEEK_SET);
+       checked_fwrite (&info.offset, sizeof(info.offset), handle->get(), handle->file());
+       checked_fwrite (&info.size, sizeof (info.size), handle->get(), handle->file());
+       checked_fwrite (info.hash.c_str(), info.hash.size(), handle->get(), handle->file());
 }
 
 dcp::FrameInfo
-ReelWriter::read_frame_info (FILE* file, Frame frame, Eyes eyes) const
+ReelWriter::read_frame_info (shared_ptr<InfoFileHandle> info, Frame frame, Eyes eyes) const
 {
-       dcp::FrameInfo info;
-       dcpomatic_fseek (file, frame_info_position (frame, eyes), SEEK_SET);
-       checked_fread (&info.offset, sizeof(info.offset), file, _film->info_file(_period));
-       checked_fread (&info.size, sizeof(info.size), file, _film->info_file(_period));
+       dcp::FrameInfo frame_info;
+       dcpomatic_fseek (info->get(), frame_info_position(frame, eyes), SEEK_SET);
+       checked_fread (&frame_info.offset, sizeof(frame_info.offset), info->get(), info->file());
+       checked_fread (&frame_info.size, sizeof(frame_info.size), info->get(), info->file());
 
        char hash_buffer[33];
-       checked_fread (hash_buffer, 32, file, _film->info_file(_period));
+       checked_fread (hash_buffer, 32, info->get(), info->file());
        hash_buffer[32] = '\0';
-       info.hash = hash_buffer;
+       frame_info.hash = hash_buffer;
 
-       return info;
+       return frame_info;
 }
 
 long
@@ -189,19 +198,12 @@ ReelWriter::frame_info_position (Frame frame, Eyes eyes) const
 }
 
 Frame
-ReelWriter::check_existing_picture_asset ()
+ReelWriter::check_existing_picture_asset (boost::filesystem::path asset)
 {
-       DCPOMATIC_ASSERT (_picture_asset->file());
-       boost::filesystem::path asset = _picture_asset->file().get();
+       shared_ptr<Job> job = _job.lock ();
 
-       /* If there is an existing asset, break any hard links to it as we are about to change its contents
-          (if only by changing the IDs); see #1126.
-       */
-
-       if (boost::filesystem::exists(asset) && boost::filesystem::hard_link_count(asset) > 1) {
-               boost::filesystem::copy_file (asset, asset.string() + ".tmp");
-               boost::filesystem::remove (asset);
-               boost::filesystem::rename (asset.string() + ".tmp", asset);
+       if (job) {
+               job->sub (_("Checking existing image data"));
        }
 
        /* Try to open the existing asset */
@@ -213,17 +215,20 @@ ReelWriter::check_existing_picture_asset ()
                LOG_GENERAL ("Opened existing asset at %1", asset.string());
        }
 
-       /* Offset of the last dcp::FrameInfo in the info file */
-       int const n = (boost::filesystem::file_size (_film->info_file(_period)) / _info_size) - 1;
-       LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size (_film->info_file(_period)), _info_size);
+       shared_ptr<InfoFileHandle> info_file;
 
-       FILE* info_file = fopen_boost (_film->info_file(_period), "rb");
-       if (!info_file) {
+       try {
+               info_file = _film->info_file_handle (_period, true);
+       } catch (OpenFileError) {
                LOG_GENERAL_NC ("Could not open film info file");
                fclose (asset_file);
                return 0;
        }
 
+       /* Offset of the last dcp::FrameInfo in the info file */
+       int const n = (boost::filesystem::file_size(info_file->file()) / _info_size) - 1;
+       LOG_GENERAL ("The last FI is %1; info file is %2, info size %3", n, boost::filesystem::file_size(info_file->file()), _info_size);
+
        Frame first_nonexistant_frame;
        if (_film->three_d ()) {
                /* Start looking at the last left frame */
@@ -246,7 +251,6 @@ ReelWriter::check_existing_picture_asset ()
        LOG_GENERAL ("Proceeding with first nonexistant frame %1", first_nonexistant_frame);
 
        fclose (asset_file);
-       fclose (info_file);
 
        return first_nonexistant_frame;
 }
@@ -254,6 +258,11 @@ ReelWriter::check_existing_picture_asset ()
 void
 ReelWriter::write (optional<Data> encoded, Frame frame, Eyes eyes)
 {
+       if (!_picture_asset_writer) {
+               /* We're not writing any data */
+               return;
+       }
+
        dcp::FrameInfo fin = _picture_asset_writer->write (encoded->data().get (), encoded->size());
        write_frame_info (frame, eyes, fin);
        _last_written[eyes] = encoded;
@@ -264,6 +273,11 @@ ReelWriter::write (optional<Data> encoded, Frame frame, Eyes eyes)
 void
 ReelWriter::fake_write (Frame frame, Eyes eyes, int size)
 {
+       if (!_picture_asset_writer) {
+               /* We're not writing any data */
+               return;
+       }
+
        _picture_asset_writer->fake_write (size);
        _last_written_video_frame = frame;
        _last_written_eyes = eyes;
@@ -272,6 +286,11 @@ ReelWriter::fake_write (Frame frame, Eyes eyes, int size)
 void
 ReelWriter::repeat_write (Frame frame, Eyes eyes)
 {
+       if (!_picture_asset_writer) {
+               /* We're not writing any data */
+               return;
+       }
+
        dcp::FrameInfo fin = _picture_asset_writer->write (
                _last_written[eyes]->data().get(),
                _last_written[eyes]->size()
@@ -284,7 +303,7 @@ ReelWriter::repeat_write (Frame frame, Eyes eyes)
 void
 ReelWriter::finish ()
 {
-       if (!_picture_asset_writer->finalize ()) {
+       if (_picture_asset_writer && !_picture_asset_writer->finalize ()) {
                /* Nothing was written to the picture asset */
                LOG_GENERAL ("Nothing was written to reel %1 of %2", _reel_index, _reel_count);
                _picture_asset.reset ();
@@ -302,15 +321,30 @@ ReelWriter::finish ()
                boost::filesystem::path video_to;
                video_to /= _film->dir (_film->dcp_name());
                video_to /= video_asset_filename (_picture_asset, _reel_index, _reel_count, _content_summary);
-
+               /* There may be an existing "to" file if we are recreating a DCP in the same place without
+                  changing any video.
+               */
                boost::system::error_code ec;
+               boost::filesystem::remove (video_to, ec);
+
                boost::filesystem::create_hard_link (video_from, video_to, ec);
                if (ec) {
                        LOG_WARNING_NC ("Hard-link failed; copying instead");
-                       boost::filesystem::copy_file (video_from, video_to, ec);
-                       if (ec) {
-                               LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message ());
-                               throw FileError (ec.message(), video_from);
+                       shared_ptr<Job> job = _job.lock ();
+                       if (job) {
+                               job->sub (_("Copying video file into DCP"));
+                               try {
+                                       copy_in_bits (video_from, video_to, bind(&Job::set_progress, job.get(), _1, false));
+                               } catch (exception& e) {
+                                       LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), e.what());
+                                       throw FileError (e.what(), video_from);
+                               }
+                       } else {
+                               boost::filesystem::copy_file (video_from, video_to, ec);
+                               if (ec) {
+                                       LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message());
+                                       throw FileError (ec.message(), video_from);
+                               }
                        }
                }
 
@@ -428,6 +462,8 @@ maybe_add_text (
 shared_ptr<dcp::Reel>
 ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr<Font> > const & fonts)
 {
+       LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
+
        shared_ptr<dcp::Reel> reel (new dcp::Reel ());
 
        shared_ptr<dcp::ReelPictureAsset> reel_picture_asset;
@@ -457,8 +493,6 @@ ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr
                }
        }
 
-       LOG_GENERAL ("create_reel for %1-%2; %3 of %4", _period.from.get(), _period.to.get(), _reel_index, _reel_count);
-
        Frame const period_duration = _period.duration().frames_round(_film->video_frame_rate());
 
        DCPOMATIC_ASSERT (reel_picture_asset);
@@ -481,9 +515,13 @@ ReelWriter::create_reel (list<ReferencedReelAsset> const & refs, list<shared_ptr
                /* We have made a sound asset of our own.  Put it into the reel */
                reel_sound_asset.reset (new dcp::ReelSoundAsset (_sound_asset, 0));
        } else {
+               LOG_GENERAL ("no sound asset of our own; look through %1", refs.size());
                /* We don't have a sound asset of our own; hopefully we have one to reference */
                BOOST_FOREACH (ReferencedReelAsset j, refs) {
                        shared_ptr<dcp::ReelSoundAsset> k = dynamic_pointer_cast<dcp::ReelSoundAsset> (j.asset);
+                       if (k) {
+                               LOG_GENERAL ("candidate sound asset period is %1-%2", j.period.from.get(), j.period.to.get());
+                       }
                        if (k && j.period == _period) {
                                reel_sound_asset = k;
                                /* If we have a hash for this asset in the CPL, assume that it is correct */
@@ -655,7 +693,7 @@ ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track,
 }
 
 bool
-ReelWriter::existing_picture_frame_ok (FILE* asset_file, FILE* info_file, Frame frame) const
+ReelWriter::existing_picture_frame_ok (FILE* asset_file, shared_ptr<InfoFileHandle> info_file, Frame frame) const
 {
        LOG_GENERAL ("Checking existing picture frame %1", frame);