Simple cover sheet support (#1039).
[dcpomatic.git] / src / lib / writer.cc
index 595915883228021dd49f86ce920ea9aa4f9492c0..ec4689b2ff7025317aaefff3615ee2f0c001549b 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -95,8 +95,9 @@ Writer::Writer (shared_ptr<const Film> film, weak_ptr<Job> j)
        _subtitle_reel = _reels.begin ();
 
        /* Check that the signer is OK if we need one */
-       if (_film->is_signed() && !Config::instance()->signer_chain()->valid ()) {
-               throw InvalidSignerError ();
+       string reason;
+       if (_film->is_signed() && !Config::instance()->signer_chain()->valid(&reason)) {
+               throw InvalidSignerError (reason);
        }
 }
 
@@ -228,23 +229,40 @@ Writer::fake_write (Frame frame, Eyes eyes)
        _empty_condition.notify_all ();
 }
 
-/** Write one video frame's worth of audio frames to the DCP.
+/** Write some audio frames to the DCP.
  *  @param audio Audio data or 0 if there is no audio to be written here (i.e. it is referenced).
  *  This method is not thread safe.
  */
 void
 Writer::write (shared_ptr<const AudioBuffers> audio)
 {
-       if (_audio_reel == _reels.end ()) {
-               /* This audio is off the end of the last reel; ignore it */
-               return;
-       }
+       /* The audio we get might span a reel boundary, and if so we have to write it in bits */
+
+       int32_t offset = 0;
+       while (offset < audio->frames ()) {
 
-       _audio_reel->write (audio);
+               if (_audio_reel == _reels.end ()) {
+                       /* This audio is off the end of the last reel; ignore it */
+                       return;
+               }
 
-       /* written is in video frames, not audio frames */
-       if (_audio_reel->total_written_audio_frames() >= _audio_reel->period().duration().frames_floor (_film->video_frame_rate())) {
-               ++_audio_reel;
+               int32_t const this_time = min (
+                       audio->frames() - offset,
+                       (int32_t) (_audio_reel->period().duration().frames_floor(_film->audio_frame_rate()) - _audio_reel->total_written_audio_frames())
+                       );
+
+               if (this_time == audio->frames()) {
+                       /* Easy case: we can write all the audio to this reel */
+                       _audio_reel->write (audio);
+               } else {
+                       /* Write the part we can */
+                       shared_ptr<AudioBuffers> part (new AudioBuffers (audio->channels(), this_time));
+                       part->copy_from (audio.get(), this_time, offset, 0);
+                       _audio_reel->write (part);
+                       ++_audio_reel;
+               }
+
+               offset += this_time;
        }
 }
 
@@ -471,7 +489,7 @@ Writer::finish ()
 
        shared_ptr<boost::asio::io_service::work> work (new boost::asio::io_service::work (service));
 
-       int const threads = max (1, Config::instance()->num_local_encoding_threads ());
+       int const threads = max (1, Config::instance()->master_encoding_threads ());
 
        for (int i = 0; i < threads; ++i) {
                pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
@@ -510,8 +528,9 @@ Writer::finish ()
        if (_film->is_signed ()) {
                signer = Config::instance()->signer_chain ();
                /* We did check earlier, but check again here to be on the safe side */
-               if (!signer->valid ()) {
-                       throw InvalidSignerError ();
+               string reason;
+               if (!signer->valid (&reason)) {
+                       throw InvalidSignerError (reason);
                }
        }
 
@@ -520,6 +539,51 @@ Writer::finish ()
        LOG_GENERAL (
                N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
                );
+
+       write_cover_sheet ();
+}
+
+void
+Writer::write_cover_sheet ()
+{
+       boost::filesystem::path const cover = _film->file ("COVER_SHEET.txt");
+       FILE* f = fopen_boost (cover, "w");
+       if (!f) {
+               throw OpenFileError (cover, errno, false);
+       }
+
+       string text = Config::instance()->cover_sheet ();
+       boost::algorithm::replace_all (text, "$CPL_NAME", _film->name());
+       boost::algorithm::replace_all (text, "$TYPE", _film->dcp_content_type()->pretty_name());
+       boost::algorithm::replace_all (text, "$CONTAINER", _film->container()->nickname());
+
+       pair<int, int> ch = audio_channel_types (_film->mapped_audio_channels(), _film->audio_channels());
+       string description = String::compose("%1.%2", ch.first, ch.second);
+
+       if (description == "0.0") {
+               description = _("None");
+       } else if (description == "1.0") {
+               description = _("Mono");
+       } else if (description == "2.0") {
+               description = _("Stereo");
+       }
+       boost::algorithm::replace_all (text, "$AUDIO", description);
+
+       int h, m, s, fr;
+       _film->length().split (_film->video_frame_rate(), h, m, s, fr);
+       string length;
+       if (h == 0 && m == 0) {
+               length = String::compose("%1s", s);
+       } else if (h == 0 && m > 0) {
+               length = String::compose("%1m%2s", m, s);
+       } else if (h > 0 && m > 0) {
+               length = String::compose("%1h%2m%3s", h, m, s);
+       }
+
+       boost::algorithm::replace_all (text, "$LENGTH", length);
+
+       fwrite (text.c_str(), 1, text.length(), f);
+       fclose (f);
 }
 
 /** @param frame Frame index within the whole DCP.
@@ -540,13 +604,13 @@ Writer::can_fake_write (Frame frame) const
 }
 
 void
-Writer::write (PlayerSubtitles subs)
+Writer::write (PlayerSubtitles subs, DCPTimePeriod period)
 {
        if (subs.text.empty ()) {
                return;
        }
 
-       if (_subtitle_reel->period().to <= subs.from) {
+       if (_subtitle_reel->period().to <= period.from) {
                ++_subtitle_reel;
        }