Correctly include keys for referenced encrypted DCPs in KDMs (#975).
[dcpomatic.git] / src / lib / film.cc
index 5e204e126e8d9008fdbc602df42f1336afe06e77..79eb313ce44de451e37184094e92454e2b492085 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.
 
 
     This file is part of DCP-o-matic.
 
@@ -39,7 +39,6 @@
 #include "ratio.h"
 #include "cross.h"
 #include "environment_info.h"
 #include "ratio.h"
 #include "cross.h"
 #include "environment_info.h"
-#include "raw_convert.h"
 #include "audio_processor.h"
 #include "digester.h"
 #include "compose.hpp"
 #include "audio_processor.h"
 #include "digester.h"
 #include "compose.hpp"
 #include "dcp_content.h"
 #include "screen_kdm.h"
 #include "cinema.h"
 #include "dcp_content.h"
 #include "screen_kdm.h"
 #include "cinema.h"
-#include <locked_sstream.h>
 #include <libcxml/cxml.h>
 #include <dcp/cpl.h>
 #include <dcp/certificate_chain.h>
 #include <dcp/util.h>
 #include <dcp/local_time.h>
 #include <dcp/decrypted_kdm.h>
 #include <libcxml/cxml.h>
 #include <dcp/cpl.h>
 #include <dcp/certificate_chain.h>
 #include <dcp/util.h>
 #include <dcp/local_time.h>
 #include <dcp/decrypted_kdm.h>
+#include <dcp/raw_convert.h>
+#include <dcp/reel_mxf.h>
+#include <dcp/reel_asset.h>
 #include <libxml++/libxml++.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
 #include <libxml++/libxml++.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
@@ -84,11 +85,15 @@ using std::cout;
 using std::list;
 using std::set;
 using std::runtime_error;
 using std::list;
 using std::set;
 using std::runtime_error;
+using std::copy;
+using std::back_inserter;
+using std::map;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 using boost::optional;
 using boost::is_any_of;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 using boost::optional;
 using boost::is_any_of;
+using dcp::raw_convert;
 
 #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
 #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
 
 #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
 #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
@@ -124,7 +129,7 @@ int const Film::current_state_version = 36;
  *  @param dir Film directory.
  */
 
  *  @param dir Film directory.
  */
 
-Film::Film (boost::filesystem::path dir, bool log)
+Film::Film (optional<boost::filesystem::path> dir)
        : _playlist (new Playlist)
        , _use_isdcf_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
        : _playlist (new Playlist)
        , _use_isdcf_name (true)
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
@@ -132,6 +137,7 @@ Film::Film (boost::filesystem::path dir, bool log)
        , _resolution (RESOLUTION_2K)
        , _signed (true)
        , _encrypted (false)
        , _resolution (RESOLUTION_2K)
        , _signed (true)
        , _encrypted (false)
+       , _context_id (dcp::make_uuid ())
        , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
        , _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
        , _video_frame_rate (24)
        , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
        , _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
        , _video_frame_rate (24)
@@ -152,27 +158,30 @@ Film::Film (boost::filesystem::path dir, bool log)
        _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
        _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3));
 
        _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
        _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3));
 
-       /* Make state.directory a complete path without ..s (where possible)
-          (Code swiped from Adam Bowen on stackoverflow)
-          XXX: couldn't/shouldn't this just be boost::filesystem::canonical?
-       */
+       if (dir) {
+               /* Make state.directory a complete path without ..s (where possible)
+                  (Code swiped from Adam Bowen on stackoverflow)
+                  XXX: couldn't/shouldn't this just be boost::filesystem::canonical?
+               */
 
 
-       boost::filesystem::path p (boost::filesystem::system_complete (dir));
-       boost::filesystem::path result;
-       for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
-               if (*i == "..") {
-                       if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
+               boost::filesystem::path p (boost::filesystem::system_complete (dir.get()));
+               boost::filesystem::path result;
+               for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
+                       if (*i == "..") {
+                               if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
+                                       result /= *i;
+                               } else {
+                                       result = result.parent_path ();
+                               }
+                       } else if (*i != ".") {
                                result /= *i;
                                result /= *i;
-                       } else {
-                               result = result.parent_path ();
                        }
                        }
