X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=0be1ddd7b71a19161c44f6957f63c5a3aae23213;hp=58c272eb16bedb078e901e91ca38da63e400176f;hb=d7ac100c0eb1b5efdcfbec59be870fd869252840;hpb=73654117144c6de0ec4efe39ddc88485df546cc9 diff --git a/src/lib/film.cc b/src/lib/film.cc index 58c272eb1..0be1ddd7b 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2016 Carl Hetherington + Copyright (C) 2012-2018 Carl Hetherington 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" @@ -45,7 +46,7 @@ #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" @@ -57,6 +58,8 @@ #include #include #include +#include +#include #include #include #include @@ -83,6 +86,9 @@ using std::cout; 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; @@ -93,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 @@ -124,7 +132,7 @@ int const Film::current_state_version = 36; * @param dir Film directory. */ -Film::Film (boost::filesystem::path dir, bool log) +Film::Film (optional dir) : _playlist (new Playlist) , _use_isdcf_name (true) , _dcp_content_type (Config::instance()->default_dcp_content_type ()) @@ -132,6 +140,7 @@ Film::Film (boost::filesystem::path dir, bool log) , _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) @@ -142,7 +151,7 @@ Film::Film (boost::filesystem::path dir, bool log) , _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) { @@ -152,27 +161,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)); - /* 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; - } 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); @@ -281,7 +293,36 @@ 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) { + 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()) { + set_name ("DCP"); + } + + BOOST_FOREACH (shared_ptr i, content ()) { + if (!i->paths_valid()) { + throw runtime_error (_("some of your content is missing")); + } + shared_ptr dcp = dynamic_pointer_cast (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 (); @@ -297,27 +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()); - 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 (new TranscodeJob (shared_from_this()))); + shared_ptr tj (new TranscodeJob (shared_from_this())); + tj->set_encoder (shared_ptr (new DCPEncoder (shared_from_this(), tj))); + JobManager::instance()->add (tj); } /** Start a job to send our DCP to the configured TMS */ @@ -329,7 +356,7 @@ Film::send_dcp_to_tms () } shared_ptr -Film::metadata () const +Film::metadata (bool with_content_paths) const { shared_ptr doc (new xmlpp::Document); xmlpp::Element* root = doc->create_root_node ("Metadata"); @@ -358,13 +385,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("ContextID")->add_child_text (_context_id); if (_audio_processor) { root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ()); } - root->add_child("ReelType")->add_child_text (raw_convert (_reel_type)); + root->add_child("ReelType")->add_child_text (raw_convert (static_cast (_reel_type))); root->add_child("ReelLength")->add_child_text (raw_convert (_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; } @@ -373,24 +401,38 @@ Film::metadata () const void Film::write_metadata () const { - boost::filesystem::create_directories (directory ()); + DCPOMATIC_ASSERT (directory()); + boost::filesystem::create_directories (directory().get()); shared_ptr doc = metadata (); - doc->write_to_file_formatted (file("metadata.xml").string ()); + doc->write_to_file_formatted (file(metadata_file).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 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 -Film::read_metadata () +Film::read_metadata (optional 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_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_file); } cxml::Document f ("Metadata"); - f.read_file (file ("metadata.xml")); + f.read_file (path.get ()); _state_version = f.number_child ("Version"); if (_state_version > current_state_version) { @@ -446,6 +488,7 @@ Film::read_metadata () _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")); @@ -462,23 +505,30 @@ 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 */ - set_backtrace_file (file ("backtrace.txt")); + if (_directory) { + set_backtrace_file (file ("backtrace.txt")); + } _dirty = false; return notes; } /** 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); + boost::filesystem::path p; - p /= _directory; + p /= _directory.get(); p /= d; - boost::filesystem::create_directories (p); + if (create) { + boost::filesystem::create_directories (p); + } return p; } @@ -489,8 +539,10 @@ Film::dir (boost::filesystem::path d) const boost::filesystem::path Film::file (boost::filesystem::path f) const { + DCPOMATIC_ASSERT (_directory); + boost::filesystem::path p; - p /= _directory; + p /= _directory.get(); p /= f; boost::filesystem::create_directories (p.parent_path ()); @@ -617,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 i, content ()) { @@ -633,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(); } } @@ -676,24 +729,11 @@ 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 (i) == dcp::LFE) { - ++lfe; - } else { - ++non_lfe; - } - } - - if (non_lfe) { - d += String::compose("_%1%2", non_lfe, lfe); + pair ch = audio_channel_types (mapped_audio_channels(), audio_channels()); + if (!ch.first && !ch.second) { + d += "_MOS"; + } else if (ch.first) { + d += String::compose("_%1%2", ch.first, ch.second); } /* XXX: HI/VI */ @@ -747,24 +787,10 @@ Film::dcp_name (bool if_created_now) const { string unfiltered; if (use_isdcf_name()) { - unfiltered = isdcf_name (if_created_now); - } else { - unfiltered = name (); + return careful_string_filter (isdcf_name (if_created_now)); } - /* 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 filtered; + return careful_string_filter (name ()); } void @@ -943,9 +969,13 @@ Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const vector Film::cpls () const { + if (!directory ()) { + return vector (); + } + vector 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) && @@ -955,12 +985,13 @@ Film::cpls () const 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(), - dcp.cpls().front()->file() + dcp.cpls().front()->file().get() ) ); } catch (...) { @@ -999,31 +1030,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_content (shared_ptr c) -{ - shared_ptr j (new ExamineContentJob (shared_from_this(), c)); - JobManager::instance()->add (j); -} - -void -Film::examine_and_add_content (shared_ptr c) +Film::examine_and_add_content (shared_ptr content, bool disable_audio_analysis) { - if (dynamic_pointer_cast (c) && !_directory.empty ()) { - run_ffprobe (c->path(0), file ("ffprobe.log"), _log); + if (dynamic_pointer_cast (content) && _directory) { + run_ffprobe (content->path(0), file ("ffprobe.log"), _log); } - shared_ptr j (new ExamineContentJob (shared_from_this(), c)); + shared_ptr j (new ExamineContentJob (shared_from_this(), content)); _job_connections.push_back ( - j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr (j), weak_ptr (c))) + j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr(j), weak_ptr(content), disable_audio_analysis)) ); JobManager::instance()->add (j); } void -Film::maybe_add_content (weak_ptr j, weak_ptr c) +Film::maybe_add_content (weak_ptr j, weak_ptr c, bool disable_audio_analysis) { shared_ptr job = j.lock (); if (!job || !job->finished_ok ()) { @@ -1036,12 +1063,13 @@ Film::maybe_add_content (weak_ptr j, weak_ptr 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 (new Playlist); playlist->add (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); } @@ -1057,6 +1085,13 @@ Film::add_content (shared_ptr c) 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 i, _template_film->content()) { + c->take_settings_from (i); + } + } + _playlist->add (c); } @@ -1082,7 +1117,7 @@ Film::move_content_later (shared_ptr c) DCPTime Film::length () const { - return _playlist->length (); + return _playlist->length().ceil(video_frame_rate()); } int @@ -1139,6 +1174,10 @@ Film::audio_frame_rate () const void Film::set_sequence (bool s) { + if (s == _sequence) { + return; + } + _sequence = s; _playlist->set_sequence (s); signal_changed (SEQUENCE); @@ -1166,8 +1205,15 @@ 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. + * @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 ( @@ -1176,30 +1222,80 @@ Film::make_kdm ( boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::LocalTime until, - dcp::Formulation formulation + dcp::Formulation formulation, + bool disable_forensic_marking_picture, + optional disable_forensic_marking_audio ) const { + if (!_encrypted) { + throw runtime_error (_("Cannot make a KDM as this project is not encrypted.")); + } + shared_ptr cpl (new dcp::CPL (cpl_file)); shared_ptr signer = Config::instance()->signer_chain (); if (!signer->valid ()) { throw InvalidSignerError (); } - return dcp::DecryptedKDM ( - cpl, key(), from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string() - ).encrypt (signer, recipient, trusted_devices, formulation); -} + /* Find keys that have been added to imported, encrypted DCP content */ + list imported_keys; + BOOST_FOREACH (shared_ptr i, content()) { + shared_ptr d = dynamic_pointer_cast (i); + if (d && d->kdm()) { + dcp::DecryptedKDM kdm (d->kdm().get(), Config::instance()->decryption_chain()->key().get()); + list keys = kdm.keys (); + copy (keys.begin(), keys.end(), back_inserter (imported_keys)); + } + } + + map, dcp::Key> keys; + + BOOST_FOREACH(shared_ptr i, cpl->reel_assets ()) { + shared_ptr mxf = boost::dynamic_pointer_cast (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()) { + LOG_GENERAL ("Using imported key for %1", mxf->key_id().get()); + keys[mxf] = j.key(); + done = true; + } + } + + if (!done) { + /* No imported key; it must be an asset that we encrypted */ + LOG_GENERAL ("Using our own key for %1", mxf->key_id().get()); + keys[mxf] = key(); + } + } -/** @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. + 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, disable_forensic_marking_picture, disable_forensic_marking_audio); +} + +/** @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. + * @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 Film::make_kdms ( list > screens, - boost::filesystem::path dcp, + 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 disable_forensic_marking_audio ) const { list kdms; @@ -1209,10 +1305,12 @@ 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 + formulation, + disable_forensic_marking_picture, + disable_forensic_marking_audio ); kdms.push_back (ScreenKDM (i, kdm)); @@ -1352,24 +1450,12 @@ Film::audio_output_names () const DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16); vector 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 (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 @@ -1394,7 +1480,7 @@ list Film::reels () const { list p; - DCPTime const len = length().round_up (video_frame_rate ()); + DCPTime const len = length(); switch (reel_type ()) { case REELTYPE_SINGLE: @@ -1445,8 +1531,73 @@ 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 { return _playlist->content_summary (period); } + +void +Film::use_template (string name) +{ + _template_film.reset (new Film (optional())); + _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; + _isdcf_metadata = _template_film->_isdcf_metadata; +} + +pair +Film::speed_up_range (int dcp_frame_rate) const +{ + return _playlist->speed_up_range (dcp_frame_rate); +} + +void +Film::copy_from (shared_ptr film) +{ + read_metadata (film->file (metadata_file)); +} + +bool +Film::references_dcp_video () const +{ + BOOST_FOREACH (shared_ptr i, _playlist->content()) { + shared_ptr d = dynamic_pointer_cast(i); + if (d && d->reference_video()) { + return true; + } + } + + return false; +} + +bool +Film::references_dcp_audio () const +{ + BOOST_FOREACH (shared_ptr i, _playlist->content()) { + shared_ptr d = dynamic_pointer_cast(i); + if (d && d->reference_audio()) { + return true; + } + } + + return false; +}