X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Ffilm.cc;h=6514efde82f3b62cbe4d5bfea1af8abff3f486e4;hb=d7ac100c0eb1b5efdcfbec59be870fd869252840;hp=18e99247800ac40274d600849a982efffb69bb51;hpb=4dbc6ef917aeceb906b1ef1caf6911033e7e2c54;p=dcpomatic.git diff --git a/src/lib/film.cc b/src/lib/film.cc index 18e992478..0be1ddd7b 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -1,91 +1,105 @@ /* - Copyright (C) 2012-2014 Carl Hetherington + Copyright (C) 2012-2018 Carl Hetherington - 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 . */ +/** @file src/film.cc + * @brief A representation of some audio, video and subtitle content, and details of + * how they should be presented in a DCP. + */ + #include "film.h" #include "job.h" #include "util.h" #include "job_manager.h" +#include "dcp_encoder.h" #include "transcode_job.h" -#include "scp_dcp_job.h" -#include "log.h" +#include "upload_job.h" +#include "null_log.h" +#include "file_log.h" #include "exceptions.h" #include "examine_content_job.h" -#include "scaler.h" #include "config.h" -#include "version.h" -#include "ui_signaller.h" #include "playlist.h" -#include "player.h" #include "dcp_content_type.h" #include "ratio.h" #include "cross.h" +#include "environment_info.h" +#include "audio_processor.h" +#include "digester.h" +#include "compose.hpp" +#include "screen.h" +#include "audio_content.h" +#include "video_content.h" +#include "text_content.h" +#include "ffmpeg_content.h" +#include "dcp_content.h" +#include "screen_kdm.h" #include "cinema.h" -#include "safe_stringstream.h" #include #include -#include +#include #include #include -#include #include +#include +#include +#include #include #include #include -#include #include +#include #include #include #include #include -#include #include #include +#include #include "i18n.h" using std::string; -using std::multimap; using std::pair; -using std::map; using std::vector; using std::setfill; using std::min; +using std::max; using std::make_pair; -using std::endl; 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; -using boost::to_upper_copy; -using boost::ends_with; -using boost::starts_with; using boost::optional; using boost::is_any_of; -using dcp::Size; -using dcp::Signer; -using dcp::raw_convert; using dcp::raw_convert; -#define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); -#define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, Log::TYPE_GENERAL); +#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. @@ -100,65 +114,94 @@ using dcp::raw_convert; * * Bumped to 32 for 2.0 branch; some times are expressed in Times rather * than frames now. + * + * 32 -> 33 + * Changed to in FFmpegSubtitleStream + * 33 -> 34 + * Content only contains audio/subtitle-related tags if those things + * are present. + * 34 -> 35 + * VideoFrameType in VideoContent is a string rather than an integer. + * 35 -> 36 + * EffectColour rather than OutlineColour in Subtitle. */ -int const Film::current_state_version = 32; +int const Film::current_state_version = 36; /** Construct a Film object in a given directory. * * @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 ()) , _container (Config::instance()->default_container ()) , _resolution (RESOLUTION_2K) - , _scaler (Scaler::from_id ("bicubic")) , _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) - , _audio_channels (6) + , _audio_channels (Config::instance()->default_dcp_audio_channels ()) , _three_d (false) - , _sequence_video (true) - , _interop (false) - , _burn_subtitles (false) + , _sequence (true) + , _interop (Config::instance()->default_interop ()) + , _audio_processor (0) + , _reel_type (REELTYPE_SINGLE) + , _reel_length (2000000000) + , _upload_after_make_dcp (Config::instance()->default_upload_after_make_dcp()) , _state_version (current_state_version) , _dirty (false) { set_isdcf_date_today (); - _playlist->Changed.connect (bind (&Film::playlist_changed, this)); - _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2)); - - /* Make state.directory a complete path without ..s (where possible) - (Code swiped from Adam Bowen on stackoverflow) - */ - - 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() == "..") { + _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)); + + 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.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); } - _playlist->set_sequence_video (_sequence_video); + _playlist->set_sequence (_sequence); +} + +Film::~Film () +{ + BOOST_FOREACH (boost::signals2::connection& i, _job_connections) { + i.disconnect (); + } + + BOOST_FOREACH (boost::signals2::connection& i, _audio_analysis_connections) { + i.disconnect (); + } } string @@ -166,156 +209,99 @@ Film::video_identifier () const { DCPOMATIC_ASSERT (container ()); - SafeStringStream s; - s.imbue (std::locale::classic ()); - - s << container()->id() - << "_" << resolution_to_string (_resolution) - << "_" << _playlist->video_identifier() - << "_" << _video_frame_rate - << "_" << scaler()->id() - << "_" << j2k_bandwidth(); + string s = container()->id() + + "_" + resolution_to_string (_resolution) + + "_" + _playlist->video_identifier() + + "_" + raw_convert(_video_frame_rate) + + "_" + raw_convert(j2k_bandwidth()); if (encrypted ()) { - s << "_E"; + s += "_E"; } else { - s << "_P"; + s += "_P"; } if (_interop) { - s << "_I"; + s += "_I"; } else { - s << "_S"; - } - - if (_burn_subtitles) { - s << "_B"; + s += "_S"; } if (_three_d) { - s << "_3D"; + s += "_3D"; } - return s.str (); + return s; } - -/** @return The path to the directory to write video frame info files to */ + +/** @return The file to write video frame info to */ boost::filesystem::path -Film::info_dir () const +Film::info_file (DCPTimePeriod period) const { boost::filesystem::path p; p /= "info"; - p /= video_identifier (); - return dir (p); + p /= video_identifier () + "_" + raw_convert (period.from.get()) + "_" + raw_convert (period.to.get()); + return file (p); } boost::filesystem::path -Film::internal_video_mxf_dir () const +Film::internal_video_asset_dir () const { return dir ("video"); } boost::filesystem::path -Film::internal_video_mxf_filename () const +Film::internal_video_asset_filename (DCPTimePeriod p) const { - return video_identifier() + ".mxf"; + return video_identifier() + "_" + raw_convert (p.from.get()) + "_" + raw_convert (p.to.get()) + ".mxf"; } boost::filesystem::path -Film::video_mxf_filename () const +Film::audio_analysis_path (shared_ptr playlist) const { - return filename_safe_name() + "_video.mxf"; -} + boost::filesystem::path p = dir ("analysis"); -boost::filesystem::path -Film::audio_mxf_filename () const -{ - return filename_safe_name() + "_audio.mxf"; -} - -boost::filesystem::path -Film::subtitle_xml_filename () const -{ - return filename_safe_name() + "_subtitle.xml"; -} + Digester digester; + BOOST_FOREACH (shared_ptr i, playlist->content ()) { + if (!i->audio) { + continue; + } -string -Film::filename_safe_name () const -{ - string const n = name (); - string o; - for (size_t i = 0; i < n.length(); ++i) { - if (isalnum (n[i])) { - o += n[i]; - } else { - o += "_"; + 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 + is because we can fake any gain change in a single-content + analysis at the plotting stage rather than having to + recompute it. + */ + digester.add (i->audio->gain ()); } } - return o; -} + if (audio_processor ()) { + digester.add (audio_processor()->id ()); + } -boost::filesystem::path -Film::audio_analysis_dir () const -{ - return dir ("analysis"); + p /= digester.get (); + return p; } /** Add suitable Jobs to the JobManager to create a DCP for this Film */ void Film::make_dcp () { - set_isdcf_date_today (); - if (dcp_name().find ("/") != string::npos) { - throw BadSettingError (_("name"), _("cannot contain slashes")); + throw BadSettingError (_("name"), _("Cannot contain slashes")); } - LOG_GENERAL ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()); - - { - char buffer[128]; - gethostname (buffer, sizeof (buffer)); - LOG_GENERAL ("Starting to make DCP on %1", buffer); - } - - ContentList cl = content (); - for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) { - LOG_GENERAL ("Content: %1", (*i)->technical_summary()); - } - LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate()); - LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads()); - LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth()); -#ifdef DCPOMATIC_DEBUG - LOG_GENERAL_NC ("DCP-o-matic built in debug mode."); -#else - LOG_GENERAL_NC ("DCP-o-matic built in optimised mode."); -#endif -#ifdef LIBDCP_DEBUG - LOG_GENERAL_NC ("libdcp built in debug mode."); -#else - LOG_GENERAL_NC ("libdcp built in optimised mode."); -#endif - -#ifdef DCPOMATIC_WINDOWS - OSVERSIONINFO info; - info.dwOSVersionInfoSize = sizeof (info); - GetVersionEx (&info); - LOG_GENERAL ("Windows version %1.%2.%3 SP %4", info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber, info.szCSDVersion); -#endif - - LOG_GENERAL ("CPU: %1, %2 processors", cpu_info(), boost::thread::hardware_concurrency ()); - list > const m = mount_info (); - for (list >::const_iterator i = m.begin(); i != m.end(); ++i) { - LOG_GENERAL ("Mount: %1 %2", i->first, i->second); - } - if (container() == 0) { throw MissingSettingError (_("container")); } if (content().empty()) { - throw StringError (_("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) { @@ -323,41 +309,54 @@ Film::make_dcp () } if (name().empty()) { - throw MissingSettingError (_("name")); + 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 (); + + BOOST_FOREACH (string i, environment_info ()) { + LOG_GENERAL_NC (i); } - JobManager::instance()->add (shared_ptr (new TranscodeJob (shared_from_this()))); + BOOST_FOREACH (shared_ptr i, content ()) { + LOG_GENERAL ("Content: %1", i->technical_summary()); + } + LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate()); + if (Config::instance()->only_servers_encode ()) { + LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE"); + } else { + LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads()); + } + LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth()); + + 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 */ void Film::send_dcp_to_tms () { - shared_ptr j (new SCPDCPJob (shared_from_this())); + shared_ptr j (new UploadJob (shared_from_this())); JobManager::instance()->add (j); } -/** Count the number of frames that have been encoded for this film. - * @return frame count. - */ -int -Film::encoded_frames () const -{ - if (container() == 0) { - return 0; - } - - int N = 0; - for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) { - ++N; - boost::this_thread::interruption_point (); - } - - return N; -} - 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"); @@ -375,20 +374,25 @@ Film::metadata () const } root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution)); - root->add_child("Scaler")->add_child_text (_scaler->id ()); root->add_child("J2KBandwidth")->add_child_text (raw_convert (_j2k_bandwidth)); _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata")); root->add_child("VideoFrameRate")->add_child_text (raw_convert (_video_frame_rate)); root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date)); root->add_child("AudioChannels")->add_child_text (raw_convert (_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("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0"); 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 ()); - _playlist->as_xml (root->add_child ("Playlist")); + 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 (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"), with_content_paths); return doc; } @@ -397,30 +401,44 @@ 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 StringError (_("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) { - throw StringError (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!")); + throw runtime_error (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!")); } - + _name = f.string_child ("Name"); if (_state_version >= 9) { _use_isdcf_name = f.bool_child ("UseISDCFName"); @@ -447,43 +465,71 @@ Film::read_metadata () } _resolution = string_to_resolution (f.string_child ("Resolution")); - _scaler = Scaler::from_id (f.string_child ("Scaler")); _j2k_bandwidth = f.number_child ("J2KBandwidth"); _video_frame_rate = f.number_child ("VideoFrameRate"); _signed = f.optional_bool_child("Signed").get_value_or (true); _encrypted = f.bool_child ("Encrypted"); _audio_channels = f.number_child ("AudioChannels"); - _sequence_video = f.bool_child ("SequenceVideo"); + /* We used to allow odd numbers (and zero) channels, but it's just not worth + the pain. + */ + if (_audio_channels == 0) { + _audio_channels = 2; + } else if ((_audio_channels % 2) == 1) { + _audio_channels++; + } + + 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"); - if (_state_version >= 32) { - _burn_subtitles = f.bool_child ("BurnSubtitles"); - } _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")); + } else { + _audio_processor = 0; + } + + _reel_type = static_cast (f.optional_number_child("ReelType").get_value_or (static_cast(REELTYPE_SINGLE))); + _reel_length = f.optional_number_child("ReelLength").get_value_or (2000000000); + _upload_after_make_dcp = f.optional_bool_child("UploadAfterMakeDCP").get_value_or (false); list notes; /* This method is the only one that can return notes (so far) */ _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; } @@ -493,29 +539,56 @@ 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 ()); - + return p; } +list +Film::mapped_audio_channels () const +{ + list 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 i, content ()) { + if (i->audio) { + list 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 { - SafeStringStream d; + string d; string raw_name = name (); /* Split the raw name up into words */ vector words; - split (words, raw_name, is_any_of (" ")); + split (words, raw_name, is_any_of (" _-")); string fixed_name; - + /* Add each word to fixed_name */ for (vector::const_iterator i = words.begin(); i != words.end(); ++i) { string w = *i; @@ -530,7 +603,7 @@ Film::isdcf_name (bool if_created_now) const ++caps; } } - + /* If w is all caps make the rest of it lower case, otherwise leave it alone. */ @@ -549,156 +622,175 @@ Film::isdcf_name (bool if_created_now) const fixed_name = fixed_name.substr (0, 14); } - d << fixed_name; + d += fixed_name; if (dcp_content_type()) { - d << "_" << dcp_content_type()->isdcf_name(); - d << "-" << isdcf_metadata().content_version; + d += "_" + dcp_content_type()->isdcf_name(); + d += "-" + raw_convert(isdcf_metadata().content_version); } ISDCFMetadata const dm = isdcf_metadata (); if (dm.temp_version) { - d << "-Temp"; + d += "-Temp"; } - + if (dm.pre_release) { - d << "-Pre"; + d += "-Pre"; } - + if (dm.red_band) { - d << "-RedBand"; + d += "-RedBand"; } - + if (!dm.chain.empty ()) { - d << "-" << dm.chain; + d += "-" + dm.chain; } if (three_d ()) { - d << "-3D"; + d += "-3D"; } if (dm.two_d_version_of_three_d) { - d << "-2D"; + d += "-2D"; } if (!dm.mastered_luminance.empty ()) { - d << "-" << dm.mastered_luminance; + d += "-" + dm.mastered_luminance; } if (video_frame_rate() != 24) { - d << "-" << video_frame_rate(); + d += "-" + raw_convert(video_frame_rate()); } - + if (container()) { - d << "_" << container()->isdcf_name(); + d += "_" + container()->isdcf_name(); } /* 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) { - ContentList cl = content (); Ratio const * content_ratio = 0; - for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) { - shared_ptr vc = dynamic_pointer_cast (*i); - if (vc) { + BOOST_FOREACH (shared_ptr i, content ()) { + 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; } } - + 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(); } } if (!dm.audio_language.empty ()) { - d << "_" << dm.audio_language; + d += "_" + dm.audio_language; if (!dm.subtitle_language.empty()) { - d << "-" << dm.subtitle_language; + + bool burnt_in = true; + BOOST_FOREACH (shared_ptr 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"; + d += "-XX"; } } if (!dm.territory.empty ()) { - d << "_" << dm.territory; - if (!dm.rating.empty ()) { - d << "-" << dm.rating; + d += "_" + dm.territory; + if (dm.rating.empty ()) { + d += "-NR"; + } else { + d += "-" + dm.rating; } } - switch (audio_channels ()) { - case 1: - d << "_10"; - break; - case 2: - d << "_20"; - break; - case 3: - d << "_30"; - break; - case 4: - d << "_40"; - break; - case 5: - d << "_50"; - break; - case 6: - d << "_51"; - break; + /* Count mapped audio channels */ + + 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 */ - d << "_" << resolution_to_string (_resolution); - + d += "_" + resolution_to_string (_resolution); + if (!dm.studio.empty ()) { - d << "_" << dm.studio; + d += "_" + dm.studio; } if (if_created_now) { - d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); + d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); } else { - d << "_" << boost::gregorian::to_iso_string (_isdcf_date); + d += "_" + boost::gregorian::to_iso_string (_isdcf_date); } if (!dm.facility.empty ()) { - d << "_" << dm.facility; + d += "_" + dm.facility; } if (_interop) { - d << "_IOP"; + d += "_IOP"; } else { - d << "_SMPTE"; + d += "_SMPTE"; } - + if (three_d ()) { - d << "-3D"; + d += "-3D"; } - if (!dm.package_type.empty ()) { - d << "_" << dm.package_type; + bool vf = false; + BOOST_FOREACH (shared_ptr i, content ()) { + shared_ptr dc = dynamic_pointer_cast (i); + if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) { + vf = true; + } } - return d.str (); + if (vf) { + d += "_VF"; + } else { + d += "_OV"; + } + + return d; } /** @return name to give the DCP */ string Film::dcp_name (bool if_created_now) const { + string unfiltered; if (use_isdcf_name()) { - return isdcf_name (if_created_now); + return careful_string_filter (isdcf_name (if_created_now)); } - return name(); + return careful_string_filter (name ()); } void @@ -743,13 +835,6 @@ Film::set_resolution (Resolution r) signal_changed (RESOLUTION); } -void -Film::set_scaler (Scaler const * s) -{ - _scaler = s; - signal_changed (SCALER); -} - void Film::set_j2k_bandwidth (int b) { @@ -783,6 +868,11 @@ Film::set_three_d (bool t) { _three_d = t; signal_changed (THREE_D); + + if (_three_d && _isdcf_metadata.two_d_version_of_three_d) { + _isdcf_metadata.two_d_version_of_three_d = false; + signal_changed (ISDCF_METADATA); + } } void @@ -793,10 +883,33 @@ Film::set_interop (bool i) } void -Film::set_burn_subtitles (bool b) +Film::set_audio_processor (AudioProcessor const * processor) { - _burn_subtitles = b; - signal_changed (BURN_SUBTITLES); + _audio_processor = processor; + signal_changed (AUDIO_PROCESSOR); + signal_changed (AUDIO_CHANNELS); +} + +void +Film::set_reel_type (ReelType t) +{ + _reel_type = t; + signal_changed (REEL_TYPE); +} + +/** @param r Desired reel length in bytes */ +void +Film::set_reel_length (int64_t r) +{ + _reel_length = 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 @@ -806,19 +919,17 @@ 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; } - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Changed), p)); - } + emit (boost::bind (boost::ref (Changed), p)); } void @@ -828,55 +939,29 @@ Film::set_isdcf_date_today () } boost::filesystem::path -Film::info_path (int f, Eyes e) const -{ - boost::filesystem::path p; - p /= info_dir (); - - SafeStringStream s; - s.width (8); - s << setfill('0') << f; - - if (e == EYES_LEFT) { - s << ".L"; - } else if (e == EYES_RIGHT) { - s << ".R"; - } - - s << ".md5"; - - p /= s.str(); - - /* info_dir() will already have added any initial bit of the path, - so don't call file() on this. - */ - return p; -} - -boost::filesystem::path -Film::j2c_path (int f, Eyes e, bool t) const +Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const { boost::filesystem::path p; p /= "j2c"; p /= video_identifier (); - SafeStringStream s; - s.width (8); - s << setfill('0') << f; + char buffer[256]; + snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame); + string s (buffer); - if (e == EYES_LEFT) { - s << ".L"; - } else if (e == EYES_RIGHT) { - s << ".R"; + if (eyes == EYES_LEFT) { + s += ".L"; + } else if (eyes == EYES_RIGHT) { + s += ".R"; } - - s << ".j2c"; - if (t) { - s << ".tmp"; + s += ".j2c"; + + if (tmp) { + s += ".tmp"; } - p /= s.str(); + p /= s; return file (p); } @@ -884,9 +969,13 @@ Film::j2c_path (int f, Eyes e, bool t) 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) && @@ -896,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 (...) { @@ -909,14 +999,8 @@ Film::cpls () const } } } - - return out; -} -shared_ptr -Film::make_player () const -{ - return shared_ptr (new Player (shared_from_this (), _playlist)); + return out; } void @@ -933,10 +1017,11 @@ Film::set_encrypted (bool e) signal_changed (ENCRYPTED); } -shared_ptr -Film::playlist () const +void +Film::set_key (dcp::Key key) { - return _playlist; + _key = key; + signal_changed (KEY); } ContentList @@ -945,45 +1030,66 @@ 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, bool calculate_digest) -{ - shared_ptr j (new ExamineContentJob (shared_from_this(), c, calculate_digest)); - JobManager::instance()->add (j); -} - -void -Film::examine_and_add_content (shared_ptr c, bool calculate_digest) +Film::examine_and_add_content (shared_ptr content, bool disable_audio_analysis) { - if (dynamic_pointer_cast (c)) { - 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, calculate_digest)); - j->Finished.connect (bind (&Film::maybe_add_content, this, boost::weak_ptr (j), boost::weak_ptr (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(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 ()) { return; } - + shared_ptr content = c.lock (); - if (content) { - add_content (content); + if (!content) { + return; + } + + add_content (content); + + 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, false, c, bind (&Film::audio_analysis_finished, this) + ); + _audio_analysis_connections.push_back (c); } } void Film::add_content (shared_ptr c) { - /* Add video content after any existing content */ - if (dynamic_pointer_cast (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 ()); + } + + 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); @@ -1007,16 +1113,17 @@ Film::move_content_later (shared_ptr c) _playlist->move_later (c); } +/** @return length of the film from time 0 to the last thing on the playlist */ DCPTime Film::length () const { - return _playlist->length (); + return _playlist->length().ceil(video_frame_rate()); } int Film::best_video_frame_rate () const { - return _playlist->best_dcp_frame_rate (); + return _playlist->best_video_frame_rate (); } FrameRateChange @@ -1026,36 +1133,54 @@ Film::active_frame_rate_change (DCPTime t) const } void -Film::playlist_content_changed (boost::weak_ptr c, int p) +Film::playlist_content_changed (weak_ptr c, int p, bool frequent) { - if (p == VideoContentProperty::VIDEO_FRAME_RATE) { - set_video_frame_rate (_playlist->best_dcp_frame_rate ()); - } + _dirty = true; - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); + if (p == ContentProperty::VIDEO_FRAME_RATE) { + set_video_frame_rate (_playlist->best_video_frame_rate ()); + } else if (p == AudioContentProperty::STREAMS) { + signal_changed (NAME); } + + emit (boost::bind (boost::ref (ContentChanged), c, p, frequent)); } void Film::playlist_changed () { signal_changed (CONTENT); -} + signal_changed (NAME); +} + +void +Film::playlist_order_changed () +{ + signal_changed (CONTENT_ORDER); +} int Film::audio_frame_rate () const { - /* XXX */ + BOOST_FOREACH (shared_ptr i, content ()) { + if (i->audio && i->audio->has_rate_above_48k ()) { + return 96000; + } + } + return 48000; } void -Film::set_sequence_video (bool s) +Film::set_sequence (bool s) { - _sequence_video = s; - _playlist->set_sequence_video (s); - signal_changed (SEQUENCE_VIDEO); + if (s == _sequence) { + return; + } + + _sequence = s; + _playlist->set_sequence (s); + signal_changed (SEQUENCE); } /** @return Size of the largest possible image in whatever resolution we are using */ @@ -1077,43 +1202,118 @@ Film::full_frame () const dcp::Size Film::frame_size () const { - return fit_ratio_within (container()->ratio(), full_frame (), 1); + return fit_ratio_within (container()->ratio(), full_frame ()); } +/** @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 ( - dcp::Certificate target, + dcp::Certificate recipient, + vector 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 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(); + shared_ptr signer = Config::instance()->signer_chain (); if (!signer->valid ()) { throw InvalidSignerError (); } - - return dcp::DecryptedKDM ( - cpl, key(), from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string() - ).encrypt (signer, target, formulation); -} -list + /* 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(); + } + } + + 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, - dcp::LocalTime from, - dcp::LocalTime until, - dcp::Formulation formulation + boost::filesystem::path cpl_file, + boost::posix_time::ptime from, + boost::posix_time::ptime until, + dcp::Formulation formulation, + bool disable_forensic_marking_picture, + optional disable_forensic_marking_audio ) const { - list kdms; - - for (list >::iterator i = screens.begin(); i != screens.end(); ++i) { - if ((*i)->certificate) { - kdms.push_back (make_kdm ((*i)->certificate.get(), dcp, from, until, formulation)); + list kdms; + + BOOST_FOREACH (shared_ptr i, screens) { + if (i->recipient) { + dcp::EncryptedKDM const kdm = make_kdm ( + i->recipient.get(), + i->trusted_devices, + 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, + disable_forensic_marking_picture, + disable_forensic_marking_audio + ); + + kdms.push_back (ScreenKDM (i, kdm)); } } @@ -1126,21 +1326,40 @@ 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. */ bool -Film::should_be_enough_disk_space (double& required, double& available) const -{ - boost::filesystem::space_info s = boost::filesystem::space (internal_video_mxf_dir ()); +Film::should_be_enough_disk_space (double& required, double& available, bool& can_hard_link) const +{ + /* Create a test file and see if we can hard-link it */ + boost::filesystem::path test = internal_video_asset_dir() / "test"; + boost::filesystem::path test2 = internal_video_asset_dir() / "test2"; + can_hard_link = true; + FILE* f = fopen_boost (test, "w"); + if (f) { + fclose (f); + boost::system::error_code ec; + boost::filesystem::create_hard_link (test, test2, ec); + if (ec) { + can_hard_link = false; + } + boost::filesystem::remove (test); + boost::filesystem::remove (test2); + } + + boost::filesystem::space_info s = boost::filesystem::space (internal_video_asset_dir ()); required = double (required_disk_space ()) / 1073741824.0f; + if (!can_hard_link) { + required *= 2; + } available = double (s.available) / 1073741824.0f; return (available - required) > 1; } @@ -1149,12 +1368,11 @@ string Film::subtitle_language () const { set languages; - + ContentList cl = content (); BOOST_FOREACH (shared_ptr& c, cl) { - shared_ptr sc = dynamic_pointer_cast (c); - if (sc) { - languages.insert (sc->subtitle_language ()); + if (c->subtitle) { + languages.insert (c->subtitle->language ()); } } @@ -1169,3 +1387,217 @@ Film::subtitle_language () const return all; } + +/** Change the gains of the supplied AudioMapping to make it a default + * for this film. The defaults are guessed based on what processor (if any) + * is in use, the number of input channels and any filename supplied. + */ +void +Film::make_audio_mapping_default (AudioMapping& mapping, optional filename) const +{ + static string const regex[] = { + ".*[\\._-]L[\\._-].*", + ".*[\\._-]R[\\._-].*", + ".*[\\._-]C[\\._-].*", + ".*[\\._-]Lfe[\\._-].*", + ".*[\\._-]Ls[\\._-].*", + ".*[\\._-]Rs[\\._-].*" + }; + + static int const regexes = sizeof(regex) / sizeof(*regex); + + if (audio_processor ()) { + audio_processor()->make_audio_mapping_default (mapping); + } else { + mapping.make_zero (); + if (mapping.input_channels() == 1) { + bool guessed = false; + + /* See if we can guess where this stream should go */ + if (filename) { + for (int i = 0; i < regexes; ++i) { + boost::regex e (regex[i], boost::regex::icase); + if (boost::regex_match (filename->string(), e) && i < mapping.output_channels()) { + mapping.set (0, i, 1); + guessed = true; + } + } + } + + if (!guessed) { + /* If we have no idea, just put it on centre */ + mapping.set (0, static_cast (dcp::CENTRE), 1); + } + } else { + /* 1:1 mapping */ + for (int i = 0; i < min (mapping.input_channels(), mapping.output_channels()); ++i) { + mapping.set (i, i, 1); + } + } + } +} + +/** @return The names of the channels that audio contents' outputs are passed into; + * this is either the DCP or a AudioProcessor. + */ +vector +Film::audio_output_names () const +{ + if (audio_processor ()) { + return audio_processor()->input_names (); + } + + DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16); + + vector n; + + for (int i = 0; i < audio_channels(); ++i) { + n.push_back (short_audio_channel_name (i)); + } + + return n; +} + +void +Film::repeat_content (ContentList c, int n) +{ + _playlist->repeat (c, n); +} + +void +Film::remove_content (ContentList c) +{ + _playlist->remove (c); +} + +void +Film::audio_analysis_finished () +{ + /* XXX */ +} + +list +Film::reels () const +{ + list p; + DCPTime const len = length(); + + switch (reel_type ()) { + case REELTYPE_SINGLE: + p.push_back (DCPTimePeriod (DCPTime (), len)); + break; + case REELTYPE_BY_VIDEO_CONTENT: + { + optional last_split; + shared_ptr last_video; + BOOST_FOREACH (shared_ptr c, content ()) { + 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 = c; + } + } + + DCPTime video_end = last_video ? last_video->end() : 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)); + } + + if (video_end < len) { + /* And maybe go after that as well if there is any non-video hanging over the end */ + p.push_back (DCPTimePeriod (video_end, len)); + } + break; + } + case REELTYPE_BY_LENGTH: + { + DCPTime current; + /* Integer-divide reel length by the size of one frame to give the number of frames per reel */ + Frame const reel_in_frames = _reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8); + while (current < len) { + DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ())); + p.push_back (DCPTimePeriod (current, end)); + current = end; + } + break; + } + } + + 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; +}