No-op; fix GPL address and use the explicit-program-name version.
[dcpomatic.git] / src / lib / film.cc
index d406152a19a494a865401979baa0e9e13eed749a..6b3625d9e727d34faec4effa67da7304aa1d832f 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/>.
 
 */
 
@@ -50,6 +51,7 @@
 #include "ffmpeg_content.h"
 #include "dcp_content.h"
 #include "screen_kdm.h"
+#include "cinema.h"
 #include <libcxml/cxml.h>
 #include <dcp/cpl.h>
 #include <dcp/certificate_chain.h>
@@ -103,8 +105,11 @@ using boost::is_any_of;
  *
  * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
  * than frames now.
+ *
+ * 32 -> 33
+ * Changed <Period> to <Subtitle> in FFmpegSubtitleStream
  */
-int const Film::current_state_version = 32;
+int const Film::current_state_version = 33;
 
 /** Construct a Film object in a given directory.
  *
@@ -124,21 +129,24 @@ Film::Film (boost::filesystem::path dir, bool log)
        , _video_frame_rate (24)
        , _audio_channels (6)
        , _three_d (false)
-       , _sequence_video (true)
+       , _sequence (true)
        , _interop (Config::instance()->default_interop ())
        , _audio_processor (0)
        , _reel_type (REELTYPE_SINGLE)
        , _reel_length (2000000000)
+       , _upload_after_make_dcp (false)
        , _state_version (current_state_version)
        , _dirty (false)
 {
        set_isdcf_date_today ();
 
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this));
+       _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?
        */
 
        boost::filesystem::path p (boost::filesystem::system_complete (dir));
@@ -162,7 +170,7 @@ Film::Film (boost::filesystem::path dir, bool log)
                _log.reset (new NullLog);
        }
 
-       _playlist->set_sequence_video (_sequence_video);
+       _playlist->set_sequence (_sequence);
 }
 
 Film::~Film ()
@@ -238,13 +246,12 @@ Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
 
        MD5Digester digester;
        BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
-               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (i);
-               if (!ac) {
+               if (!i->audio) {
                        continue;
                }
 
-               digester.add (ac->digest ());
-               digester.add (ac->audio_mapping().digest ());
+               digester.add (i->digest ());
+               digester.add (i->audio->mapping().digest ());
                if (playlist->content().size() != 1) {
                        /* Analyses should be considered equal regardless of gain
                           if they were made from just one piece of content.  This
@@ -252,7 +259,7 @@ Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
                           analysis at the plotting stage rather than having to
                           recompute it.
                        */
-                       digester.add (ac->audio_gain ());
+                       digester.add (i->audio->gain ());
                }
        }
 
@@ -274,7 +281,9 @@ Film::make_dcp ()
 
        set_isdcf_date_today ();
 
-       environment_info (log ());
+       BOOST_FOREACH (string i, environment_info ()) {
+               LOG_GENERAL_NC (i);
+       }
 
        BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
                LOG_GENERAL ("Content: %1", i->technical_summary());
@@ -339,7 +348,7 @@ Film::metadata () const
        root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
        root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
        root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
-       root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0");
+       root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0");
        root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
        root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
        root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
@@ -349,6 +358,7 @@ Film::metadata () const
        }
        root->add_child("ReelType")->add_child_text (raw_convert<string> (_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");
        _playlist->as_xml (root->add_child ("Playlist"));
 
        return doc;
@@ -421,7 +431,13 @@ Film::read_metadata ()
        } else if ((_audio_channels % 2) == 1) {
                _audio_channels++;
        }
-       _sequence_video = f.bool_child ("SequenceVideo");
+
+       if (f.optional_bool_child("SequenceVideo")) {
+               _sequence = f.bool_child("SequenceVideo");
+       } else {
+               _sequence = f.bool_child("Sequence");
+       }
+
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
        _key = dcp::Key (f.string_child ("Key"));
@@ -434,6 +450,7 @@ Film::read_metadata ()
 
        _reel_type = static_cast<ReelType> (f.optional_number_child<int>("ReelType").get_value_or (static_cast<int>(REELTYPE_SINGLE)));
        _reel_length = f.optional_number_child<int64_t>("ReelLength").get_value_or (2000000000);
+       _upload_after_make_dcp = f.optional_bool_child("UploadAfterMakeDCP").get_value_or (false);
 
        list<string> notes;
        /* This method is the only one that can return notes (so far) */
@@ -476,6 +493,31 @@ Film::file (boost::filesystem::path f) const
        return p;
 }
 
