Take Film pointer out of Content.
[dcpomatic.git] / src / lib / film.cc
index 68ebddba2c5edb597ac0d74b32f7e3eb86346674..4d6b3d7e3b4fa132bf54b9e75fa568246f1e3615 100644 (file)
 #include "screen.h"
 #include "audio_content.h"
 #include "video_content.h"
-#include "subtitle_content.h"
+#include "text_content.h"
 #include "ffmpeg_content.h"
 #include "dcp_content.h"
 #include "screen_kdm.h"
 #include "cinema.h"
+#include "change_signaller.h"
+#include "check_content_change_job.h"
 #include <libcxml/cxml.h>
 #include <dcp/cpl.h>
 #include <dcp/certificate_chain.h>
@@ -124,8 +126,11 @@ string const Film::metadata_file = "metadata.xml";
  * VideoFrameType in VideoContent is a string rather than an integer.
  * 35 -> 36
  * EffectColour rather than OutlineColour in Subtitle.
+ * 36 -> 37
+ * TextContent can be in a Caption tag, and some of the tag names
+ * have had Subtitle prefixes or suffixes removed.
  */
-int const Film::current_state_version = 36;
+int const Film::current_state_version = 37;
 
 /** Construct a Film object in a given directory.
  *
@@ -152,14 +157,15 @@ Film::Film (optional<boost::filesystem::path> dir)
        , _reel_type (REELTYPE_SINGLE)
        , _reel_length (2000000000)
        , _upload_after_make_dcp (Config::instance()->default_upload_after_make_dcp())
+       , _reencode_j2k (false)
        , _state_version (current_state_version)
        , _dirty (false)
 {
        set_isdcf_date_today ();
 
-       _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this));
+       _playlist_change_connection = _playlist->Change.connect (bind (&Film::playlist_change, this, _1));
        _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_content_change_connection = _playlist->ContentChange.connect (bind (&Film::playlist_content_change, this, _1, _2, _3, _4));
 
        if (dir) {
                /* Make state.directory a complete path without ..s (where possible)
@@ -293,7 +299,7 @@ void
 Film::make_dcp ()
 {
        if (dcp_name().find ("/") != string::npos) {
-               throw BadSettingError (_("name"), _("cannot contain slashes"));
+               throw BadSettingError (_("name"), _("Cannot contain slashes"));
        }
 
        if (container() == 0) {
@@ -301,7 +307,7 @@ Film::make_dcp ()
        }
 
        if (content().empty()) {
-               throw runtime_error (_("you must add some content to the DCP before creating it"));
+               throw runtime_error (_("You must add some content to the DCP before creating it"));
        }
 
        if (dcp_content_type() == 0) {
@@ -318,10 +324,10 @@ Film::make_dcp ()
                }
                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"));
+                       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"));
+                       throw runtime_error (_("Some of your content needs an OV"));
                }
        }
 
@@ -344,7 +350,8 @@ Film::make_dcp ()
 
        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);
+       shared_ptr<CheckContentChangeJob> cc (new CheckContentChangeJob (shared_from_this(), tj));
+       JobManager::instance()->add (cc);
 }
 
 /** Start a job to send our DCP to the configured TMS */
@@ -392,11 +399,19 @@ Film::metadata (bool with_content_paths) const
        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("ReencodeJ2K")->add_child_text (_reencode_j2k ? "1" : "0");
        _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
 
        return doc;
 }
 
+void
+Film::write_metadata (boost::filesystem::path path) const
+{
+       shared_ptr<xmlpp::Document> doc = metadata ();
+       doc->write_to_file_formatted (path.string());
+}
+
 /** Write state to our `metadata' file */
 void
 Film::write_metadata () const
@@ -499,6 +514,7 @@ Film::read_metadata (optional<boost::filesystem::path> path)
        _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);
+       _reencode_j2k = f.optional_bool_child("ReencodeJ2K").get_value_or(false);
 
        list<string> notes;
        /* This method is the only one that can return notes (so far) */