-               } else if (*i != ".") {
-                       result /= *i;
                }
                }
+
+               set_directory (result.make_preferred ());
        }
 
        }
 
-       set_directory (result.make_preferred ());
-       if (log) {
+       if (_directory) {
                _log.reset (new FileLog (file ("log")));
        } else {
                _log.reset (new NullLog);
                _log.reset (new FileLog (file ("log")));
        } else {
                _log.reset (new NullLog);
@@ -197,32 +206,29 @@ Film::video_identifier () const
 {
        DCPOMATIC_ASSERT (container ());
 
 {
        DCPOMATIC_ASSERT (container ());
 
-       locked_stringstream s;
-       s.imbue (std::locale::classic ());
-
-       s << container()->id()
-         << "_" << resolution_to_string (_resolution)
-         << "_" << _playlist->video_identifier()
-         << "_" << _video_frame_rate
-         << "_" << j2k_bandwidth();
+       string s = container()->id()
+               + "_" + resolution_to_string (_resolution)
+               + "_" + _playlist->video_identifier()
+               + "_" + raw_convert<string>(_video_frame_rate)
+               + "_" + raw_convert<string>(j2k_bandwidth());
 
        if (encrypted ()) {
 
        if (encrypted ()) {
-               s << "_E";
+               s += "_E";
        } else {
        } else {
-               s << "_P";
+               s += "_P";
        }
 
        if (_interop) {
        }
 
        if (_interop) {
-               s << "_I";
+               s += "_I";
        } else {
        } else {
-               s << "_S";
+               s += "_S";
        }
 
        if (_three_d) {
        }
 
        if (_three_d) {
-               s << "_3D";
+               s += "_3D";
        }
 
        }
 
-       return s.str ();
+       return s;
 }
 
 /** @return The file to write video frame info to */
 }
 
 /** @return The file to write video frame info to */
@@ -287,6 +293,35 @@ Film::make_dcp ()
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
 
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
 
+       if (container() == 0) {
+               throw MissingSettingError (_("container"));
+       }
+
+       if (content().empty()) {
+               throw runtime_error (_("you must add some content to the DCP before creating it"));
+       }
+
+       if (dcp_content_type() == 0) {
+               throw MissingSettingError (_("content type"));
+       }
+
+       if (name().empty()) {
+               throw MissingSettingError (_("name"));
+       }
+
+       BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
+               if (!i->paths_valid()) {
+                       throw runtime_error (_("some of your content is missing"));
+               }
+               shared_ptr<const DCPContent> dcp = dynamic_pointer_cast<const DCPContent> (i);
+               if (dcp && dcp->needs_kdm()) {
+                       throw runtime_error (_("some of your content needs a KDM"));
+               }
+               if (dcp && dcp->needs_assets()) {
+                       throw runtime_error (_("some of your content needs an OV"));
+               }
+       }
+
        set_isdcf_date_today ();
 
        BOOST_FOREACH (string i, environment_info ()) {
        set_isdcf_date_today ();
 
        BOOST_FOREACH (string i, environment_info ()) {
@@ -304,22 +339,6 @@ Film::make_dcp ()
        }
        LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
 
        }
        LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
 
-       if (container() == 0) {
-               throw MissingSettingError (_("container"));
-       }
-
-       if (content().empty()) {
-               throw runtime_error (_("You must add some content to the DCP before creating it"));
-       }
-
-       if (dcp_content_type() == 0) {
-               throw MissingSettingError (_("content type"));
-       }
-
-       if (name().empty()) {
-               throw MissingSettingError (_("name"));
-       }
-
        JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
 }
 
        JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
 }
 
@@ -332,7 +351,7 @@ Film::send_dcp_to_tms ()
 }
 
 shared_ptr<xmlpp::Document>
 }
 
 shared_ptr<xmlpp::Document>
