Disable DCP panel stuff which cannot be altered when a DCP is being referenced.
[dcpomatic.git] / src / lib / film.cc
index 2dcdf4eefbae6432f9553241da0a44a276d3fe5b..68ebddba2c5edb597ac0d74b32f7e3eb86346674 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -19,7 +19,7 @@
 */
 
 /** @file  src/film.cc
- *  @brief A representation of some audio and video content, and details of
+ *  @brief A representation of some audio, video and subtitle content, and details of
  *  how they should be presented in a DCP.
  */
 
@@ -27,6 +27,7 @@
 #include "job.h"
 #include "util.h"
 #include "job_manager.h"
+#include "dcp_encoder.h"
 #include "transcode_job.h"
 #include "upload_job.h"
 #include "null_log.h"
@@ -98,6 +99,8 @@ 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);
 
+string const Film::metadata_file = "metadata.xml";
+
 /* 5 -> 6
  * AudioMapping XML changed.
  * 6 -> 7
@@ -148,7 +151,7 @@ Film::Film (optional<boost::filesystem::path> dir)
        , _audio_processor (0)
        , _reel_type (REELTYPE_SINGLE)
        , _reel_length (2000000000)
-       , _upload_after_make_dcp (false)
+       , _upload_after_make_dcp (Config::instance()->default_upload_after_make_dcp())
        , _state_version (current_state_version)
        , _dirty (false)
 {
@@ -306,7 +309,7 @@ Film::make_dcp ()
        }
 
        if (name().empty()) {
-               throw MissingSettingError (_("name"));
+               set_name ("DCP");
        }
 
        BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
@@ -335,11 +338,13 @@ Film::make_dcp ()
        if (Config::instance()->only_servers_encode ()) {
                LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
        } else {
-               LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads());
+               LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads());
        }
        LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
 
-       JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
+       shared_ptr<TranscodeJob> tj (new TranscodeJob (shared_from_this()));
+       tj->set_encoder (shared_ptr<Encoder> (new DCPEncoder (shared_from_this(), tj)));
+       JobManager::instance()->add (tj);
 }
 
 /** Start a job to send our DCP to the configured TMS */
@@ -399,7 +404,7 @@ Film::write_metadata () const
        DCPOMATIC_ASSERT (directory());
        boost::filesystem::create_directories (directory().get());
        shared_ptr<xmlpp::Document> doc = metadata ();
-       doc->write_to_file_formatted (file("metadata.xml").string ());
+       doc->write_to_file_formatted (file(metadata_file).string ());
        _dirty = false;
 }
 
@@ -419,11 +424,11 @@ list<string>
 Film::read_metadata (optional<boost::filesystem::path> path)
 {
        if (!path) {
-               if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+               if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file (metadata_file))) {
                        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");
+               path = file (metadata_file);
        }
 
        cxml::Document f ("Metadata");
@@ -509,10 +514,11 @@ Film::read_metadata (optional<boost::filesystem::path> path)
 }
 
 /** Given a directory name, return its full path within the Film's directory.
- *  The directory (and its parents) will be created if they do not exist.
+ *  @param d directory name within the Film's directory.
+ *  @param create true to create the directory (and its parents) if they do not exist.
  */
 boost::filesystem::path
-Film::dir (boost::filesystem::path d) const
+Film::dir (boost::filesystem::path d, bool create) const
 {
        DCPOMATIC_ASSERT (_directory);
 
@@ -520,7 +526,9 @@ Film::dir (boost::filesystem::path d) const
        p /= _directory.get();
        p /= d;
 
-       boost::filesystem::create_directories (p);
+       if (create) {
+               boost::filesystem::create_directories (p);
+       }
 
        return p;
 }
@@ -661,7 +669,7 @@ Film::isdcf_name (bool if_created_now) const
 
        /* XXX: this uses the first bit of content only */
 