+list<int>
+Film::mapped_audio_channels () const
+{
+       list<int> mapped;
+
+       if (audio_processor ()) {
+               /* Processors are mapped 1:1 to DCP outputs so we can work out mappings from there */
+               for (int i = 0; i < audio_processor()->out_channels(); ++i) {
+                       mapped.push_back (i);
+               }
+       } else {
+               BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+                       if (i->audio) {
+                               list<int> c = i->audio->mapping().mapped_output_channels ();
+                               copy (c.begin(), c.end(), back_inserter (mapped));
+                       }
+               }
+
+               mapped.sort ();
+               mapped.unique ();
+       }
+
+       return mapped;
+}
+
 /** @return a ISDCF-compliant name for a DCP of this film */
 string
 Film::isdcf_name (bool if_created_now) const
@@ -574,13 +616,12 @@ Film::isdcf_name (bool if_created_now) const
        if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
                Ratio const * content_ratio = 0;
                BOOST_FOREACH (shared_ptr<Content> i, content ()) {
-                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (i);
-                       if (vc) {
+                       if (i->video) {
                                /* Here's the first piece of video content */
-                               if (vc->scale().ratio ()) {
-                                       content_ratio = vc->scale().ratio ();
+                               if (i->video->scale().ratio ()) {
+                                       content_ratio = i->video->scale().ratio ();
                                } else {
-                                       content_ratio = Ratio::from_ratio (vc->video_size().ratio ());
+                                       content_ratio = Ratio::from_ratio (i->video->size().ratio ());
                                }
                                break;
                        }
@@ -594,7 +635,26 @@ Film::isdcf_name (bool if_created_now) const
        if (!dm.audio_language.empty ()) {
                d << "_" << dm.audio_language;
                if (!dm.subtitle_language.empty()) {
-                       d << "-" << dm.subtitle_language;
+
+                       bool burnt_in = true;
+                       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+                               if (!i->subtitle) {
+                                       continue;
+                               }
+
+                               if (i->subtitle->use() && !i->subtitle->burn()) {
+                                       burnt_in = false;
+                               }
+                       }
+
+                       string language = dm.subtitle_language;
+                       if (burnt_in && language != "XX") {
+                               transform (language.begin(), language.end(), language.begin(), ::tolower);
+                       } else {
+                               transform (language.begin(), language.end(), language.begin(), ::toupper);
+                       }
+
+                       d << "-" << language;
                } else {
                        d << "-XX";
                }
@@ -609,46 +669,21 @@ Film::isdcf_name (bool if_created_now) const
                }
        }
 
-       /* Find all mapped channels */
+       /* Count mapped audio channels */
 
        int non_lfe = 0;
        int lfe = 0;
 
-       if (audio_processor ()) {
-               /* Processors are mapped 1:1 to DCP outputs so we can guess the number of LFE/
-                  non-LFE from the channel counts.
-               */
-               non_lfe = audio_processor()->out_channels ();
-               if (non_lfe >= 4) {
-                       --non_lfe;
-                       ++lfe;
-               }
-       } else {
-               list<int> mapped;
-               BOOST_FOREACH (shared_ptr<Content> i, content ()) {
-                       shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (i);
-                       if (ac) {
-                               list<int> c = ac->audio_mapping().mapped_output_channels ();
-                               copy (c.begin(), c.end(), back_inserter (mapped));
-                       }
+       BOOST_FOREACH (int i, mapped_audio_channels ()) {
+               if (i >= audio_channels()) {
+                       /* This channel is mapped but is not included in the DCP */
+                       continue;
                }
 
-               mapped.sort ();
-               mapped.unique ();
-
-               /* Count them */
-
-               for (list<int>::const_iterator i = mapped.begin(); i != mapped.end(); ++i) {
-                       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 (static_cast<dcp::Channel> (i) == dcp::LFE) {
+                       ++lfe;
+               } else {
+                       ++non_lfe;
                }
        }
 
@@ -831,6 +866,7 @@ Film::set_reel_type (ReelType t)
        signal_changed (REEL_TYPE);
 }
 
+/** @param r Desired reel length in bytes */
 void
 Film::set_reel_length (int64_t r)
 {
@@ -838,6 +874,13 @@ Film::set_reel_length (int64_t r)
        signal_changed (REEL_LENGTH);
 }
 
+void
+Film::set_upload_after_make_dcp (bool u)
+{
+       _upload_after_make_dcp = u;
+       signal_changed (UPLOAD_AFTER_MAKE_DCP);
+}
+
 void
 Film::signal_changed (Property p)
 {
@@ -845,11 +888,11 @@ Film::signal_changed (Property p)
 
        switch (p) {
        case Film::CONTENT:
-               set_video_frame_rate (_playlist->best_dcp_frame_rate ());
+               set_video_frame_rate (_playlist->best_video_frame_rate ());
                break;
        case Film::VIDEO_FRAME_RATE:
-       case Film::SEQUENCE_VIDEO:
-               _playlist->maybe_sequence_video ();
+       case Film::SEQUENCE:
+               _playlist->maybe_sequence ();
                break;
        default:
                break;
@@ -988,7 +1031,7 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
        }
 
        add_content (content);
-       if (Config::instance()->automatic_audio_analysis ()) {
+       if (Config::instance()->automatic_audio_analysis() && content->audio) {
                shared_ptr<Playlist> playlist (new Playlist);
                playlist->add (content);
                boost::signals2::connection c;
@@ -1002,9 +1045,11 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
 void
 Film::add_content (shared_ptr<Content> c)
 {
-       /* Add video content after any existing content */
-       if (dynamic_pointer_cast<VideoContent> (c)) {
+       /* Add {video,subtitle} content after any existing {video,subtitle} content */
+       if (c->video) {
                c->set_position (_playlist->video_end ());
+       } else if (c->subtitle) {
+               c->set_position (_playlist->subtitle_end ());
        }
 
        _playlist->add (c);
@@ -1038,7 +1083,7 @@ Film::length () const
 int
 Film::best_video_frame_rate () const
 {
-       return _playlist->best_dcp_frame_rate ();
+       return _playlist->best_video_frame_rate ();
 }
 
 FrameRateChange
@@ -1052,9 +1097,9 @@ Film::playlist_content_changed (weak_ptr<Content> c, int p, bool frequent)
 {
        _dirty = true;
 
-       if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
-               set_video_frame_rate (_playlist->best_dcp_frame_rate ());
-       } else if (p == AudioContentProperty::AUDIO_STREAMS) {
+       if (p == ContentProperty::VIDEO_FRAME_RATE) {
+               set_video_frame_rate (_playlist->best_video_frame_rate ());
+       } else if (p == AudioContentProperty::STREAMS) {
                signal_changed (NAME);
        }
 
@@ -1068,12 +1113,17 @@ Film::playlist_changed ()
        signal_changed (NAME);
 }
 
+void
+Film::playlist_order_changed ()
+{
+       signal_changed (CONTENT_ORDER);
+}
+
 int
 Film::audio_frame_rate () const
 {
        BOOST_FOREACH (shared_ptr<Content> i, content ()) {
-               shared_ptr<AudioContent> a = dynamic_pointer_cast<AudioContent> (i);
-               if (a && a->has_rate_above_48k ()) {
+               if (i->audio && i->audio->has_rate_above_48k ()) {
                        return 96000;
                }
        }
@@ -1082,11 +1132,11 @@ Film::audio_frame_rate () const
 }
 
 void
-Film::set_sequence_video (bool s)
+Film::set_sequence (bool s)
 {
-       _sequence_video = s;
-       _playlist->set_sequence_video (s);
-       signal_changed (SEQUENCE_VIDEO);
+       _sequence = s;
+       _playlist->set_sequence (s);
+       signal_changed (SEQUENCE);
 }
 
 /** @return Size of the largest possible image in whatever resolution we are using */
@@ -1111,9 +1161,13 @@ 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
+ */
 dcp::EncryptedKDM
 Film::make_kdm (
-       dcp::Certificate target,
+       dcp::Certificate recipient,
+       vector<dcp::Certificate> trusted_devices,
        boost::filesystem::path cpl_file,
        dcp::LocalTime from,
        dcp::LocalTime until,
@@ -1127,16 +1181,19 @@ Film::make_kdm (
        }
 
        return dcp::DecryptedKDM (
-               cpl, key(), from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string()
-               ).encrypt (signer, target, formulation);
+               cpl, key(), from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
+               ).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.
+ */
 list<ScreenKDM>
 Film::make_kdms (
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       dcp::LocalTime from,
-       dcp::LocalTime until,
+       boost::posix_time::ptime from,
+       boost::posix_time::ptime until,
        dcp::Formulation formulation
        ) const
 {
@@ -1144,7 +1201,16 @@ Film::make_kdms (
 
        BOOST_FOREACH (shared_ptr<Screen> i, screens) {
                if (i->recipient) {
-                       kdms.push_back (ScreenKDM (i, make_kdm (i->recipient.get(), dcp, from, until, formulation)));
+                       dcp::EncryptedKDM const kdm = make_kdm (
+                               i->recipient.get(),
+                               i->trusted_devices,
+                               dcp,
+                               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
+                               );
+
+                       kdms.push_back (ScreenKDM (i, kdm));
                }
        }
 
@@ -1157,12 +1223,12 @@ Film::make_kdms (
 uint64_t
 Film::required_disk_space () const
 {
-       return uint64_t (j2k_bandwidth() / 8) * length().seconds();
+       return _playlist->required_disk_space (j2k_bandwidth(), audio_channels(), audio_frame_rate());
 }
 
 /** This method checks the disk that the Film is on and tries to decide whether or not
  *  there will be enough space to make a DCP for it.  If so, true is returned; if not,
- *  false is returned and required and availabe are filled in with the amount of disk space
+ *  false is returned and required and available are filled in with the amount of disk space
  *  required and available respectively (in Gb).
  *
  *  Note: the decision made by this method isn't, of course, 100% reliable.
@@ -1202,9 +1268,8 @@ Film::subtitle_language () const
 
        ContentList cl = content ();
        BOOST_FOREACH (shared_ptr<Content>& c, cl) {
-               shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c);
-               if (sc) {
-                       languages.insert (sc->subtitle_language ());
+               if (c->subtitle) {
+                       languages.insert (c->subtitle->language ());
                }
        }
 
@@ -1270,8 +1335,8 @@ Film::audio_output_names () const
        n.push_back (_("BsR"));
        n.push_back (_("DBP"));
        n.push_back (_("DBS"));
-       n.push_back (_("NC"));
-       n.push_back (_("NC"));
+       n.push_back ("");
+       n.push_back ("");
 
        return vector<string> (n.begin(), n.begin() + audio_channels ());
 }
@@ -1307,18 +1372,17 @@ Film::reels () const
        case REELTYPE_BY_VIDEO_CONTENT:
        {
                optional<DCPTime> last_split;
-               shared_ptr<VideoContent> last_video;
+               shared_ptr<Content> last_video;
                ContentList cl = content ();
                BOOST_FOREACH (shared_ptr<Content> c, content ()) {
-                       shared_ptr<VideoContent> v = dynamic_pointer_cast<VideoContent> (c);
-                       if (v) {
-                               BOOST_FOREACH (DCPTime t, v->reel_split_points()) {
+                       if (c->video) {
+                               BOOST_FOREACH (DCPTime t, c->reel_split_points()) {
                                        if (last_split) {
                                                p.push_back (DCPTimePeriod (last_split.get(), t));
                                        }
                                        last_split = t;
                                }
-                               last_video = v;
+                               last_video = c;
                        }
                }