-Film::metadata () const
+Film::metadata (bool with_content_paths) const
 {
        shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
        xmlpp::Element* root = doc->create_root_node ("Metadata");
 {
        shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
        xmlpp::Element* root = doc->create_root_node ("Metadata");
@@ -361,13 +380,14 @@ Film::metadata () const
        root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
        root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
        root->add_child("Key")->add_child_text (_key.hex ());
        root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
        root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
        root->add_child("Key")->add_child_text (_key.hex ());
+       root->add_child("ContextID")->add_child_text (_context_id);
        if (_audio_processor) {
                root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
        }
        if (_audio_processor) {
                root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
        }
-       root->add_child("ReelType")->add_child_text (raw_convert<string> (_reel_type));
+       root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
        root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
        root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
        root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
        root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
-       _playlist->as_xml (root->add_child ("Playlist"));
+       _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
 
        return doc;
 }
 
        return doc;
 }
@@ -376,24 +396,38 @@ Film::metadata () const
 void
 Film::write_metadata () const
 {
 void
 Film::write_metadata () const
 {
-       boost::filesystem::create_directories (directory ());
+       DCPOMATIC_ASSERT (directory());
+       boost::filesystem::create_directories (directory().get());
        shared_ptr<xmlpp::Document> doc = metadata ();
        doc->write_to_file_formatted (file("metadata.xml").string ());
        _dirty = false;
 }
 
        shared_ptr<xmlpp::Document> doc = metadata ();
        doc->write_to_file_formatted (file("metadata.xml").string ());
        _dirty = false;
 }
 
+/** Write a template from this film */
+void
+Film::write_template (boost::filesystem::path path) const
+{
+       boost::filesystem::create_directories (path.parent_path());
+       shared_ptr<xmlpp::Document> doc = metadata (false);
+       doc->write_to_file_formatted (path.string ());
+}
+
 /** Read state from our metadata file.
  *  @return Notes about things that the user should know about, or empty.
  */
 list<string>
 /** Read state from our metadata file.
  *  @return Notes about things that the user should know about, or empty.
  */
 list<string>
