/*
- Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
#include <dcp/local_time.h>
#include <dcp/decrypted_kdm.h>
#include <dcp/raw_convert.h>
+#include <dcp/reel_mxf.h>
+#include <dcp/reel_asset.h>
#include <libxml++/libxml++.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
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;
* @param dir Film directory.
*/
-Film::Film (boost::filesystem::path dir, bool log)
+Film::Film (optional<boost::filesystem::path> dir)
: _playlist (new Playlist)
, _use_isdcf_name (true)
, _dcp_content_type (Config::instance()->default_dcp_content_type ())
, _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)
_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);
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()) {
+ throw MissingSettingError (_("name"));
+ }
+
+ BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
+ if (!i->paths_valid()) {
+ throw runtime_error (_("some of your content is missing"));
+ }
+ 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"));
+ }
+ 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 ("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<Job> (new TranscodeJob (shared_from_this())));
}
}
shared_ptr<xmlpp::Document>
-Film::metadata () const
+Film::metadata (bool with_content_paths) const
{
shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
xmlpp::Element* root = doc->create_root_node ("Metadata");
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<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");
- _playlist->as_xml (root->add_child ("Playlist"));
+ _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
return doc;
}
void
Film::write_metadata () const
{
- boost::filesystem::create_directories (directory ());
+ DCPOMATIC_ASSERT (directory());
+ boost::filesystem::create_directories (directory().get());
shared_ptr<xmlpp::Document> doc = metadata ();
doc->write_to_file_formatted (file("metadata.xml").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<xmlpp::Document> 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<string>
-Film::read_metadata ()
+Film::read_metadata (optional<boost::filesystem::path> 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.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!"));
+ }
+
+ path = file ("metadata.xml");
}
cxml::Document f ("Metadata");
- f.read_file (file ("metadata.xml"));
+ f.read_file (path.get ());
_state_version = f.number_child<int> ("Version");
if (_state_version > current_state_version) {
_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"));
_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;
boost::filesystem::path
Film::dir (boost::filesystem::path d) const
{
+ DCPOMATIC_ASSERT (_directory);
+
boost::filesystem::path p;
- p /= _directory;
+ p /= _directory.get();
p /= d;
boost::filesystem::create_directories (p);
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 ());
{
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
vector<CPLSummary>
Film::cpls () const
{
+ if (!directory ()) {
+ return vector<CPLSummary> ();
+ }
+
vector<CPLSummary> 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) &&
return _playlist->content ();
}
-void
-Film::examine_content (shared_ptr<Content> c)
-{
- shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
- JobManager::instance()->add (j);
-}
-
void
Film::examine_and_add_content (shared_ptr<Content> c)
{
- if (dynamic_pointer_cast<FFmpegContent> (c) && !_directory.empty ()) {
+ if (dynamic_pointer_cast<FFmpegContent> (c) && _directory) {
run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
}
}
add_content (content);
+
if (Config::instance()->automatic_audio_analysis() && content->audio) {
shared_ptr<Playlist> playlist (new Playlist);
playlist->add (content);
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<Content> i, _template_film->content()) {
+ if (typeid(i.get()) == typeid(c.get())) {
+ c->use_template (i);
+ }
+ }
+ }
+
_playlist->add (c);
}
throw InvalidSignerError ();
}
+ /* Find keys that have been added to imported, encrypted DCP content */
+ list<dcp::DecryptedKDMKey> imported_keys;
+ BOOST_FOREACH (shared_ptr<Content> i, content()) {
+ shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
+ if (d && d->kdm()) {
+ dcp::DecryptedKDM kdm (d->kdm().get(), Config::instance()->decryption_chain()->key().get());
+ list<dcp::DecryptedKDMKey> keys = kdm.keys ();
+ copy (keys.begin(), keys.end(), back_inserter (imported_keys));
+ }
+ }
+
+ map<shared_ptr<const dcp::ReelMXF>, dcp::Key> keys;
+
+ BOOST_FOREACH(shared_ptr<const dcp::ReelAsset> i, cpl->reel_assets ()) {
+ shared_ptr<const dcp::ReelMXF> mxf = boost::dynamic_pointer_cast<const dcp::ReelMXF> (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, key(), from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
+ cpl->id(), keys, from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
).encrypt (signer, recipient, trusted_devices, formulation);
}
DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
vector<string> 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<string> (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
Film::reels () const
{
list<DCPTimePeriod> p;
- DCPTime const len = length().round_up (video_frame_rate ());
+ DCPTime const len = length().ceil (video_frame_rate ());
switch (reel_type ()) {
case REELTYPE_SINGLE:
{
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;
+}
+
+void
+Film::use_template (string name)
+{
+ _template_film.reset (new Film (optional<boost::filesystem::path>()));
+ _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;
+}