@@ -694,14 +710,19 @@ Film::isdcf_name (bool if_created_now) const
                d += "_" + dm.audio_language;
                if (!dm.subtitle_language.empty()) {
 
-                       bool burnt_in = true;
-                       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
-                               if (!i->subtitle) {
-                                       continue;
-                               }
+                       /* I'm not clear on the precise details of the convention for CCAP labelling;
+                          for now I'm just appending -CCAP if we have any closed captions.
+                       */
 
-                               if (i->subtitle->use() && !i->subtitle->burn()) {
-                                       burnt_in = false;
+                       bool burnt_in = true;
+                       bool ccap = false;
+                       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+                               BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
+                                       if (j->type() == TEXT_OPEN_SUBTITLE && j->use() && !j->burn()) {
+                                               burnt_in = false;
+                                       } else if (j->type() == TEXT_CLOSED_CAPTION) {
+                                               ccap = true;
+                                       }
                                }
                        }
 
@@ -713,6 +734,9 @@ Film::isdcf_name (bool if_created_now) const
                        }
 
                        d += "-" + language;
+                       if (ccap) {
+                               d += "-CCAP";
+                       }
                } else {
                        d += "-XX";
                }
@@ -730,7 +754,9 @@ Film::isdcf_name (bool if_created_now) const
        /* Count mapped audio channels */
 
        pair<int, int> ch = audio_channel_types (mapped_audio_channels(), audio_channels());