-Film::read_metadata ()
+Film::read_metadata (optional<boost::filesystem::path> path)
 {
 {
-       if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
-               throw runtime_error (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
+       if (!path) {
+               if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+                       throw runtime_error (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
+               }
+
+               path = file ("metadata.xml");
        }
 
        cxml::Document f ("Metadata");
        }
 
        cxml::Document f ("Metadata");
-       f.read_file (file ("metadata.xml"));
+       f.read_file (path.get ());
 
        _state_version = f.number_child<int> ("Version");
        if (_state_version > current_state_version) {
 
        _state_version = f.number_child<int> ("Version");
        if (_state_version > current_state_version) {
@@ -449,6 +483,7 @@ Film::read_metadata ()
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
        _key = dcp::Key (f.string_child ("Key"));
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
        _key = dcp::Key (f.string_child ("Key"));
+       _context_id = f.optional_string_child("ContextID").get_value_or (dcp::make_uuid ());
 
        if (f.optional_string_child ("AudioProcessor")) {
                _audio_processor = AudioProcessor::from_id (f.string_child ("AudioProcessor"));
 
        if (f.optional_string_child ("AudioProcessor")) {
                _audio_processor = AudioProcessor::from_id (f.string_child ("AudioProcessor"));
@@ -465,7 +500,9 @@ Film::read_metadata ()
        _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
 
        /* Write backtraces to this film's directory, until another film is loaded */
        _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
 
        /* Write backtraces to this film's directory, until another film is loaded */
-       set_backtrace_file (file ("backtrace.txt"));
+       if (_directory) {
+               set_backtrace_file (file ("backtrace.txt"));
+       }
 
        _dirty = false;
        return notes;
 
        _dirty = false;
        return notes;
@@ -477,8 +514,10 @@ Film::read_metadata ()
 boost::filesystem::path
 Film::dir (boost::filesystem::path d) const
 {
 boost::filesystem::path
 Film::dir (boost::filesystem::path d) const
 {
+       DCPOMATIC_ASSERT (_directory);
+
        boost::filesystem::path p;
        boost::filesystem::path p;
-       p /= _directory;
+       p /= _directory.get();
        p /= d;
 
        boost::filesystem::create_directories (p);
        p /= d;
 
        boost::filesystem::create_directories (p);
@@ -492,8 +531,10 @@ Film::dir (boost::filesystem::path d) const
 boost::filesystem::path
 Film::file (boost::filesystem::path f) const
 {
 boost::filesystem::path
 Film::file (boost::filesystem::path f) const
 {
+       DCPOMATIC_ASSERT (_directory);
+
        boost::filesystem::path p;
        boost::filesystem::path p;
-       p /= _directory;
+       p /= _directory.get();
        p /= f;
 
        boost::filesystem::create_directories (p.parent_path ());
        p /= f;
 
        boost::filesystem::create_directories (p.parent_path ());
@@ -530,7 +571,7 @@ Film::mapped_audio_channels () const
 string
 Film::isdcf_name (bool if_created_now) const
 {
 string
 Film::isdcf_name (bool if_created_now) const
 {
-       locked_stringstream d;
+       string d;
 
        string raw_name = name ();
 
 
        string raw_name = name ();
 
@@ -573,49 +614,49 @@ Film::isdcf_name (bool if_created_now) const
                fixed_name = fixed_name.substr (0, 14);
        }
 
                fixed_name = fixed_name.substr (0, 14);
        }
 
-       d << fixed_name;
+       d += fixed_name;
 
        if (dcp_content_type()) {
 
        if (dcp_content_type()) {
-               d << "_" << dcp_content_type()->isdcf_name();
-               d << "-" << isdcf_metadata().content_version;
+               d += "_" + dcp_content_type()->isdcf_name();
+               d += "-" + raw_convert<string>(isdcf_metadata().content_version);
        }
 
        ISDCFMetadata const dm = isdcf_metadata ();
 
        if (dm.temp_version) {
        }
 
        ISDCFMetadata const dm = isdcf_metadata ();
 
        if (dm.temp_version) {
-               d << "-Temp";
+               d += "-Temp";
        }
 
        if (dm.pre_release) {
        }
 
        if (dm.pre_release) {
-               d << "-Pre";
+               d += "-Pre";
        }
 
        if (dm.red_band) {
        }
 
        if (dm.red_band) {
-               d << "-RedBand";
+               d += "-RedBand";
        }
 
        if (!dm.chain.empty ()) {
        }
 
        if (!dm.chain.empty ()) {
-               d << "-" << dm.chain;
+               d += "-" + dm.chain;
        }
 
        if (three_d ()) {
        }
 
        if (three_d ()) {
-               d << "-3D";
+               d += "-3D";
        }
 
        if (dm.two_d_version_of_three_d) {
        }
 
        if (dm.two_d_version_of_three_d) {
-               d << "-2D";
+               d += "-2D";
        }
 
        if (!dm.mastered_luminance.empty ()) {
        }
 
        if (!dm.mastered_luminance.empty ()) {
-               d << "-" << dm.mastered_luminance;
+               d += "-" + dm.mastered_luminance;
        }
 
        if (video_frame_rate() != 24) {
        }
 
        if (video_frame_rate() != 24) {
-               d << "-" << video_frame_rate();
+               d += "-" + raw_convert<string>(video_frame_rate());
        }
 
        if (container()) {
        }
 
        if (container()) {
-               d << "_" << container()->isdcf_name();
+               d += "_" + container()->isdcf_name();
        }
 
        /* XXX: this uses the first bit of content only */
        }
 
        /* XXX: this uses the first bit of content only */
@@ -636,12 +677,12 @@ Film::isdcf_name (bool if_created_now) const
                }
 
                if (content_ratio && content_ratio != container()) {
                }
 
                if (content_ratio && content_ratio != container()) {
-                       d << "-" << content_ratio->isdcf_name();
+                       d += "-" + content_ratio->isdcf_name();
                }
        }
 
        if (!dm.audio_language.empty ()) {
                }
        }
 
        if (!dm.audio_language.empty ()) {
-               d << "_" << dm.audio_language;
+               d += "_" + dm.audio_language;
                if (!dm.subtitle_language.empty()) {
 
                        bool burnt_in = true;
                if (!dm.subtitle_language.empty()) {
 
                        bool burnt_in = true;
@@ -662,18 +703,18 @@ Film::isdcf_name (bool if_created_now) const
                                transform (language.begin(), language.end(), language.begin(), ::toupper);
                        }
 
                                transform (language.begin(), language.end(), language.begin(), ::toupper);
                        }
 
-                       d << "-" << language;
+                       d += "-" + language;
                } else {
                } else {
-                       d << "-XX";
+                       d += "-XX";
                }
        }
 
        if (!dm.territory.empty ()) {
                }
        }
 
        if (!dm.territory.empty ()) {
-               d << "_" << dm.territory;
+               d += "_" + dm.territory;
                if (dm.rating.empty ()) {
                if (dm.rating.empty ()) {
-                       d << "-NR";
+                       d += "-NR";
                } else {
                } else {
-                       d << "-" << dm.rating;
+                       d += "-" + dm.rating;
                }
        }
 
                }
        }
 
@@ -696,35 +737,35 @@ Film::isdcf_name (bool if_created_now) const
        }
 
        if (non_lfe) {
        }
 
        if (non_lfe) {
-               d << "_" << non_lfe << lfe;
+               d += String::compose("_%1%2", non_lfe, lfe);
        }
 
        /* XXX: HI/VI */
 
        }
 
        /* XXX: HI/VI */
 
-       d << "_" << resolution_to_string (_resolution);
+       d += "_" + resolution_to_string (_resolution);
 
        if (!dm.studio.empty ()) {
 
        if (!dm.studio.empty ()) {
-               d << "_" << dm.studio;
+               d += "_" + dm.studio;
        }
 
        if (if_created_now) {
        }
 
        if (if_created_now) {
-               d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
+               d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
        } else {
        } else {
-               d << "_" << boost::gregorian::to_iso_string (_isdcf_date);
+               d += "_" + boost::gregorian::to_iso_string (_isdcf_date);
        }
 
        if (!dm.facility.empty ()) {
        }
 
        if (!dm.facility.empty ()) {
-               d << "_" << dm.facility;
+               d += "_" + dm.facility;
        }
 
        if (_interop) {
        }
 
        if (_interop) {
-               d << "_IOP";
+               d += "_IOP";
        } else {
        } else {
-               d << "_SMPTE";
+               d += "_SMPTE";
        }
 
        if (three_d ()) {
        }
 
        if (three_d ()) {
-               d << "-3D";
+               d += "-3D";
        }
 
        bool vf = false;
        }
 
        bool vf = false;
@@ -736,12 +777,12 @@ Film::isdcf_name (bool if_created_now) const
        }
 
        if (vf) {
        }
 
        if (vf) {
-               d << "_VF";
+               d += "_VF";
        } else {
        } else {
-               d << "_OV";
+               d += "_OV";
        }
 
        }
 
