2 Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
22 * @brief A representation of some audio and video content, and details of
23 * how they should be presented in a DCP.
29 #include "job_manager.h"
30 #include "transcode_job.h"
31 #include "upload_job.h"
34 #include "exceptions.h"
35 #include "examine_content_job.h"
38 #include "dcp_content_type.h"
41 #include "environment_info.h"
42 #include "audio_processor.h"
44 #include "compose.hpp"
46 #include "audio_content.h"
47 #include "video_content.h"
48 #include "subtitle_content.h"
49 #include "ffmpeg_content.h"
50 #include "dcp_content.h"
51 #include "screen_kdm.h"
53 #include <libcxml/cxml.h>
55 #include <dcp/certificate_chain.h>
57 #include <dcp/local_time.h>
58 #include <dcp/decrypted_kdm.h>
59 #include <dcp/raw_convert.h>
60 #include <dcp/reel_mxf.h>
61 #include <dcp/reel_asset.h>
62 #include <libxml++/libxml++.h>
63 #include <boost/filesystem.hpp>
64 #include <boost/algorithm/string.hpp>
65 #include <boost/foreach.hpp>
66 #include <boost/regex.hpp>
87 using std::runtime_error;
89 using std::back_inserter;
91 using boost::shared_ptr;
92 using boost::weak_ptr;
93 using boost::dynamic_pointer_cast;
94 using boost::optional;
95 using boost::is_any_of;
96 using dcp::raw_convert;
98 #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
99 #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
101 string const Film::metadata_file = "metadata.xml";
104 * AudioMapping XML changed.
106 * Subtitle offset changed to subtitle y offset, and subtitle x offset added.
108 * Use <Scale> tag in <VideoContent> rather than <Ratio>.
112 * Subtitle X and Y scale.
114 * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
118 * Changed <Period> to <Subtitle> in FFmpegSubtitleStream
120 * Content only contains audio/subtitle-related tags if those things
123 * VideoFrameType in VideoContent is a string rather than an integer.
125 * EffectColour rather than OutlineColour in Subtitle.
127 int const Film::current_state_version = 36;
129 /** Construct a Film object in a given directory.
131 * @param dir Film directory.
134 Film::Film (optional<boost::filesystem::path> dir)
135 : _playlist (new Playlist)
136 , _use_isdcf_name (true)
137 , _dcp_content_type (Config::instance()->default_dcp_content_type ())
138 , _container (Config::instance()->default_container ())
139 , _resolution (RESOLUTION_2K)
142 , _context_id (dcp::make_uuid ())
143 , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
144 , _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
145 , _video_frame_rate (24)
146 , _audio_channels (Config::instance()->default_dcp_audio_channels ())
149 , _interop (Config::instance()->default_interop ())
150 , _audio_processor (0)
151 , _reel_type (REELTYPE_SINGLE)
152 , _reel_length (2000000000)
153 , _upload_after_make_dcp (false)
154 , _state_version (current_state_version)
157 set_isdcf_date_today ();
159 _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this));
160 _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
161 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3));
164 /* Make state.directory a complete path without ..s (where possible)
165 (Code swiped from Adam Bowen on stackoverflow)
166 XXX: couldn't/shouldn't this just be boost::filesystem::canonical?
169 boost::filesystem::path p (boost::filesystem::system_complete (dir.get()));
170 boost::filesystem::path result;
171 for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
173 if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
176 result = result.parent_path ();
178 } else if (*i != ".") {
183 set_directory (result.make_preferred ());
187 _log.reset (new FileLog (file ("log")));
189 _log.reset (new NullLog);
192 _playlist->set_sequence (_sequence);
197 BOOST_FOREACH (boost::signals2::connection& i, _job_connections) {
201 BOOST_FOREACH (boost::signals2::connection& i, _audio_analysis_connections) {
207 Film::video_identifier () const
209 DCPOMATIC_ASSERT (container ());
211 string s = container()->id()
212 + "_" + resolution_to_string (_resolution)
213 + "_" + _playlist->video_identifier()
214 + "_" + raw_convert<string>(_video_frame_rate)
215 + "_" + raw_convert<string>(j2k_bandwidth());
236 /** @return The file to write video frame info to */
237 boost::filesystem::path
238 Film::info_file (DCPTimePeriod period) const
240 boost::filesystem::path p;
242 p /= video_identifier () + "_" + raw_convert<string> (period.from.get()) + "_" + raw_convert<string> (period.to.get());
246 boost::filesystem::path
247 Film::internal_video_asset_dir () const
249 return dir ("video");
252 boost::filesystem::path
253 Film::internal_video_asset_filename (DCPTimePeriod p) const
255 return video_identifier() + "_" + raw_convert<string> (p.from.get()) + "_" + raw_convert<string> (p.to.get()) + ".mxf";
258 boost::filesystem::path
259 Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
261 boost::filesystem::path p = dir ("analysis");
264 BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
269 digester.add (i->digest ());
270 digester.add (i->audio->mapping().digest ());
271 if (playlist->content().size() != 1) {
272 /* Analyses should be considered equal regardless of gain
273 if they were made from just one piece of content. This
274 is because we can fake any gain change in a single-content
275 analysis at the plotting stage rather than having to
278 digester.add (i->audio->gain ());
282 if (audio_processor ()) {
283 digester.add (audio_processor()->id ());
286 p /= digester.get ();
290 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
294 if (dcp_name().find ("/") != string::npos) {
295 throw BadSettingError (_("name"), _("cannot contain slashes"));
298 if (container() == 0) {
299 throw MissingSettingError (_("container"));
302 if (content().empty()) {
303 throw runtime_error (_("you must add some content to the DCP before creating it"));
306 if (dcp_content_type() == 0) {
307 throw MissingSettingError (_("content type"));
310 if (name().empty()) {
311 throw MissingSettingError (_("name"));
314 BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
315 if (!i->paths_valid()) {
316 throw runtime_error (_("some of your content is missing"));
318 shared_ptr<const DCPContent> dcp = dynamic_pointer_cast<const DCPContent> (i);
319 if (dcp && dcp->needs_kdm()) {
320 throw runtime_error (_("some of your content needs a KDM"));
322 if (dcp && dcp->needs_assets()) {
323 throw runtime_error (_("some of your content needs an OV"));
327 set_isdcf_date_today ();
329 BOOST_FOREACH (string i, environment_info ()) {
333 BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
334 LOG_GENERAL ("Content: %1", i->technical_summary());
336 LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
337 if (Config::instance()->only_servers_encode ()) {
338 LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
340 LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads());
342 LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
344 JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
347 /** Start a job to send our DCP to the configured TMS */
349 Film::send_dcp_to_tms ()
351 shared_ptr<Job> j (new UploadJob (shared_from_this()));
352 JobManager::instance()->add (j);
355 shared_ptr<xmlpp::Document>
356 Film::metadata (bool with_content_paths) const
358 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
359 xmlpp::Element* root = doc->create_root_node ("Metadata");
361 root->add_child("Version")->add_child_text (raw_convert<string> (current_state_version));
362 root->add_child("Name")->add_child_text (_name);
363 root->add_child("UseISDCFName")->add_child_text (_use_isdcf_name ? "1" : "0");
365 if (_dcp_content_type) {
366 root->add_child("DCPContentType")->add_child_text (_dcp_content_type->isdcf_name ());
370 root->add_child("Container")->add_child_text (_container->id ());
373 root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
374 root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
375 _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata"));
376 root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
377 root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
378 root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
379 root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
380 root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0");
381 root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
382 root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
383 root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
384 root->add_child("Key")->add_child_text (_key.hex ());
385 root->add_child("ContextID")->add_child_text (_context_id);
386 if (_audio_processor) {
387 root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
389 root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
390 root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
391 root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
392 _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
397 /** Write state to our `metadata' file */
399 Film::write_metadata () const
401 DCPOMATIC_ASSERT (directory());
402 boost::filesystem::create_directories (directory().get());
403 shared_ptr<xmlpp::Document> doc = metadata ();
404 doc->write_to_file_formatted (file(metadata_file).string ());
408 /** Write a template from this film */
410 Film::write_template (boost::filesystem::path path) const
412 boost::filesystem::create_directories (path.parent_path());
413 shared_ptr<xmlpp::Document> doc = metadata (false);
414 doc->write_to_file_formatted (path.string ());
417 /** Read state from our metadata file.
418 * @return Notes about things that the user should know about, or empty.
421 Film::read_metadata (optional<boost::filesystem::path> path)
424 if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file (metadata_file))) {
425 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!"));
428 path = file (metadata_file);
431 cxml::Document f ("Metadata");
432 f.read_file (path.get ());
434 _state_version = f.number_child<int> ("Version");
435 if (_state_version > current_state_version) {
436 throw runtime_error (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!"));
439 _name = f.string_child ("Name");
440 if (_state_version >= 9) {
441 _use_isdcf_name = f.bool_child ("UseISDCFName");
442 _isdcf_metadata = ISDCFMetadata (f.node_child ("ISDCFMetadata"));
443 _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("ISDCFDate"));
445 _use_isdcf_name = f.bool_child ("UseDCIName");
446 _isdcf_metadata = ISDCFMetadata (f.node_child ("DCIMetadata"));
447 _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
451 optional<string> c = f.optional_string_child ("DCPContentType");
453 _dcp_content_type = DCPContentType::from_isdcf_name (c.get ());
458 optional<string> c = f.optional_string_child ("Container");
460 _container = Ratio::from_id (c.get ());
464 _resolution = string_to_resolution (f.string_child ("Resolution"));
465 _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
466 _video_frame_rate = f.number_child<int> ("VideoFrameRate");
467 _signed = f.optional_bool_child("Signed").get_value_or (true);
468 _encrypted = f.bool_child ("Encrypted");
469 _audio_channels = f.number_child<int> ("AudioChannels");
470 /* We used to allow odd numbers (and zero) channels, but it's just not worth
473 if (_audio_channels == 0) {
475 } else if ((_audio_channels % 2) == 1) {
479 if (f.optional_bool_child("SequenceVideo")) {
480 _sequence = f.bool_child("SequenceVideo");
482 _sequence = f.bool_child("Sequence");
485 _three_d = f.bool_child ("ThreeD");
486 _interop = f.bool_child ("Interop");
487 _key = dcp::Key (f.string_child ("Key"));
488 _context_id = f.optional_string_child("ContextID").get_value_or (dcp::make_uuid ());
490 if (f.optional_string_child ("AudioProcessor")) {
491 _audio_processor = AudioProcessor::from_id (f.string_child ("AudioProcessor"));
493 _audio_processor = 0;
496 _reel_type = static_cast<ReelType> (f.optional_number_child<int>("ReelType").get_value_or (static_cast<int>(REELTYPE_SINGLE)));
497 _reel_length = f.optional_number_child<int64_t>("ReelLength").get_value_or (2000000000);
498 _upload_after_make_dcp = f.optional_bool_child("UploadAfterMakeDCP").get_value_or (false);
501 /* This method is the only one that can return notes (so far) */
502 _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
504 /* Write backtraces to this film's directory, until another film is loaded */
506 set_backtrace_file (file ("backtrace.txt"));
513 /** Given a directory name, return its full path within the Film's directory.
514 * @param d directory name within the Filn's directory.
515 * @param create true to create the directory (and its parents) if they do not exist.
517 boost::filesystem::path
518 Film::dir (boost::filesystem::path d, bool create) const
520 DCPOMATIC_ASSERT (_directory);
522 boost::filesystem::path p;
523 p /= _directory.get();
527 boost::filesystem::create_directories (p);
533 /** Given a file or directory name, return its full path within the Film's directory.
534 * Any required parent directories will be created.
536 boost::filesystem::path
537 Film::file (boost::filesystem::path f) const
539 DCPOMATIC_ASSERT (_directory);
541 boost::filesystem::path p;
542 p /= _directory.get();
545 boost::filesystem::create_directories (p.parent_path ());
551 Film::mapped_audio_channels () const
555 if (audio_processor ()) {
556 /* Processors are mapped 1:1 to DCP outputs so we can work out mappings from there */
557 for (int i = 0; i < audio_processor()->out_channels(); ++i) {
558 mapped.push_back (i);
561 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
563 list<int> c = i->audio->mapping().mapped_output_channels ();
564 copy (c.begin(), c.end(), back_inserter (mapped));
575 /** @return a ISDCF-compliant name for a DCP of this film */
577 Film::isdcf_name (bool if_created_now) const
581 string raw_name = name ();
583 /* Split the raw name up into words */
584 vector<string> words;
585 split (words, raw_name, is_any_of (" _-"));
589 /* Add each word to fixed_name */
590 for (vector<string>::const_iterator i = words.begin(); i != words.end(); ++i) {
593 /* First letter is always capitalised */
594 w[0] = toupper (w[0]);
596 /* Count caps in w */
598 for (size_t i = 0; i < w.size(); ++i) {
599 if (isupper (w[i])) {
604 /* If w is all caps make the rest of it lower case, otherwise
607 if (caps == w.size ()) {
608 for (size_t i = 1; i < w.size(); ++i) {
609 w[i] = tolower (w[i]);
613 for (size_t i = 0; i < w.size(); ++i) {
618 if (fixed_name.length() > 14) {
619 fixed_name = fixed_name.substr (0, 14);
624 if (dcp_content_type()) {
625 d += "_" + dcp_content_type()->isdcf_name();
626 d += "-" + raw_convert<string>(isdcf_metadata().content_version);
629 ISDCFMetadata const dm = isdcf_metadata ();
631 if (dm.temp_version) {
635 if (dm.pre_release) {
643 if (!dm.chain.empty ()) {
651 if (dm.two_d_version_of_three_d) {
655 if (!dm.mastered_luminance.empty ()) {
656 d += "-" + dm.mastered_luminance;
659 if (video_frame_rate() != 24) {
660 d += "-" + raw_convert<string>(video_frame_rate());
664 d += "_" + container()->isdcf_name();
667 /* XXX: this uses the first bit of content only */
669 /* The standard says we don't do this for trailers, for some strange reason */
670 if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
671 Ratio const * content_ratio = 0;
672 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
674 /* Here's the first piece of video content */
675 if (i->video->scale().ratio ()) {
676 content_ratio = i->video->scale().ratio ();
678 content_ratio = Ratio::from_ratio (i->video->size().ratio ());
684 if (content_ratio && content_ratio != container()) {
685 d += "-" + content_ratio->isdcf_name();
689 if (!dm.audio_language.empty ()) {
690 d += "_" + dm.audio_language;
691 if (!dm.subtitle_language.empty()) {
693 bool burnt_in = true;
694 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
699 if (i->subtitle->use() && !i->subtitle->burn()) {
704 string language = dm.subtitle_language;
705 if (burnt_in && language != "XX") {
706 transform (language.begin(), language.end(), language.begin(), ::tolower);
708 transform (language.begin(), language.end(), language.begin(), ::toupper);
717 if (!dm.territory.empty ()) {
718 d += "_" + dm.territory;
719 if (dm.rating.empty ()) {
722 d += "-" + dm.rating;
726 /* Count mapped audio channels */
728 pair<int, int> ch = audio_channel_types (mapped_audio_channels(), audio_channels());
730 d += String::compose("_%1%2", ch.first, ch.second);
735 d += "_" + resolution_to_string (_resolution);
737 if (!dm.studio.empty ()) {
738 d += "_" + dm.studio;
741 if (if_created_now) {
742 d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
744 d += "_" + boost::gregorian::to_iso_string (_isdcf_date);
747 if (!dm.facility.empty ()) {
748 d += "_" + dm.facility;
762 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
763 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
764 if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) {
778 /** @return name to give the DCP */
780 Film::dcp_name (bool if_created_now) const
783 if (use_isdcf_name()) {
784 return careful_string_filter (isdcf_name (if_created_now));
787 return careful_string_filter (name ());
791 Film::set_directory (boost::filesystem::path d)
798 Film::set_name (string n)
801 signal_changed (NAME);
805 Film::set_use_isdcf_name (bool u)
808 signal_changed (USE_ISDCF_NAME);
812 Film::set_dcp_content_type (DCPContentType const * t)
814 _dcp_content_type = t;
815 signal_changed (DCP_CONTENT_TYPE);
819 Film::set_container (Ratio const * c)
822 signal_changed (CONTAINER);
826 Film::set_resolution (Resolution r)
829 signal_changed (RESOLUTION);
833 Film::set_j2k_bandwidth (int b)
836 signal_changed (J2K_BANDWIDTH);
840 Film::set_isdcf_metadata (ISDCFMetadata m)
843 signal_changed (ISDCF_METADATA);
847 Film::set_video_frame_rate (int f)
849 _video_frame_rate = f;
850 signal_changed (VIDEO_FRAME_RATE);
854 Film::set_audio_channels (int c)
857 signal_changed (AUDIO_CHANNELS);
861 Film::set_three_d (bool t)
864 signal_changed (THREE_D);
866 if (_three_d && _isdcf_metadata.two_d_version_of_three_d) {
867 _isdcf_metadata.two_d_version_of_three_d = false;
868 signal_changed (ISDCF_METADATA);
873 Film::set_interop (bool i)
876 signal_changed (INTEROP);
880 Film::set_audio_processor (AudioProcessor const * processor)
882 _audio_processor = processor;
883 signal_changed (AUDIO_PROCESSOR);
884 signal_changed (AUDIO_CHANNELS);
888 Film::set_reel_type (ReelType t)
891 signal_changed (REEL_TYPE);
894 /** @param r Desired reel length in bytes */
896 Film::set_reel_length (int64_t r)
899 signal_changed (REEL_LENGTH);
903 Film::set_upload_after_make_dcp (bool u)
905 _upload_after_make_dcp = u;
906 signal_changed (UPLOAD_AFTER_MAKE_DCP);
910 Film::signal_changed (Property p)
916 set_video_frame_rate (_playlist->best_video_frame_rate ());
918 case Film::VIDEO_FRAME_RATE:
920 _playlist->maybe_sequence ();
926 emit (boost::bind (boost::ref (Changed), p));
930 Film::set_isdcf_date_today ()
932 _isdcf_date = boost::gregorian::day_clock::local_day ();
935 boost::filesystem::path
936 Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const
938 boost::filesystem::path p;
940 p /= video_identifier ();
943 snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame);
946 if (eyes == EYES_LEFT) {
948 } else if (eyes == EYES_RIGHT) {
962 /** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs */
967 return vector<CPLSummary> ();
970 vector<CPLSummary> out;
972 boost::filesystem::path const dir = directory().get();
973 for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
975 boost::filesystem::is_directory (*i) &&
976 i->path().leaf() != "j2c" && i->path().leaf() != "video" && i->path().leaf() != "info" && i->path().leaf() != "analysis"
982 DCPOMATIC_ASSERT (dcp.cpls().front()->file());
985 i->path().leaf().string(),
986 dcp.cpls().front()->id(),
987 dcp.cpls().front()->annotation_text(),
988 dcp.cpls().front()->file().get()
1001 Film::set_signed (bool s)
1004 signal_changed (SIGNED);
1008 Film::set_encrypted (bool e)
1011 signal_changed (ENCRYPTED);
1015 Film::set_key (dcp::Key key)
1018 signal_changed (KEY);
1022 Film::content () const
1024 return _playlist->content ();
1028 Film::examine_and_add_content (shared_ptr<Content> c)
1030 if (dynamic_pointer_cast<FFmpegContent> (c) && _directory) {
1031 run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
1034 shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
1036 _job_connections.push_back (
1037 j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr<Job> (j), weak_ptr<Content> (c)))
1040 JobManager::instance()->add (j);
1044 Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
1046 shared_ptr<Job> job = j.lock ();
1047 if (!job || !job->finished_ok ()) {
1051 shared_ptr<Content> content = c.lock ();
1056 add_content (content);
1058 if (Config::instance()->automatic_audio_analysis() && content->audio) {
1059 shared_ptr<Playlist> playlist (new Playlist);
1060 playlist->add (content);
1061 boost::signals2::connection c;
1062 JobManager::instance()->analyse_audio (
1063 shared_from_this (), playlist, c, bind (&Film::audio_analysis_finished, this)
1065 _audio_analysis_connections.push_back (c);
1070 Film::add_content (shared_ptr<Content> c)
1072 /* Add {video,subtitle} content after any existing {video,subtitle} content */
1074 c->set_position (_playlist->video_end ());
1075 } else if (c->subtitle) {
1076 c->set_position (_playlist->subtitle_end ());
1079 if (_template_film) {
1080 /* Take settings from the first piece of content of c's type in _template */
1081 BOOST_FOREACH (shared_ptr<Content> i, _template_film->content()) {
1082 if (typeid(i.get()) == typeid(c.get())) {
1083 c->use_template (i);
1092 Film::remove_content (shared_ptr<Content> c)
1094 _playlist->remove (c);
1098 Film::move_content_earlier (shared_ptr<Content> c)
1100 _playlist->move_earlier (c);
1104 Film::move_content_later (shared_ptr<Content> c)
1106 _playlist->move_later (c);
1109 /** @return length of the film from time 0 to the last thing on the playlist */
1111 Film::length () const
1113 return _playlist->length ();
1117 Film::best_video_frame_rate () const
1119 return _playlist->best_video_frame_rate ();
1123 Film::active_frame_rate_change (DCPTime t) const
1125 return _playlist->active_frame_rate_change (t, video_frame_rate ());
1129 Film::playlist_content_changed (weak_ptr<Content> c, int p, bool frequent)
1133 if (p == ContentProperty::VIDEO_FRAME_RATE) {
1134 set_video_frame_rate (_playlist->best_video_frame_rate ());
1135 } else if (p == AudioContentProperty::STREAMS) {
1136 signal_changed (NAME);
1139 emit (boost::bind (boost::ref (ContentChanged), c, p, frequent));
1143 Film::playlist_changed ()
1145 signal_changed (CONTENT);
1146 signal_changed (NAME);
1150 Film::playlist_order_changed ()
1152 signal_changed (CONTENT_ORDER);
1156 Film::audio_frame_rate () const
1158 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
1159 if (i->audio && i->audio->has_rate_above_48k ()) {
1168 Film::set_sequence (bool s)
1171 _playlist->set_sequence (s);
1172 signal_changed (SEQUENCE);
1175 /** @return Size of the largest possible image in whatever resolution we are using */
1177 Film::full_frame () const
1179 switch (_resolution) {
1181 return dcp::Size (2048, 1080);
1183 return dcp::Size (4096, 2160);
1186 DCPOMATIC_ASSERT (false);
1187 return dcp::Size ();
1190 /** @return Size of the frame */
1192 Film::frame_size () const
1194 return fit_ratio_within (container()->ratio(), full_frame ());
1197 /** @param recipient KDM recipient certificate.
1198 * @param trusted_devices Certificates of other trusted devices (can be empty).
1199 * @param cpl_file CPL filename.
1200 * @param from KDM from time expressed as a local time with an offset from UTC.
1201 * @param until KDM to time expressed as a local time with an offset from UTC.
1202 * @param formulation KDM formulation to use.
1206 dcp::Certificate recipient,
1207 vector<dcp::Certificate> trusted_devices,
1208 boost::filesystem::path cpl_file,
1209 dcp::LocalTime from,
1210 dcp::LocalTime until,
1211 dcp::Formulation formulation
1214 shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
1215 shared_ptr<const dcp::CertificateChain> signer = Config::instance()->signer_chain ();
1216 if (!signer->valid ()) {
1217 throw InvalidSignerError ();
1220 /* Find keys that have been added to imported, encrypted DCP content */
1221 list<dcp::DecryptedKDMKey> imported_keys;
1222 BOOST_FOREACH (shared_ptr<Content> i, content()) {
1223 shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
1224 if (d && d->kdm()) {
1225 dcp::DecryptedKDM kdm (d->kdm().get(), Config::instance()->decryption_chain()->key().get());
1226 list<dcp::DecryptedKDMKey> keys = kdm.keys ();
1227 copy (keys.begin(), keys.end(), back_inserter (imported_keys));
1231 map<shared_ptr<const dcp::ReelMXF>, dcp::Key> keys;
1233 BOOST_FOREACH(shared_ptr<const dcp::ReelAsset> i, cpl->reel_assets ()) {
1234 shared_ptr<const dcp::ReelMXF> mxf = boost::dynamic_pointer_cast<const dcp::ReelMXF> (i);
1235 if (!mxf || !mxf->key_id()) {
1239 /* Get any imported key for this ID */
1241 BOOST_FOREACH (dcp::DecryptedKDMKey j, imported_keys) {
1242 if (j.id() == mxf->key_id().get()) {
1243 LOG_GENERAL ("Using imported key for %1", mxf->key_id().get());
1244 keys[mxf] = j.key();
1250 /* No imported key; it must be an asset that we encrypted */
1251 LOG_GENERAL ("Using our own key for %1", mxf->key_id().get());
1256 return dcp::DecryptedKDM (
1257 cpl->id(), keys, from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
1258 ).encrypt (signer, recipient, trusted_devices, formulation);
1261 /** @param screens Screens to make KDMs for.
1262 * @param cpl_file Path to CPL to make KDMs for.
1263 * @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema.
1264 * @param until KDM to time expressed as a local time in the time zone of the Screen's Cinema.
1265 * @param formulation KDM formulation to use.
1269 list<shared_ptr<Screen> > screens,
1270 boost::filesystem::path cpl_file,
1271 boost::posix_time::ptime from,
1272 boost::posix_time::ptime until,
1273 dcp::Formulation formulation
1276 list<ScreenKDM> kdms;
1278 BOOST_FOREACH (shared_ptr<Screen> i, screens) {
1280 dcp::EncryptedKDM const kdm = make_kdm (
1284 dcp::LocalTime (from, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
1285 dcp::LocalTime (until, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
1289 kdms.push_back (ScreenKDM (i, kdm));
1296 /** @return The approximate disk space required to encode a DCP of this film with the
1297 * current settings, in bytes.
1300 Film::required_disk_space () const
1302 return _playlist->required_disk_space (j2k_bandwidth(), audio_channels(), audio_frame_rate());
1305 /** This method checks the disk that the Film is on and tries to decide whether or not
1306 * there will be enough space to make a DCP for it. If so, true is returned; if not,
1307 * false is returned and required and available are filled in with the amount of disk space
1308 * required and available respectively (in Gb).
1310 * Note: the decision made by this method isn't, of course, 100% reliable.
1313 Film::should_be_enough_disk_space (double& required, double& available, bool& can_hard_link) const
1315 /* Create a test file and see if we can hard-link it */
1316 boost::filesystem::path test = internal_video_asset_dir() / "test";
1317 boost::filesystem::path test2 = internal_video_asset_dir() / "test2";
1318 can_hard_link = true;
1319 FILE* f = fopen_boost (test, "w");
1322 boost::system::error_code ec;
1323 boost::filesystem::create_hard_link (test, test2, ec);
1325 can_hard_link = false;
1327 boost::filesystem::remove (test);
1328 boost::filesystem::remove (test2);
1331 boost::filesystem::space_info s = boost::filesystem::space (internal_video_asset_dir ());
1332 required = double (required_disk_space ()) / 1073741824.0f;
1333 if (!can_hard_link) {
1336 available = double (s.available) / 1073741824.0f;
1337 return (available - required) > 1;
1341 Film::subtitle_language () const
1343 set<string> languages;
1345 ContentList cl = content ();
1346 BOOST_FOREACH (shared_ptr<Content>& c, cl) {
1348 languages.insert (c->subtitle->language ());
1353 BOOST_FOREACH (string s, languages) {
1354 if (!all.empty ()) {
1364 /** Change the gains of the supplied AudioMapping to make it a default
1365 * for this film. The defaults are guessed based on what processor (if any)
1366 * is in use, the number of input channels and any filename supplied.
1369 Film::make_audio_mapping_default (AudioMapping& mapping, optional<boost::filesystem::path> filename) const
1371 static string const regex[] = {
1372 ".*[\\._-]L[\\._-].*",
1373 ".*[\\._-]R[\\._-].*",
1374 ".*[\\._-]C[\\._-].*",
1375 ".*[\\._-]Lfe[\\._-].*",
1376 ".*[\\._-]Ls[\\._-].*",
1377 ".*[\\._-]Rs[\\._-].*"
1380 static int const regexes = sizeof(regex) / sizeof(*regex);
1382 if (audio_processor ()) {
1383 audio_processor()->make_audio_mapping_default (mapping);
1385 mapping.make_zero ();
1386 if (mapping.input_channels() == 1) {
1387 bool guessed = false;
1389 /* See if we can guess where this stream should go */
1391 for (int i = 0; i < regexes; ++i) {
1392 boost::regex e (regex[i], boost::regex::icase);
1393 if (boost::regex_match (filename->string(), e) && i < mapping.output_channels()) {
1394 mapping.set (0, i, 1);
1401 /* If we have no idea, just put it on centre */
1402 mapping.set (0, static_cast<int> (dcp::CENTRE), 1);
1406 for (int i = 0; i < min (mapping.input_channels(), mapping.output_channels()); ++i) {
1407 mapping.set (i, i, 1);
1413 /** @return The names of the channels that audio contents' outputs are passed into;
1414 * this is either the DCP or a AudioProcessor.
1417 Film::audio_output_names () const
1419 if (audio_processor ()) {
1420 return audio_processor()->input_names ();
1423 DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
1427 for (int i = 0; i < audio_channels(); ++i) {
1428 n.push_back (short_audio_channel_name (i));
1435 Film::repeat_content (ContentList c, int n)
1437 _playlist->repeat (c, n);
1441 Film::remove_content (ContentList c)
1443 _playlist->remove (c);
1447 Film::audio_analysis_finished ()
1453 Film::reels () const
1455 list<DCPTimePeriod> p;
1456 DCPTime const len = length().ceil (video_frame_rate ());
1458 switch (reel_type ()) {
1459 case REELTYPE_SINGLE:
1460 p.push_back (DCPTimePeriod (DCPTime (), len));
1462 case REELTYPE_BY_VIDEO_CONTENT:
1464 optional<DCPTime> last_split;
1465 shared_ptr<Content> last_video;
1466 BOOST_FOREACH (shared_ptr<Content> c, content ()) {
1468 BOOST_FOREACH (DCPTime t, c->reel_split_points()) {
1470 p.push_back (DCPTimePeriod (last_split.get(), t));
1478 DCPTime video_end = last_video ? last_video->end() : DCPTime(0);
1480 /* Definitely go from the last split to the end of the video content */
1481 p.push_back (DCPTimePeriod (last_split.get(), video_end));
1484 if (video_end < len) {
1485 /* And maybe go after that as well if there is any non-video hanging over the end */
1486 p.push_back (DCPTimePeriod (video_end, len));
1490 case REELTYPE_BY_LENGTH:
1493 /* Integer-divide reel length by the size of one frame to give the number of frames per reel */
1494 Frame const reel_in_frames = _reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8);
1495 while (current < len) {
1496 DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ()));
1497 p.push_back (DCPTimePeriod (current, end));
1507 /** @param period A period within the DCP
1508 * @return Name of the content which most contributes to the given period.
1511 Film::content_summary (DCPTimePeriod period) const
1513 return _playlist->content_summary (period);
1517 Film::fix_conflicting_settings ()
1521 list<boost::filesystem::path> was_referencing;
1522 BOOST_FOREACH (shared_ptr<Content> i, content()) {
1523 shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
1525 list<string> reasons;
1527 if (!d->can_reference_video(reasons) && d->reference_video()) {
1528 d->set_reference_video (false);
1531 if (!d->can_reference_audio(reasons) && d->reference_audio()) {
1532 d->set_reference_audio (false);
1535 if (!d->can_reference_subtitle(reasons) && d->reference_subtitle()) {
1536 d->set_reference_subtitle (false);
1540 was_referencing.push_back (d->path(0).parent_path().filename());
1545 BOOST_FOREACH (boost::filesystem::path d, was_referencing) {
1546 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()));
1553 Film::use_template (string name)
1555 _template_film.reset (new Film (optional<boost::filesystem::path>()));
1556 _template_film->read_metadata (Config::instance()->template_path (name));
1557 _use_isdcf_name = _template_film->_use_isdcf_name;
1558 _dcp_content_type = _template_film->_dcp_content_type;
1559 _container = _template_film->_container;
1560 _resolution = _template_film->_resolution;
1561 _j2k_bandwidth = _template_film->_j2k_bandwidth;
1562 _video_frame_rate = _template_film->_video_frame_rate;
1563 _signed = _template_film->_signed;
1564 _encrypted = _template_film->_encrypted;
1565 _audio_channels = _template_film->_audio_channels;
1566 _sequence = _template_film->_sequence;
1567 _three_d = _template_film->_three_d;
1568 _interop = _template_film->_interop;
1569 _audio_processor = _template_film->_audio_processor;
1570 _reel_type = _template_film->_reel_type;
1571 _reel_length = _template_film->_reel_length;
1572 _upload_after_make_dcp = _template_film->_upload_after_make_dcp;
1575 pair<double, double>
1576 Film::speed_up_range (int dcp_frame_rate) const
1578 return _playlist->speed_up_range (dcp_frame_rate);
1582 Film::copy_from (shared_ptr<const Film> film)
1584 read_metadata (film->file (metadata_file));