-       if (ch.first) {
+       if (!ch.first && !ch.second) {
+               d += "_MOS";
+       } else if (ch.first) {
                d += String::compose("_%1%2", ch.first, ch.second);
        }
 
@@ -765,7 +791,17 @@ Film::isdcf_name (bool if_created_now) const
        bool vf = false;
        BOOST_FOREACH (shared_ptr<Content> i, content ()) {
                shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
-               if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) {
+               if (!dc) {
+                       continue;
+               }
+
+               bool any_text = false;
+               for (int i = 0; i < TEXT_COUNT; ++i) {
+                       if (dc->reference_text(static_cast<TextType>(i))) {
+                               any_text = true;
+                       }
+               }
+               if (dc->reference_video() || dc->reference_audio() || any_text) {
                        vf = true;
                }
        }
@@ -801,133 +837,149 @@ Film::set_directory (boost::filesystem::path d)
 void
 Film::set_name (string n)
 {
+       ChangeSignaller<Film> ch (this, NAME);
        _name = n;
-       signal_changed (NAME);
 }
 
 void
 Film::set_use_isdcf_name (bool u)
 {
+       ChangeSignaller<Film> ch (this, USE_ISDCF_NAME);
        _use_isdcf_name = u;
-       signal_changed (USE_ISDCF_NAME);
 }
 
 void
 Film::set_dcp_content_type (DCPContentType const * t)
 {
+       ChangeSignaller<Film> ch (this, DCP_CONTENT_TYPE);
        _dcp_content_type = t;
-       signal_changed (DCP_CONTENT_TYPE);
 }
 
 void
 Film::set_container (Ratio const * c)
 {
+       ChangeSignaller<Film> ch (this, CONTAINER);
        _container = c;
-       signal_changed (CONTAINER);
 }
 
 void
 Film::set_resolution (Resolution r)
 {
+       ChangeSignaller<Film> ch (this, RESOLUTION);
        _resolution = r;
-       signal_changed (RESOLUTION);
 }
 
 void
 Film::set_j2k_bandwidth (int b)
 {
+       ChangeSignaller<Film> ch (this, J2K_BANDWIDTH);
        _j2k_bandwidth = b;
-       signal_changed (J2K_BANDWIDTH);
 }
 
 void
 Film::set_isdcf_metadata (ISDCFMetadata m)
 {
+       ChangeSignaller<Film> ch (this, ISDCF_METADATA);
        _isdcf_metadata = m;
-       signal_changed (ISDCF_METADATA);
 }
 
 void
 Film::set_video_frame_rate (int f)
 {
+       ChangeSignaller<Film> ch (this, VIDEO_FRAME_RATE);
        _video_frame_rate = f;
-       signal_changed (VIDEO_FRAME_RATE);
 }
 
 void
 Film::set_audio_channels (int c)
 {
+       ChangeSignaller<Film> ch (this, AUDIO_CHANNELS);
        _audio_channels = c;
-       signal_changed (AUDIO_CHANNELS);
 }
 
 void
 Film::set_three_d (bool t)
 {
+       ChangeSignaller<Film> ch (this, THREE_D);
        _three_d = t;
-       signal_changed (THREE_D);
 
        if (_three_d && _isdcf_metadata.two_d_version_of_three_d) {
+               ChangeSignaller<Film> ch (this, ISDCF_METADATA);
                _isdcf_metadata.two_d_version_of_three_d = false;
-               signal_changed (ISDCF_METADATA);
        }
 }
 
 void
 Film::set_interop (bool i)
 {
+       ChangeSignaller<Film> ch (this, INTEROP);
        _interop = i;
-       signal_changed (INTEROP);
 }
 
 void
 Film::set_audio_processor (AudioProcessor const * processor)
 {
+       ChangeSignaller<Film> ch1 (this, AUDIO_PROCESSOR);
+       ChangeSignaller<Film> ch2 (this, AUDIO_CHANNELS);
        _audio_processor = processor;
-       signal_changed (AUDIO_PROCESSOR);
-       signal_changed (AUDIO_CHANNELS);
 }
 
 void
 Film::set_reel_type (ReelType t)
 {
+       ChangeSignaller<Film> ch (this, REEL_TYPE);
        _reel_type = t;
-       signal_changed (REEL_TYPE);
 }
 
 /** @param r Desired reel length in bytes */
 void
 Film::set_reel_length (int64_t r)
 {
+       ChangeSignaller<Film> ch (this, REEL_LENGTH);
        _reel_length = r;
-       signal_changed (REEL_LENGTH);
 }
 
 void
 Film::set_upload_after_make_dcp (bool u)
 {
+       ChangeSignaller<Film> ch (this, UPLOAD_AFTER_MAKE_DCP);
        _upload_after_make_dcp = u;
-       signal_changed (UPLOAD_AFTER_MAKE_DCP);
 }
 
 void
-Film::signal_changed (Property p)
+Film::set_reencode_j2k (bool r)
 {
-       _dirty = true;
+       ChangeSignaller<Film> ch (this, REENCODE_J2K);
+       _reencode_j2k = r;
+}
 
-       switch (p) {
-       case Film::CONTENT:
-               set_video_frame_rate (_playlist->best_video_frame_rate ());
-               break;
-       case Film::VIDEO_FRAME_RATE:
-       case Film::SEQUENCE:
-               _playlist->maybe_sequence ();
-               break;
-       default:
-               break;
-       }
+void
+Film::signal_change (ChangeType type, int p)
+{
+       signal_change (type, static_cast<Property>(p));
+}
+
+void
+Film::signal_change (ChangeType type, Property p)
+{
+       if (type == CHANGE_TYPE_DONE) {
+               _dirty = true;
+
+               if (p == Film::CONTENT) {
+                       set_video_frame_rate (_playlist->best_video_frame_rate ());
+               }
 
-       emit (boost::bind (boost::ref (Changed), p));
+               emit (boost::bind (boost::ref (Change), type, p));
+
+               if (p == Film::VIDEO_FRAME_RATE || p == Film::SEQUENCE) {
+                       /* We want to call Playlist::maybe_sequence but this must happen after the
+                          main signal emission (since the butler will see that emission and un-suspend itself).
+                       */
+                       emit (boost::bind(&Playlist::maybe_sequence, _playlist.get(), shared_from_this()));
+               }
+       } else {
+               Change (type, p);
+       }
 }
 
 void
@@ -1004,22 +1056,22 @@ Film::cpls () const
 void
 Film::set_signed (bool s)
 {
+       ChangeSignaller<Film> ch (this, SIGNED);
        _signed = s;
-       signal_changed (SIGNED);
 }
 
 void
 Film::set_encrypted (bool e)
 {
+       ChangeSignaller<Film> ch (this, ENCRYPTED);
        _encrypted = e;
-       signal_changed (ENCRYPTED);
 }
 
 void
 Film::set_key (dcp::Key key)
 {
+       ChangeSignaller<Film> ch (this, KEY);
        _key = key;
-       signal_changed (KEY);
 }
 
 ContentList
@@ -1064,10 +1116,10 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c, bool disable_audi
 
        if (Config::instance()->automatic_audio_analysis() && content->audio && !disable_audio_analysis) {
                shared_ptr<Playlist> playlist (new Playlist);
-               playlist->add (content);
+               playlist->add (shared_from_this(), content);
                boost::signals2::connection c;
                JobManager::instance()->analyse_audio (
-                       shared_from_this (), playlist, c, bind (&Film::audio_analysis_finished, this)
+                       shared_from_this(), playlist, false, c, bind (&Film::audio_analysis_finished, this)
                        );
                _audio_analysis_connections.push_back (c);
        }
@@ -1078,21 +1130,19 @@ Film::add_content (shared_ptr<Content> 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 ());
+               c->set_position (shared_from_this(), _playlist->video_end(shared_from_this()));
+       } else if (!c->text.empty()) {
+               c->set_position (shared_from_this(), _playlist->text_end(shared_from_this()));
        }
 
        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->take_settings_from (i);
