Basics of custom DCP filename components.
[dcpomatic.git] / src / lib / writer.cc
index 2a9b6d6d7414a5752dab0cd78f38fa8c6ff6e090..00dfcdcbe9319c4022379866ed1d4de021ac1621 100644 (file)
@@ -1,19 +1,20 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
 
-    This program is free software; you can redistribute it and/or modify
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
+    DCP-o-matic is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
@@ -29,7 +30,6 @@
 #include "job.h"
 #include "cross.h"
 #include "audio_buffers.h"
-#include "md5_digester.h"
 #include "version.h"
 #include "font.h"
 #include "util.h"
@@ -58,6 +58,9 @@ using std::pair;
 using std::string;
 using std::list;
 using std::cout;
+using std::map;
+using std::min;
+using std::max;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
@@ -95,8 +98,6 @@ Writer::Writer (shared_ptr<const Film> film, weak_ptr<Job> j)
        if (_film->is_signed() && !Config::instance()->signer_chain()->valid ()) {
                throw InvalidSignerError ();
        }
-
-       job->sub (_("Encoding image data"));
 }
 
 void
@@ -359,19 +360,6 @@ try
                        }
 
                        lock.lock ();
-
-                       shared_ptr<Job> job = _job.lock ();
-                       DCPOMATIC_ASSERT (job);
-                       int64_t total = _film->length().frames_round (_film->video_frame_rate ());
-                       if (_film->three_d ()) {
-                               /* _full_written and so on are incremented for each eye, so we need to double the total
-                                  frames to get the correct progress.
-                               */
-                               total *= 2;
-                       }
-                       if (total) {
-                               job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
-                       }
                }
 
                while (_queued_full_in_memory > _maximum_frames_in_memory) {
@@ -448,12 +436,18 @@ Writer::finish ()
                return;
        }
 
+       LOG_GENERAL_NC ("Terminating writer thread");
+
        terminate_thread (true);
 
+       LOG_GENERAL_NC ("Finishing ReelWriters");
+
        BOOST_FOREACH (ReelWriter& i, _reels) {
                i.finish ();
        }
 
+       LOG_GENERAL_NC ("Writing XML");
+
        dcp::DCP dcp (_film->dir (_film->dcp_name()));
 
        shared_ptr<dcp::CPL> cpl (
@@ -465,13 +459,35 @@ Writer::finish ()
 
        dcp.add (cpl);
 
+       /* Calculate digests for each reel in parallel */
+
+       shared_ptr<Job> job = _job.lock ();
+       job->sub (_("Computing digests"));
+
+       boost::asio::io_service service;
+       boost::thread_group pool;
+
+       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 ());
+
+       for (int i = 0; i < threads; ++i) {
+               pool.create_thread (boost::bind (&boost::asio::io_service::run, &service));
+       }
+
        BOOST_FOREACH (ReelWriter& i, _reels) {
+               boost::function<void (float)> set_progress = boost::bind (&Writer::set_digest_progress, this, job.get(), _1);
+               service.post (boost::bind (&ReelWriter::calculate_digests, &i, set_progress));
+       }
 
-               cpl->add (i.create_reel (_reel_assets, _fonts));
+       work.reset ();
+       pool.join_all ();
+       service.stop ();
 
-               shared_ptr<Job> job = _job.lock ();
-               DCPOMATIC_ASSERT (job);
-               i.calculate_digests (job);
+       /* Add reels to CPL */
+
+       BOOST_FOREACH (ReelWriter& i, _reels) {
+               cpl->add (i.create_reel (_reel_assets, _fonts));
        }
 
        dcp::XMLMetadata meta;
@@ -496,7 +512,7 @@ Writer::finish ()
                }
        }
 
-       dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer);
+       dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer, Config::instance()->dcp_filename_format());
 
        LOG_GENERAL (
                N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
@@ -537,8 +553,20 @@ Writer::write (PlayerSubtitles subs)
 void
 Writer::write (list<shared_ptr<Font> > fonts)
 {
-       /* Just keep a list of fonts and we'll deal with them in ::finish */
-       copy (fonts.begin (), fonts.end (), back_inserter (_fonts));
+       /* Just keep a list of unique fonts and we'll deal with them in ::finish */
+
+       BOOST_FOREACH (shared_ptr<Font> i, fonts) {
+               bool got = false;
+               BOOST_FOREACH (shared_ptr<Font> j, _fonts) {
+                       if (*i == *j) {
+                               got = true;
+                       }
+               }
+
+               if (!got) {
+                       _fonts.push_back (i);
+               }
+       }
 }
 
 bool
@@ -564,7 +592,16 @@ operator== (QueueItem const & a, QueueItem const & b)
 void
 Writer::set_encoder_threads (int threads)
 {
-       _maximum_frames_in_memory = lrint (threads * 1.1);
+       /* I think the scaling factor here should be the ratio of the longest frame
+          encode time to the shortest; if the thread count is T, longest time is L
+          and the shortest time S we could encode L/S frames per thread whilst waiting
+          for the L frame to encode so we might have to store LT/S frames.
+
+          However we don't want to use too much memory, so keep it a bit lower than we'd
+          perhaps like.  A J2K frame is typically about 1Mb so 3 here will mean we could
+          use about 240Mb with 72 encoding threads.
+       */
+       _maximum_frames_in_memory = lrint (threads * 3);
 }
 
 void
@@ -585,3 +622,18 @@ Writer::video_reel (int frame) const
        DCPOMATIC_ASSERT (i < _reels.size ());
        return i;
 }
+
+void
+Writer::set_digest_progress (Job* job, float progress)
+{
+       /* I believe this is thread-safe */
+       _digest_progresses[boost::this_thread::get_id()] = progress;
+
+       boost::mutex::scoped_lock lm (_digest_progresses_mutex);
+       float min_progress = 0;
+       for (map<boost::thread::id, float>::const_iterator i = _digest_progresses.begin(); i != _digest_progresses.end(); ++i) {
+               min_progress = min (min_progress, i->second);
+       }
+
+       job->set_progress (min_progress);
+}