Some work on making KDMs in Film
[dcpomatic.git] / src / lib / film.cc
index e7f47c462c550c4bb8bc9fedb97cad1871bdd1ed..48677ba613848f419864efe0e4307a6842c5bea9 100644 (file)
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/date_time.hpp>
+#include <libxml++/libxml++.h>
+#include <libdcp/crypt_chain.h>
+#include <libdcp/certificates.h>
+#include "cinema.h"
 #include "film.h"
 #include "format.h"
 #include "job.h"
@@ -65,6 +69,7 @@ using std::ofstream;
 using std::setfill;
 using std::min;
 using std::make_pair;
+using std::list;
 using std::cout;
 using boost::shared_ptr;
 using boost::lexical_cast;
@@ -85,6 +90,7 @@ int const Film::state_version = 1;
 
 Film::Film (string d, bool must_exist)
        : _use_dci_name (true)
+       , _trust_content_header (true)
        , _dcp_content_type (0)
        , _format (0)
        , _scaler (Scaler::from_id ("bicubic"))
@@ -98,6 +104,7 @@ Film::Film (string d, bool must_exist)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
+       , _encrypted (false)
        , _frames_per_second (0)
        , _dirty (false)
 {
@@ -130,19 +137,23 @@ Film::Film (string d, bool must_exist)
        }
 
        _external_audio_stream = ExternalAudioStream::create ();
-
-       read_metadata ();
+       
+       if (must_exist) {
+               read_metadata ();
+       }
 
        _log = new FileLog (file ("log"));
        set_dci_date_today ();
 }
 
 Film::Film (Film const & o)
-       : _log (0)
+       : boost::enable_shared_from_this<Film> (o)
+       , _log (0)
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
        , _content           (o._content)
+       , _trust_content_header (o._trust_content_header)
        , _dcp_content_type  (o._dcp_content_type)
        , _format            (o._format)
        , _crop              (o._crop)
@@ -161,6 +172,7 @@ Film::Film (Film const & o)
        , _with_subtitles    (o._with_subtitles)
        , _subtitle_offset   (o._subtitle_offset)
        , _subtitle_scale    (o._subtitle_scale)
+       , _encrypted         (o._encrypted)
        , _audio_language    (o._audio_language)
        , _subtitle_language (o._subtitle_language)
        , _territory         (o._territory)
@@ -240,6 +252,8 @@ Film::make_dcp (bool transcode)
                char buffer[128];
                gethostname (buffer, sizeof (buffer));
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
+               log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video")));
+               log()->log (String::compose ("Content length %1", length()));
        }
                
        if (format() == 0) {
@@ -265,8 +279,18 @@ Film::make_dcp (bool transcode)
                oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
                if (audio_stream()) {
                        oe->audio_range = make_pair (
-                               video_frames_to_audio_frames (oe->video_range.get().first, audio_stream()->sample_rate(), frames_per_second()),
-                               video_frames_to_audio_frames (oe->video_range.get().second, audio_stream()->sample_rate(), frames_per_second())
+
+                               video_frames_to_audio_frames (
+                                       oe->video_range.get().first,
+                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
+                                       dcp_frame_rate (frames_per_second()).frames_per_second
+                                       ),
+                               
+                               video_frames_to_audio_frames (
+                                       oe->video_range.get().second,
+                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
+                                       dcp_frame_rate (frames_per_second()).frames_per_second
+                                       )
                                );
                }
                        
@@ -369,6 +393,7 @@ Film::write_metadata () const
        f << "name " << _name << "\n";
        f << "use_dci_name " << _use_dci_name << "\n";
        f << "content " << _content << "\n";
+       f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n";
        if (_dcp_content_type) {
                f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
        }
@@ -402,6 +427,7 @@ Film::write_metadata () const
        f << "with_subtitles " << _with_subtitles << "\n";
        f << "subtitle_offset " << _subtitle_offset << "\n";
        f << "subtitle_scale " << _subtitle_scale << "\n";
+       f << "encrypted " << _encrypted << "\n";
        f << "audio_language " << _audio_language << "\n";
        f << "subtitle_language " << _subtitle_language << "\n";
        f << "territory " << _territory << "\n";
@@ -446,8 +472,12 @@ Film::read_metadata ()
        boost::optional<int> audio_sample_rate;
        boost::optional<int> audio_stream_index;
        boost::optional<int> subtitle_stream_index;
-       
+
        ifstream f (file ("metadata").c_str());
+       if (!f.good()) {
+               throw OpenFileError (file("metadata"));
+       }
+       
        multimap<string, string> kv = read_key_value (f);
 
        /* We need version before anything else */
@@ -471,6 +501,8 @@ Film::read_metadata ()
                        _use_dci_name = (v == "1");
                } else if (k == "content") {
                        _content = v;
+               } else if (k == "trust_content_header") {
+                       _trust_content_header = (v == "1");
                } else if (k == "dcp_content_type") {
                        _dcp_content_type = DCPContentType::from_pretty_name (v);
                } else if (k == "format") {
@@ -521,6 +553,8 @@ Film::read_metadata ()
                        _subtitle_offset = atoi (v.c_str ());
                } else if (k == "subtitle_scale") {
                        _subtitle_scale = atof (v.c_str ());
+               } else if (k == "encrypted") {
+                       _encrypted = (v == "1");
                } else if (k == "audio_language") {
                        _audio_language = v;
                } else if (k == "subtitle_language") {
@@ -670,11 +704,15 @@ Film::target_audio_sample_rate () const
        return rint (t);
 }
 