-                       }
+                       c->take_settings_from (i);
                }
        }
 
-       _playlist->add (c);
+       _playlist->add (shared_from_this(), c);
 }
 
 void
@@ -1104,20 +1154,20 @@ Film::remove_content (shared_ptr<Content> c)
 void
 Film::move_content_earlier (shared_ptr<Content> c)
 {
-       _playlist->move_earlier (c);
+       _playlist->move_earlier (shared_from_this(), c);
 }
 
 void
 Film::move_content_later (shared_ptr<Content> c)
 {
-       _playlist->move_later (c);
+       _playlist->move_later (shared_from_this(), c);
 }
 
 /** @return length of the film from time 0 to the last thing on the playlist */
 DCPTime
 Film::length () const
 {
-       return _playlist->length().ceil(video_frame_rate());
+       return _playlist->length(shared_from_this()).ceil(video_frame_rate());
 }
 
 int
@@ -1133,30 +1183,33 @@ Film::active_frame_rate_change (DCPTime t) const
 }
 
 void
-Film::playlist_content_changed (weak_ptr<Content> c, int p, bool frequent)
+Film::playlist_content_change (ChangeType type, weak_ptr<Content> c, int p, bool frequent)
 {
-       _dirty = true;
-
        if (p == ContentProperty::VIDEO_FRAME_RATE) {
-               set_video_frame_rate (_playlist->best_video_frame_rate ());
+               signal_change (type, Film::CONTENT);
        } else if (p == AudioContentProperty::STREAMS) {
-               signal_changed (NAME);
+               signal_change (type, Film::NAME);
        }
 
-       emit (boost::bind (boost::ref (ContentChanged), c, p, frequent));
+       if (type == CHANGE_TYPE_DONE) {
+               emit (boost::bind (boost::ref (ContentChange), type, c, p, frequent));
+       } else {
+               ContentChange (type, c, p, frequent);
+       }
 }
 
 void
-Film::playlist_changed ()
+Film::playlist_change (ChangeType type)
 {
-       signal_changed (CONTENT);
-       signal_changed (NAME);
+       signal_change (type, CONTENT);
+       signal_change (type, NAME);
 }
 
 void
 Film::playlist_order_changed ()
 {
-       signal_changed (CONTENT_ORDER);
+       /* XXX: missing PENDING */
+       signal_change (CHANGE_TYPE_DONE, CONTENT_ORDER);
 }
 
 int
@@ -1174,9 +1227,13 @@ Film::audio_frame_rate () const
 void
 Film::set_sequence (bool s)
 {
+       if (s == _sequence) {
+               return;
+       }
+
+       ChangeSignaller<Film> cc (this, SEQUENCE);
        _sequence = s;
        _playlist->set_sequence (s);
-       signal_changed (SEQUENCE);
 }
 
 /** @return Size of the largest possible image in whatever resolution we are using */