-       return d.str ();
+       return d;
 }
 
 /** @return name to give the DCP */
 }
 
 /** @return name to give the DCP */
@@ -922,23 +963,23 @@ Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const
        p /= "j2c";
        p /= video_identifier ();
 
        p /= "j2c";
        p /= video_identifier ();
 
-       locked_stringstream s;
-       s.width (8);
-       s << setfill('0') << reel << "_" << frame;
+       char buffer[256];
+       snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame);
+       string s (buffer);
 
        if (eyes == EYES_LEFT) {
 
        if (eyes == EYES_LEFT) {
-               s << ".L";
+               s += ".L";
        } else if (eyes == EYES_RIGHT) {
        } else if (eyes == EYES_RIGHT) {
-               s << ".R";
+               s += ".R";
        }
 
        }
 
-       s << ".j2c";
+       s += ".j2c";
 
        if (tmp) {
 
        if (tmp) {
-               s << ".tmp";
+               s += ".tmp";
        }
 
        }
 
-       p /= s.str();
+       p /= s;
        return file (p);
 }
 
        return file (p);
 }
 
@@ -946,9 +987,13 @@ Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const
 vector<CPLSummary>
 Film::cpls () const
 {
 vector<CPLSummary>
 Film::cpls () const
 {
+       if (!directory ()) {
+               return vector<CPLSummary> ();
+       }
+
        vector<CPLSummary> out;
 
        vector<CPLSummary> out;
 
-       boost::filesystem::path const dir = directory ();
+       boost::filesystem::path const dir = directory().get();
        for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
                if (
                        boost::filesystem::is_directory (*i) &&
        for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
                if (
                        boost::filesystem::is_directory (*i) &&
@@ -958,12 +1003,13 @@ Film::cpls () const
                        try {
                                dcp::DCP dcp (*i);
                                dcp.read ();
                        try {
                                dcp::DCP dcp (*i);
                                dcp.read ();
+                               DCPOMATIC_ASSERT (dcp.cpls().front()->file());
                                out.push_back (
                                        CPLSummary (
                                                i->path().leaf().string(),
                                                dcp.cpls().front()->id(),
                                                dcp.cpls().front()->annotation_text(),
                                out.push_back (
                                        CPLSummary (
                                                i->path().leaf().string(),
                                                dcp.cpls().front()->id(),
                                                dcp.cpls().front()->annotation_text(),
-                                               dcp.cpls().front()->file()
+                                               dcp.cpls().front()->file().get()
                                                )
                                        );
                        } catch (...) {
                                                )
                                        );
                        } catch (...) {
@@ -1002,17 +1048,10 @@ Film::content () const
        return _playlist->content ();
 }
 
        return _playlist->content ();
 }
 
-void
-Film::examine_content (shared_ptr<Content> c)
-{
-       shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
-       JobManager::instance()->add (j);
-}
-
 void
 Film::examine_and_add_content (shared_ptr<Content> c)
 {
 void
 Film::examine_and_add_content (shared_ptr<Content> c)
 {
-       if (dynamic_pointer_cast<FFmpegContent> (c) && !_directory.empty ()) {
+       if (dynamic_pointer_cast<FFmpegContent> (c) && _directory) {
                run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
        }
 
                run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
        }
 
@@ -1039,6 +1078,7 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
        }
 
        add_content (content);
        }
 
        add_content (content);
+
        if (Config::instance()->automatic_audio_analysis() && content->audio) {
                shared_ptr<Playlist> playlist (new Playlist);
                playlist->add (content);
        if (Config::instance()->automatic_audio_analysis() && content->audio) {
                shared_ptr<Playlist> playlist (new Playlist);
                playlist->add (content);
@@ -1060,6 +1100,15 @@ Film::add_content (shared_ptr<Content> c)
                c->set_position (_playlist->subtitle_end ());
        }
 
                c->set_position (_playlist->subtitle_end ());
        }
 
+       if (_template_film) {
+               /* Take settings from the first piece of content of c's type in _template */
+               BOOST_FOREACH (shared_ptr<Content> i, _template_film->content()) {
+                       if (typeid(i.get()) == typeid(c.get())) {
+                               c->use_template (i);
+                       }
+               }
+       }
+
        _playlist->add (c);
 }
 
        _playlist->add (c);
 }
 
@@ -1188,8 +1237,42 @@ Film::make_kdm (
                throw InvalidSignerError ();
        }
 
                throw InvalidSignerError ();
        }
 
+       /* Find keys that have been added to imported, encrypted DCP content */
+       list<dcp::DecryptedKDMKey> imported_keys;
+       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+               shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
+               if (d && d->kdm()) {
+                       dcp::DecryptedKDM kdm (d->kdm().get(), Config::instance()->decryption_chain()->key().get());
+                       list<dcp::DecryptedKDMKey> keys = kdm.keys ();
+                       copy (keys.begin(), keys.end(), back_inserter (imported_keys));
+               }
+       }
+
+       map<shared_ptr<const dcp::ReelMXF>, dcp::Key> keys;
+
+       BOOST_FOREACH(shared_ptr<const dcp::ReelAsset> i, cpl->reel_assets ()) {
+               shared_ptr<const dcp::ReelMXF> mxf = boost::dynamic_pointer_cast<const dcp::ReelMXF> (i);
+               if (!mxf || !mxf->key_id()) {
+                       continue;
+               }
+
+               /* Get any imported key for this ID */
+               bool done = false;
+               BOOST_FOREACH (dcp::DecryptedKDMKey j, imported_keys) {
+                       if (j.id() == mxf->key_id().get()) {
+                               keys[mxf] = j.key();
+                               done = true;
+                       }
+               }
+
+               if (!done) {
+                       /* No imported key; it must be an asset that we encrypted */
+                       keys[mxf] = key();
+               }
+       }
+
        return dcp::DecryptedKDM (
        return dcp::DecryptedKDM (
-               cpl, key(), from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
+               cpl->id(), keys, from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
                ).encrypt (signer, recipient, trusted_devices, formulation);
 }
 
                ).encrypt (signer, recipient, trusted_devices, formulation);
 }
 