-       /* The standard says we don't do this for trailers, for some strange reason */
+       /* Interior aspect ratio.  The standard says we don't do this for trailers, for some strange reason */
        if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
                Ratio const * content_ratio = 0;
                BOOST_FOREACH (shared_ptr<Content> i, content ()) {
@@ -677,7 +685,8 @@ Film::isdcf_name (bool if_created_now) const
                }
 
                if (content_ratio && content_ratio != container()) {
-                       d += "-" + content_ratio->isdcf_name();
+                       /* This needs to be the numeric version of the ratio, and ::id() is close enough */
+                       d += "-" + content_ratio->id();
                }
        }
 
@@ -720,24 +729,9 @@ Film::isdcf_name (bool if_created_now) const
 
        /* Count mapped audio channels */
 
-       int non_lfe = 0;
-       int lfe = 0;
-
-       BOOST_FOREACH (int i, mapped_audio_channels ()) {
-               if (i >= audio_channels()) {
-                       /* This channel is mapped but is not included in the DCP */
-                       continue;
-               }
-
-               if (static_cast<dcp::Channel> (i) == dcp::LFE) {
-                       ++lfe;
-               } else {
-                       ++non_lfe;
-               }
-       }
-
-       if (non_lfe) {
-               d += String::compose("_%1%2", non_lfe, lfe);
+       pair<int, int> ch = audio_channel_types (mapped_audio_channels(), audio_channels());
+       if (ch.first) {
+               d += String::compose("_%1%2", ch.first, ch.second);
        }
 
        /* XXX: HI/VI */
@@ -791,24 +785,10 @@ Film::dcp_name (bool if_created_now) const
 {
        string unfiltered;
        if (use_isdcf_name()) {
-               unfiltered = isdcf_name (if_created_now);
-       } else {
-               unfiltered = name ();
-       }
-
-       /* Filter out `bad' characters which cause problems with some systems.
-          There's no apparent list of what really is allowed, so this is a guess.
-       */
-
-       string filtered;
-       string const allowed = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
-       for (size_t i = 0; i < unfiltered.size(); ++i) {
-               if (allowed.find (unfiltered[i]) != string::npos) {
-                       filtered += unfiltered[i];
-               }
+               return careful_string_filter (isdcf_name (if_created_now));
        }
 
-       return filtered;
+       return careful_string_filter (name ());
 }
 
 void
@@ -1048,24 +1028,27 @@ Film::content () const
        return _playlist->content ();
 }
 
+/** @param content Content to add.
+ *  @param disable_audio_analysis true to never do automatic audio analysis, even if it is enabled in configuration.
+ */
 void
-Film::examine_and_add_content (shared_ptr<Content> c)
+Film::examine_and_add_content (shared_ptr<Content> content, bool disable_audio_analysis)
 {
-       if (dynamic_pointer_cast<FFmpegContent> (c) && _directory) {
-               run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
+       if (dynamic_pointer_cast<FFmpegContent> (content) && _directory) {
+               run_ffprobe (content->path(0), file ("ffprobe.log"), _log);
        }
 
-       shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
+       shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), content));
 
        _job_connections.push_back (
-               j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr<Job> (j), weak_ptr<Content> (c)))
+               j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr<Job>(j), weak_ptr<Content>(content), disable_audio_analysis))
                );
 
        JobManager::instance()->add (j);
 }
 
 void
-Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
+Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c, bool disable_audio_analysis)
 {
        shared_ptr<Job> job = j.lock ();
        if (!job || !job->finished_ok ()) {
@@ -1079,7 +1062,7 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
 
        add_content (content);
 
-       if (Config::instance()->automatic_audio_analysis() && content->audio) {
+       if (Config::instance()->automatic_audio_analysis() && content->audio && !disable_audio_analysis) {
                shared_ptr<Playlist> playlist (new Playlist);
                playlist->add (content);
                boost::signals2::connection c;
@@ -1104,7 +1087,7 @@ Film::add_content (shared_ptr<Content> c)
                /* 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);
+                               c->take_settings_from (i);
                        }
                }
        }
@@ -1134,7 +1117,7 @@ Film::move_content_later (shared_ptr<Content> c)
 DCPTime
 Film::length () const
 {
-       return _playlist->length ();
+       return _playlist->length().ceil(video_frame_rate());
 }
 
 int
@@ -1218,8 +1201,12 @@ Film::frame_size () const
        return fit_ratio_within (container()->ratio(), full_frame ());
 }
 
-/** @param from KDM from time expressed as a local time with an offset from UTC
- *  @param to KDM to time expressed as a local time with an offset from UTC
+/** @param recipient KDM recipient certificate.
+ *  @param trusted_devices Certificates of other trusted devices (can be empty).
+ *  @param cpl_file CPL filename.
+ *  @param from KDM from time expressed as a local time with an offset from UTC.
+ *  @param until KDM to time expressed as a local time with an offset from UTC.
+ *  @param formulation KDM formulation to use.
  */
 dcp::EncryptedKDM
 Film::make_kdm (
@@ -1278,13 +1265,16 @@ Film::make_kdm (
                ).encrypt (signer, recipient, trusted_devices, formulation);
 }
 
-/** @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema.
- *  @param to KDM to time expressed as a local time in the time zone of the Screen's Cinema.
+/** @param screens Screens to make KDMs for.
+ *  @param cpl_file Path to CPL to make KDMs for.
+ *  @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema.
+ *  @param until KDM to time expressed as a local time in the time zone of the Screen's Cinema.
+ *  @param formulation KDM formulation to use.
  */
 list<ScreenKDM>
 Film::make_kdms (
        list<shared_ptr<Screen> > screens,
-       boost::filesystem::path dcp,
+       boost::filesystem::path cpl_file,
        boost::posix_time::ptime from,
        boost::posix_time::ptime until,
        dcp::Formulation formulation
@@ -1297,7 +1287,7 @@ Film::make_kdms (
                        dcp::EncryptedKDM const kdm = make_kdm (
                                i->recipient.get(),
                                i->trusted_devices,
-                               dcp,
+                               cpl_file,
                                dcp::LocalTime (from, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
                                dcp::LocalTime (until, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
                                formulation
@@ -1470,7 +1460,7 @@ list<DCPTimePeriod>
 Film::reels () const
 {
        list<DCPTimePeriod> p;
-       DCPTime const len = length().ceil (video_frame_rate ());
+       DCPTime const len = length();
 
        switch (reel_type ()) {
        case REELTYPE_SINGLE:
@@ -1521,6 +1511,9 @@ Film::reels () const
        return p;
 }
 
+/** @param period A period within the DCP
+ *  @return Name of the content which most contributes to the given period.
+ */
 string
 Film::content_summary (DCPTimePeriod period) const
 {
@@ -1585,3 +1578,41 @@ Film::use_template (string name)
        _reel_length = _template_film->_reel_length;
        _upload_after_make_dcp = _template_film->_upload_after_make_dcp;
 }
+
+pair<double, double>
+Film::speed_up_range (int dcp_frame_rate) const
+{
+       return _playlist->speed_up_range (dcp_frame_rate);
+}
+
+void
+Film::copy_from (shared_ptr<const Film> film)
+{
+       read_metadata (film->file (metadata_file));
+}
+
+bool
+Film::references_dcp_video () const
+{
+       BOOST_FOREACH (shared_ptr<Content> i, _playlist->content()) {
+               shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent>(i);
+               if (d && d->reference_video()) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+bool
+Film::references_dcp_audio () const
+{
+       BOOST_FOREACH (shared_ptr<Content> i, _playlist->content()) {
+               shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent>(i);
+               if (d && d->reference_audio()) {
+                       return true;
+               }
+       }
+
+       return false;
+}