@@ -1202,22 +1259,31 @@ Film::frame_size () const
 }
 
 /** @param recipient KDM recipient certificate.
- *  @param trusted_devices Certificates of other trusted devices (can be empty).
+ *  @param trusted_devices Certificate thumbprints 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.
+ *  @param disable_forensic_marking_picture true to disable forensic marking of picture.
+ *  @param disable_forensic_marking_audio if not set, don't disable forensic marking of audio.  If set to 0,
+ *  disable all forensic marking; if set above 0, disable forensic marking above that channel.
  */
 dcp::EncryptedKDM
 Film::make_kdm (
        dcp::Certificate recipient,
-       vector<dcp::Certificate> trusted_devices,
+       vector<string> trusted_devices,
        boost::filesystem::path cpl_file,
        dcp::LocalTime from,
        dcp::LocalTime until,
-       dcp::Formulation formulation
+       dcp::Formulation formulation,
+       bool disable_forensic_marking_picture,
+       optional<int> disable_forensic_marking_audio
        ) const
 {
+       if (!_encrypted) {
+               throw runtime_error (_("Cannot make a KDM as this project is not encrypted."));
+       }
+
        shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
        shared_ptr<const dcp::CertificateChain> signer = Config::instance()->signer_chain ();
        if (!signer->valid ()) {
@@ -1262,7 +1328,7 @@ Film::make_kdm (
 
        return dcp::DecryptedKDM (
                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, disable_forensic_marking_picture, disable_forensic_marking_audio);
 }
 
 /** @param screens Screens to make KDMs for.
@@ -1270,6 +1336,9 @@ Film::make_kdm (
  *  @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.
+ *  @param disable_forensic_marking_picture true to disable forensic marking of picture.
+ *  @param disable_forensic_marking_audio if not set, don't disable forensic marking of audio.  If set to 0,
+ *  disable all forensic marking; if set above 0, disable forensic marking above that channel.
  */
 list<ScreenKDM>
 Film::make_kdms (
@@ -1277,7 +1346,9 @@ Film::make_kdms (
        boost::filesystem::path cpl_file,
        boost::posix_time::ptime from,
        boost::posix_time::ptime until,
-       dcp::Formulation formulation
+       dcp::Formulation formulation,
+       bool disable_forensic_marking_picture,
+       optional<int> disable_forensic_marking_audio
        ) const
 {
        list<ScreenKDM> kdms;
@@ -1286,11 +1357,13 @@ Film::make_kdms (
                if (i->recipient) {
                        dcp::EncryptedKDM const kdm = make_kdm (
                                i->recipient.get(),
-                               i->trusted_devices,
+                               i->trusted_device_thumbprints(),
                                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
+                               formulation,
+                               disable_forensic_marking_picture,
+                               disable_forensic_marking_audio
                                );
 
                        kdms.push_back (ScreenKDM (i, kdm));
@@ -1306,7 +1379,7 @@ Film::make_kdms (
 uint64_t
 Film::required_disk_space () const
 {
-       return _playlist->required_disk_space (j2k_bandwidth(), audio_channels(), audio_frame_rate());
+       return _playlist->required_disk_space (shared_from_this(), j2k_bandwidth(), audio_channels(), audio_frame_rate());
 }
 
 /** This method checks the disk that the Film is on and tries to decide whether or not
@@ -1349,10 +1422,9 @@ Film::subtitle_language () const
 {
        set<string> languages;
 
-       ContentList cl = content ();
-       BOOST_FOREACH (shared_ptr<Content>& c, cl) {
-               if (c->subtitle) {
-                       languages.insert (c->subtitle->language ());
+       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+               BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
+                       languages.insert (j->language ());
                }
        }
 
@@ -1441,7 +1513,7 @@ Film::audio_output_names () const
 void
 Film::repeat_content (ContentList c, int n)
 {
-       _playlist->repeat (c, n);
+       _playlist->repeat (shared_from_this(), c, n);
 }
 
 void
@@ -1482,7 +1554,7 @@ Film::reels () const
                        }
                }
 
-               DCPTime video_end = last_video ? last_video->end() : DCPTime(0);
+               DCPTime video_end = last_video ? last_video->end(shared_from_this()) : DCPTime(0);
                if (last_split) {
                        /* Definitely go from the last split to the end of the video content */
                        p.push_back (DCPTimePeriod (last_split.get(), video_end));
@@ -1517,43 +1589,7 @@ Film::reels () const
 string
 Film::content_summary (DCPTimePeriod period) const
 {
-       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;
+       return _playlist->content_summary (shared_from_this(), period);
 }
 
 void
@@ -1577,6 +1613,7 @@ Film::use_template (string name)
        _reel_type = _template_film->_reel_type;
        _reel_length = _template_film->_reel_length;
        _upload_after_make_dcp = _template_film->_upload_after_make_dcp;
+       _isdcf_metadata = _template_film->_isdcf_metadata;
 }
 
 pair<double, double>
@@ -1616,3 +1653,20 @@ Film::references_dcp_audio () const
 
        return false;
 }
+
+list<DCPTextTrack>
+Film::closed_caption_tracks () const
+{
+       list<DCPTextTrack> tt;
+       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+               BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
+                       /* XXX: Empty DCPTextTrack ends up being a magic value here */
+                       DCPTextTrack dtt = j->dcp_track().get_value_or(DCPTextTrack());
+                       if (j->type() == TEXT_CLOSED_CAPTION && find(tt.begin(), tt.end(), dtt) == tt.end()) {
+                               tt.push_back (dtt);
+                       }
+               }
+       }
+
+       return tt;
+}