@@ -1355,24 +1438,12 @@ Film::audio_output_names () const
        DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
 
        vector<string> n;
        DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
 
        vector<string> n;
-       n.push_back (_("L"));
-       n.push_back (_("R"));
-       n.push_back (_("C"));
-       n.push_back (_("Lfe"));
-       n.push_back (_("Ls"));
-       n.push_back (_("Rs"));
-       n.push_back (_("HI"));
-       n.push_back (_("VI"));
-       n.push_back (_("Lc"));
-       n.push_back (_("Rc"));
-       n.push_back (_("BsL"));
-       n.push_back (_("BsR"));
-       n.push_back (_("DBP"));
-       n.push_back (_("DBS"));
-       n.push_back ("");
-       n.push_back ("");
-
-       return vector<string> (n.begin(), n.begin() + audio_channels ());
+
+       for (int i = 0; i < audio_channels(); ++i) {
+               n.push_back (short_audio_channel_name (i));
+       }
+
+       return n;
 }
 
 void
 }
 
 void
@@ -1397,7 +1468,7 @@ list<DCPTimePeriod>
 Film::reels () const
 {
        list<DCPTimePeriod> p;
 Film::reels () const
 {
        list<DCPTimePeriod> p;
-       DCPTime const len = length().round_up (video_frame_rate ());
+       DCPTime const len = length().ceil (video_frame_rate ());
 
        switch (reel_type ()) {
        case REELTYPE_SINGLE:
 
        switch (reel_type ()) {
        case REELTYPE_SINGLE:
@@ -1453,3 +1524,62 @@ Film::content_summary (DCPTimePeriod period) const
 {
        return _playlist->content_summary (period);
 }
 {
        return _playlist->content_summary (period);
 }
+
+list<string>
+Film::fix_conflicting_settings ()
+{
+       list<string> notes;
+
+       list<boost::filesystem::path> was_referencing;
+       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+               shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
+               if (d) {
+                       list<string> reasons;
+                       bool was = false;
+                       if (!d->can_reference_video(reasons) && d->reference_video()) {
+                               d->set_reference_video (false);
+                               was = true;
+                       }
+                       if (!d->can_reference_audio(reasons) && d->reference_audio()) {
+                               d->set_reference_audio (false);
+                               was = true;
+                       }
+                       if (!d->can_reference_subtitle(reasons) && d->reference_subtitle()) {
+                               d->set_reference_subtitle (false);
+                               was = true;
+                       }
+                       if (was) {
+                               was_referencing.push_back (d->path(0).parent_path().filename());
+                       }
+               }
+       }
+
+       BOOST_FOREACH (boost::filesystem::path d, was_referencing) {
+               notes.push_back (String::compose (_("The DCP %1 was being referred to by this film.  This not now possible because the reel sizes in the film no longer agree with those in the imported DCP.\n\nSetting the 'Reel type' to 'split by video content' will probably help.\n\nAfter doing that you would need to re-tick the appropriate 'refer to existing DCP' checkboxes."), d.string()));
+       }
+
+       return notes;
+}
+
+void
+Film::use_template (string name)
+{
+       _template_film.reset (new Film (optional<boost::filesystem::path>()));
+       _template_film->read_metadata (Config::instance()->template_path (name));
+       _use_isdcf_name = _template_film->_use_isdcf_name;
+       _dcp_content_type = _template_film->_dcp_content_type;
+       _container = _template_film->_container;
+       _resolution = _template_film->_resolution;
+       _j2k_bandwidth = _template_film->_j2k_bandwidth;
+       _video_frame_rate = _template_film->_video_frame_rate;
+       _signed = _template_film->_signed;
+       _encrypted = _template_film->_encrypted;
+       _audio_channels = _template_film->_audio_channels;
+       _sequence = _template_film->_sequence;
+       _three_d = _template_film->_three_d;
+       _interop = _template_film->_interop;
+       _audio_processor = _template_film->_audio_processor;
+       _reel_type = _template_film->_reel_type;
+       _reel_length = _template_film->_reel_length;
+       _upload_after_make_dcp = _template_film->_upload_after_make_dcp;
+}