-boost::optional<SourceFrame>
+boost::optional<int>
 Film::dcp_length () const
 {
+       if (content_type() == STILL) {
+               return _still_duration * frames_per_second();
+       }
+       
        if (!length()) {
-               return boost::optional<SourceFrame> ();
+               return boost::optional<int> ();
        }
 
        return length().get() - dcp_trim_start() - dcp_trim_end();
@@ -841,6 +879,9 @@ Film::set_content (string c)
        _content_audio_stream = shared_ptr<AudioStream> ();
        _subtitle_stream = shared_ptr<SubtitleStream> ();
 
+       /* Start off using content audio */
+       set_use_content_audio (true);
+
        /* Create a temporary decoder so that we can get information
           about the content.
        */
@@ -852,10 +893,12 @@ Film::set_content (string c)
                set_size (d.video->native_size ());
                set_frames_per_second (d.video->frames_per_second ());
                set_subtitle_streams (d.video->subtitle_streams ());
-               set_content_audio_streams (d.audio->audio_streams ());
+               if (d.audio) {
+                       set_content_audio_streams (d.audio->audio_streams ());
+               }
 
                /* Start off with the first audio and subtitle streams */
-               if (!d.audio->audio_streams().empty()) {
+               if (d.audio && !d.audio->audio_streams().empty()) {
                        set_content_audio_stream (d.audio->audio_streams().front());
                }
                
@@ -870,8 +913,6 @@ Film::set_content (string c)
                
                signal_changed (CONTENT);
                
-               set_content_digest (md5_digest (content_path ()));
-               
                examine_content ();
 
        } catch (...) {
@@ -881,6 +922,37 @@ Film::set_content (string c)
                throw;
 
        }
+
+       /* Default format */
+       switch (content_type()) {
+       case STILL:
+               set_format (Format::from_id ("var-185"));
+               break;
+       case VIDEO:
+               set_format (Format::from_id ("185"));
+               break;
+       }
+
+       /* Still image DCPs must use external audio */
+       if (content_type() == STILL) {
+               set_use_content_audio (false);
+       }
+}
+
+void
+Film::set_trust_content_header (bool t)
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _trust_content_header = t;
+       }
+       
+       signal_changed (TRUST_CONTENT_HEADER);
+
+       if (!_trust_content_header && !content().empty()) {
+               /* We just said that we don't trust the content's header */
+               examine_content ();
+       }
 }
               
 void
@@ -1128,6 +1200,16 @@ Film::set_subtitle_scale (float s)
        signal_changed (SUBTITLE_SCALE);
 }
 
+void
+Film::set_encrypted (bool e)
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _encrypted = e;
+       }
+       signal_changed (ENCRYPTED);
+}
+
 void
 Film::set_audio_language (string l)
 {
@@ -1307,3 +1389,69 @@ Film::audio_stream () const
 
        return _external_audio_stream;
 }
+
+void
+Film::make_kdms (
+       list<shared_ptr<Screen> > screens,
+       boost::posix_time::ptime from,
+       boost::posix_time::ptime until,
+       string directory
+       ) const
+{
+       string const cd = Config::instance()->crypt_chain_directory ();
+       if (boost::filesystem::is_empty (cd)) {
+               libdcp::make_crypt_chain (cd);
+       }
+
+       libdcp::CertificateChain chain;
+
+       {
+               boost::filesystem::path p (cd);
+               p /= "ca.self-signed.pem";
+               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p.string ())));
+       }
+
+       {
+               boost::filesystem::path p (cd);
+               p /= "intermediate.signed.pem";
+               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p.string ())));
+       }
+
+       {
+               boost::filesystem::path p (cd);
+               p /= "leaf.signed.pem";
+               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p.string ())));
+       }
+
+       boost::filesystem::path signer_key (cd);
+       signer_key /= "leaf.key";
+
+       /* Find the DCP to make the KDM for */
+       string const dir = this->directory ();
+       list<string> dcps;
+       for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
+               if (boost::filesystem::is_directory (*i) && i->path().leaf() != "j2c" && i->path().leaf() != "wavs") {
+                       dcps.push_back (i->path().string());
+               }
+       }
+
+       if (dcps.empty()) {
+               throw KDMError ("Could not find DCP to make KDM for");
+       } else if (dcps.size() > 1) {
+               throw KDMError ("More than one possible DCP to make KDM for");
+       }
+
+       for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
+
+               libdcp::DCP dcp (dcps.front ());
+               dcp.read ();
+               
+               /* XXX: single CPL only */
+               shared_ptr<xmlpp::Document> kdm = dcp.cpls().front()->make_kdm (chain, signer_key.string(), (*i)->certificate, from, until);
+
+               boost::filesystem::path out = directory;
+               out /= "kdm.xml";
+               kdm->write_to_file_formatted (out.string());
+       }
+}
+