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, video and subtitle content, and details of
23 * how they should be presented in a DCP.
29 #include "job_manager.h"
30 #include "dcp_encoder.h"
31 #include "transcode_job.h"
32 #include "upload_job.h"
35 #include "exceptions.h"
36 #include "examine_content_job.h"
39 #include "dcp_content_type.h"
42 #include "environment_info.h"
43 #include "audio_processor.h"
45 #include "compose.hpp"
47 #include "audio_content.h"
48 #include "video_content.h"
49 #include "subtitle_content.h"
50 #include "ffmpeg_content.h"
51 #include "dcp_content.h"
52 #include "screen_kdm.h"
54 #include <libcxml/cxml.h>
56 #include <dcp/certificate_chain.h>
58 #include <dcp/local_time.h>
59 #include <dcp/decrypted_kdm.h>
60 #include <dcp/raw_convert.h>
61 #include <dcp/reel_mxf.h>
62 #include <dcp/reel_asset.h>
63 #include <libxml++/libxml++.h>
64 #include <boost/filesystem.hpp>
65 #include <boost/algorithm/string.hpp>
66 #include <boost/foreach.hpp>
67 #include <boost/regex.hpp>
88 using std::runtime_error;
90 using std::back_inserter;
92 using boost::shared_ptr;
93 using boost::weak_ptr;
94 using boost::dynamic_pointer_cast;
95 using boost::optional;
96 using boost::is_any_of;
97 using dcp::raw_convert;
99 #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
100 #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
102 string const Film::metadata_file = "metadata.xml";
105 * AudioMapping XML changed.
107 * Subtitle offset changed to subtitle y offset, and subtitle x offset added.
109 * Use <Scale> tag in <VideoContent> rather than <Ratio>.
113 * Subtitle X and Y scale.
115 * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
119 * Changed <Period> to <Subtitle> in FFmpegSubtitleStream
121 * Content only contains audio/subtitle-related tags if those things
124 * VideoFrameType in VideoContent is a string rather than an integer.
126 * EffectColour rather than OutlineColour in Subtitle.
128 int const Film::current_state_version = 36;
130 /** Construct a Film object in a given directory.
132 * @param dir Film directory.
135 Film::Film (optional<boost::filesystem::path> dir)
136 : _playlist (new Playlist)
137 , _use_isdcf_name (true)
138 , _dcp_content_type (Config::instance()->default_dcp_content_type ())
139 , _container (Config::instance()->default_container ())
140 , _resolution (RESOLUTION_2K)
143 , _context_id (dcp::make_uuid ())
144 , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
145 , _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
146 , _video_frame_rate (24)
147 , _audio_channels (Config::instance()->default_dcp_audio_channels ())
150 , _interop (Config::instance()->default_interop ())
151 , _audio_processor (0)
152 , _reel_type (REELTYPE_SINGLE)
153 , _reel_length (2000000000)
154 , _upload_after_make_dcp (false)
155 , _state_version (current_state_version)
158 set_isdcf_date_today ();
160 _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this));
161 _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
162 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3));
165 /* Make state.directory a complete path without ..s (where possible)
166 (Code swiped from Adam Bowen on stackoverflow)
167 XXX: couldn't/shouldn't this just be boost::filesystem::canonical?
170 boost::filesystem::path p (boost::filesystem::system_complete (dir.get()));
171 boost::filesystem::path result;
172 for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
174 if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
177 result = result.parent_path ();
179 } else if (*i != ".") {
184 set_directory (result.make_preferred ());
188 _log.reset (new FileLog (file ("log")));
190 _log.reset (new NullLog);
193 _playlist->set_sequence (_sequence);
198 BOOST_FOREACH (boost::signals2::connection& i, _job_connections) {
202 BOOST_FOREACH (boost::signals2::connection& i, _audio_analysis_connections) {
208 Film::video_identifier () const
210 DCPOMATIC_ASSERT (container ());
212 string s = container()->id()
213 + "_" + resolution_to_string (_resolution)
214 + "_" + _playlist->video_identifier()
215 + "_" + raw_convert<string>(_video_frame_rate)
216 + "_" + raw_convert<string>(j2k_bandwidth());
237 /** @return The file to write video frame info to */
238 boost::filesystem::path
239 Film::info_file (DCPTimePeriod period) const
241 boost::filesystem::path p;
243 p /= video_identifier () + "_" + raw_convert<string> (period.from.get()) + "_" + raw_convert<string> (period.to.get());
247 boost::filesystem::path
248 Film::internal_video_asset_dir () const
250 return dir ("video");
253 boost::filesystem::path
254 Film::internal_video_asset_filename (DCPTimePeriod p) const
256 return video_identifier() + "_" + raw_convert<string> (p.from.get()) + "_" + raw_convert<string> (p.to.get()) + ".mxf";
259 boost::filesystem::path
260 Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
262 boost::filesystem::path p = dir ("analysis");
265 BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
270 digester.add (i->digest ());
271 digester.add (i->audio->mapping().digest ());
272 if (playlist->content().size() != 1) {
273 /* Analyses should be considered equal regardless of gain
274 if they were made from just one piece of content. This
275 is because we can fake any gain change in a single-content
276 analysis at the plotting stage rather than having to
279 digester.add (i->audio->gain ());
283 if (audio_processor ()) {
284 digester.add (audio_processor()->id ());
287 p /= digester.get ();
291 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
295 if (dcp_name().find ("/") != string::npos) {
296 throw BadSettingError (_("name"), _("cannot contain slashes"));
299 if (container() == 0) {
300 throw MissingSettingError (_("container"));
303 if (content().empty()) {
304 throw runtime_error (_("you must add some content to the DCP before creating it"));
307 if (dcp_content_type() == 0) {
308 throw MissingSettingError (_("content type"));
311 if (name().empty()) {
315 BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
316 if (!i->paths_valid()) {
317 throw runtime_error (_("some of your content is missing"));
319 shared_ptr<const DCPContent> dcp = dynamic_pointer_cast<const DCPContent> (i);
320 if (dcp && dcp->needs_kdm()) {
321 throw runtime_error (_("some of your content needs a KDM"));
323 if (dcp && dcp->needs_assets()) {
324 throw runtime_error (_("some of your content needs an OV"));
328 set_isdcf_date_today ();
330 BOOST_FOREACH (string i, environment_info ()) {
334 BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
335 LOG_GENERAL ("Content: %1", i->technical_summary());
337 LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
338 if (Config::instance()->only_servers_encode ()) {
339 LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
341 LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads());
343 LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
345 shared_ptr<TranscodeJob> tj (new TranscodeJob (shared_from_this()));
346 tj->set_encoder (shared_ptr<Encoder> (new DCPEncoder (shared_from_this(), tj)));
347 JobManager::instance()->add (tj);
350 /** Start a job to send our DCP to the configured TMS */
352 Film::send_dcp_to_tms ()
354 shared_ptr<Job> j (new UploadJob (shared_from_this()));
355 JobManager::instance()->add (j);
358 shared_ptr<xmlpp::Document>
359 Film::metadata (bool with_content_paths) const
361 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
362 xmlpp::Element* root = doc->create_root_node ("Metadata");
364 root->add_child("Version")->add_child_text (raw_convert<string> (current_state_version));
365 root->add_child("Name")->add_child_text (_name);
366 root->add_child("UseISDCFName")->add_child_text (_use_isdcf_name ? "1" : "0");
368 if (_dcp_content_type) {
369 root->add_child("DCPContentType")->add_child_text (_dcp_content_type->isdcf_name ());
373 root->add_child("Container")->add_child_text (_container->id ());
376 root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
377 root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
378 _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata"));
379 root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
380 root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
381 root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
382 root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
383 root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0");
384 root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
385 root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
386 root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
387 root->add_child("Key")->add_child_text (_key.hex ());
388 root->add_child("ContextID")->add_child_text (_context_id);
389 if (_audio_processor) {
390 root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
392 root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
393 root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
394 root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
395 _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
400 /** Write state to our `metadata' file */
402 Film::write_metadata () const
404 DCPOMATIC_ASSERT (directory());
405 boost::filesystem::create_directories (directory().get());
406 shared_ptr<xmlpp::Document> doc = metadata ();
407 doc->write_to_file_formatted (file(metadata_file).string ());
411 /** Write a template from this film */
413 Film::write_template (boost::filesystem::path path) const
415 boost::filesystem::create_directories (path.parent_path());
416 shared_ptr<xmlpp::Document> doc = metadata (false);
417 doc->write_to_file_formatted (path.string ());
420 /** Read state from our metadata file.
421 * @return Notes about things that the user should know about, or empty.
424 Film::read_metadata (optional<boost::filesystem::path> path)
427 if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file (metadata_file))) {
428 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!"));
431 path = file (metadata_file);
434 cxml::Document f ("Metadata");
435 f.read_file (path.get ());
437 _state_version = f.number_child<int> ("Version");
438 if (_state_version > current_state_version) {
439 throw runtime_error (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!"));
442 _name = f.string_child ("Name");
443 if (_state_version >= 9) {
444 _use_isdcf_name = f.bool_child ("UseISDCFName");
445 _isdcf_metadata = ISDCFMetadata (f.node_child ("ISDCFMetadata"));
446 _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("ISDCFDate"));
448 _use_isdcf_name = f.bool_child ("UseDCIName");
449 _isdcf_metadata = ISDCFMetadata (f.node_child ("DCIMetadata"));
450 _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
454 optional<string> c = f.optional_string_child ("DCPContentType");
456 _dcp_content_type = DCPContentType::from_isdcf_name (c.get ());
461 optional<string> c = f.optional_string_child ("Container");
463 _container = Ratio::from_id (c.get ());
467 _resolution = string_to_resolution (f.string_child ("Resolution"));
468 _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
469 _video_frame_rate = f.number_child<int> ("VideoFrameRate");
470 _signed = f.optional_bool_child("Signed").get_value_or (true);
471 _encrypted = f.bool_child ("Encrypted");
472 _audio_channels = f.number_child<int> ("AudioChannels");
473 /* We used to allow odd numbers (and zero) channels, but it's just not worth
476 if (_audio_channels == 0) {
478 } else if ((_audio_channels % 2) == 1) {
482 if (f.optional_bool_child("SequenceVideo")) {
483 _sequence = f.bool_child("SequenceVideo");
485 _sequence = f.bool_child("Sequence");
488 _three_d = f.bool_child ("ThreeD");
489 _interop = f.bool_child ("Interop");
490 _key = dcp::Key (f.string_child ("Key"));
491 _context_id = f.optional_string_child("ContextID").get_value_or (dcp::make_uuid ());
493 if (f.optional_string_child ("AudioProcessor")) {
494 _audio_processor = AudioProcessor::from_id (f.string_child ("AudioProcessor"));
496 _audio_processor = 0;
499 _reel_type = static_cast<ReelType> (f.optional_number_child<int>("ReelType").get_value_or (static_cast<int>(REELTYPE_SINGLE)));
500 _reel_length = f.optional_number_child<int64_t>("ReelLength").get_value_or (2000000000);
501 _upload_after_make_dcp = f.optional_bool_child("UploadAfterMakeDCP").get_value_or (false);
504 /* This method is the only one that can return notes (so far) */
505 _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
507 /* Write backtraces to this film's directory, until another film is loaded */
509 set_backtrace_file (file ("backtrace.txt"));
516 /** Given a directory name, return its full path within the Film's directory.
517 * @param d directory name within the Film's directory.
518 * @param create true to create the directory (and its parents) if they do not exist.
520 boost::filesystem::path
521 Film::dir (boost::filesystem::path d, bool create) const
523 DCPOMATIC_ASSERT (_directory);
525 boost::filesystem::path p;
526 p /= _directory.get();
530 boost::filesystem::create_directories (p);
536 /** Given a file or directory name, return its full path within the Film's directory.
537 * Any required parent directories will be created.
539 boost::filesystem::path
540 Film::file (boost::filesystem::path f) const
542 DCPOMATIC_ASSERT (_directory);
544 boost::filesystem::path p;
545 p /= _directory.get();
548 boost::filesystem::create_directories (p.parent_path ());
554 Film::mapped_audio_channels () const
558 if (audio_processor ()) {
559 /* Processors are mapped 1:1 to DCP outputs so we can work out mappings from there */
560 for (int i = 0; i < audio_processor()->out_channels(); ++i) {
561 mapped.push_back (i);
564 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
566 list<int> c = i->audio->mapping().mapped_output_channels ();
567 copy (c.begin(), c.end(), back_inserter (mapped));
578 /** @return a ISDCF-compliant name for a DCP of this film */
580 Film::isdcf_name (bool if_created_now) const
584 string raw_name = name ();
586 /* Split the raw name up into words */
587 vector<string> words;
588 split (words, raw_name, is_any_of (" _-"));
592 /* Add each word to fixed_name */
593 for (vector<string>::const_iterator i = words.begin(); i != words.end(); ++i) {
596 /* First letter is always capitalised */
597 w[0] = toupper (w[0]);
599 /* Count caps in w */
601 for (size_t i = 0; i < w.size(); ++i) {
602 if (isupper (w[i])) {
607 /* If w is all caps make the rest of it lower case, otherwise
610 if (caps == w.size ()) {
611 for (size_t i = 1; i < w.size(); ++i) {
612 w[i] = tolower (w[i]);
616 for (size_t i = 0; i < w.size(); ++i) {
621 if (fixed_name.length() > 14) {
622 fixed_name = fixed_name.substr (0, 14);
627 if (dcp_content_type()) {
628 d += "_" + dcp_content_type()->isdcf_name();
629 d += "-" + raw_convert<string>(isdcf_metadata().content_version);
632 ISDCFMetadata const dm = isdcf_metadata ();
634 if (dm.temp_version) {
638 if (dm.pre_release) {
646 if (!dm.chain.empty ()) {
654 if (dm.two_d_version_of_three_d) {
658 if (!dm.mastered_luminance.empty ()) {
659 d += "-" + dm.mastered_luminance;
662 if (video_frame_rate() != 24) {
663 d += "-" + raw_convert<string>(video_frame_rate());
667 d += "_" + container()->isdcf_name();
670 /* XXX: this uses the first bit of content only */
672 /* The standard says we don't do this for trailers, for some strange reason */
673 if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
674 Ratio const * content_ratio = 0;
675 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
677 /* Here's the first piece of video content */
678 if (i->video->scale().ratio ()) {
679 content_ratio = i->video->scale().ratio ();
681 content_ratio = Ratio::from_ratio (i->video->size().ratio ());
687 if (content_ratio && content_ratio != container()) {
688 d += "-" + content_ratio->isdcf_name();
692 if (!dm.audio_language.empty ()) {
693 d += "_" + dm.audio_language;
694 if (!dm.subtitle_language.empty()) {
696 bool burnt_in = true;
697 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
702 if (i->subtitle->use() && !i->subtitle->burn()) {
707 string language = dm.subtitle_language;
708 if (burnt_in && language != "XX") {
709 transform (language.begin(), language.end(), language.begin(), ::tolower);
711 transform (language.begin(), language.end(), language.begin(), ::toupper);
720 if (!dm.territory.empty ()) {
721 d += "_" + dm.territory;
722 if (dm.rating.empty ()) {
725 d += "-" + dm.rating;
729 /* Count mapped audio channels */
731 pair<int, int> ch = audio_channel_types (mapped_audio_channels(), audio_channels());
733 d += String::compose("_%1%2", ch.first, ch.second);
738 d += "_" + resolution_to_string (_resolution);
740 if (!dm.studio.empty ()) {
741 d += "_" + dm.studio;
744 if (if_created_now) {
745 d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
747 d += "_" + boost::gregorian::to_iso_string (_isdcf_date);
750 if (!dm.facility.empty ()) {
751 d += "_" + dm.facility;
765 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
766 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
767 if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) {
781 /** @return name to give the DCP */
783 Film::dcp_name (bool if_created_now) const
786 if (use_isdcf_name()) {
787 return careful_string_filter (isdcf_name (if_created_now));
790 return careful_string_filter (name ());
794 Film::set_directory (boost::filesystem::path d)
801 Film::set_name (string n)
804 signal_changed (NAME);
808 Film::set_use_isdcf_name (bool u)
811 signal_changed (USE_ISDCF_NAME);
815 Film::set_dcp_content_type (DCPContentType const * t)
817 _dcp_content_type = t;
818 signal_changed (DCP_CONTENT_TYPE);
822 Film::set_container (Ratio const * c)
825 signal_changed (CONTAINER);
829 Film::set_resolution (Resolution r)
832 signal_changed (RESOLUTION);
836 Film::set_j2k_bandwidth (int b)
839 signal_changed (J2K_BANDWIDTH);
843 Film::set_isdcf_metadata (ISDCFMetadata m)
846 signal_changed (ISDCF_METADATA);
850 Film::set_video_frame_rate (int f)
852 _video_frame_rate = f;
853 signal_changed (VIDEO_FRAME_RATE);
857 Film::set_audio_channels (int c)
860 signal_changed (AUDIO_CHANNELS);
864 Film::set_three_d (bool t)
867 signal_changed (THREE_D);
869 if (_three_d && _isdcf_metadata.two_d_version_of_three_d) {
870 _isdcf_metadata.two_d_version_of_three_d = false;
871 signal_changed (ISDCF_METADATA);
876 Film::set_interop (bool i)
879 signal_changed (INTEROP);
883 Film::set_audio_processor (AudioProcessor const * processor)
885 _audio_processor = processor;
886 signal_changed (AUDIO_PROCESSOR);
887 signal_changed (AUDIO_CHANNELS);
891 Film::set_reel_type (ReelType t)
894 signal_changed (REEL_TYPE);
897 /** @param r Desired reel length in bytes */
899 Film::set_reel_length (int64_t r)
902 signal_changed (REEL_LENGTH);
906 Film::set_upload_after_make_dcp (bool u)
908 _upload_after_make_dcp = u;
909 signal_changed (UPLOAD_AFTER_MAKE_DCP);
913 Film::signal_changed (Property p)
919 set_video_frame_rate (_playlist->best_video_frame_rate ());
921 case Film::VIDEO_FRAME_RATE:
923 _playlist->maybe_sequence ();
929 emit (boost::bind (boost::ref (Changed), p));
933 Film::set_isdcf_date_today ()
935 _isdcf_date = boost::gregorian::day_clock::local_day ();
938 boost::filesystem::path
939 Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const
941 boost::filesystem::path p;
943 p /= video_identifier ();
946 snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame);
949 if (eyes == EYES_LEFT) {
951 } else if (eyes == EYES_RIGHT) {
965 /** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs */
970 return vector<CPLSummary> ();
973 vector<CPLSummary> out;
975 boost::filesystem::path const dir = directory().get();
976 for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
978 boost::filesystem::is_directory (*i) &&
979 i->path().leaf() != "j2c" && i->path().leaf() != "video" && i->path().leaf() != "info" && i->path().leaf() != "analysis"
985 DCPOMATIC_ASSERT (dcp.cpls().front()->file());
988 i->path().leaf().string(),
989 dcp.cpls().front()->id(),
990 dcp.cpls().front()->annotation_text(),
991 dcp.cpls().front()->file().get()
1004 Film::set_signed (bool s)
1007 signal_changed (SIGNED);
1011 Film::set_encrypted (bool e)
1014 signal_changed (ENCRYPTED);
1018 Film::set_key (dcp::Key key)
1021 signal_changed (KEY);
1025 Film::content () const
1027 return _playlist->content ();
1031 Film::examine_and_add_content (shared_ptr<Content> c)
1033 if (dynamic_pointer_cast<FFmpegContent> (c) && _directory) {
1034 run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
1037 shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
1039 _job_connections.push_back (
1040 j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr<Job> (j), weak_ptr<Content> (c)))
1043 JobManager::instance()->add (j);
1047 Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
1049 shared_ptr<Job> job = j.lock ();
1050 if (!job || !job->finished_ok ()) {
1054 shared_ptr<Content> content = c.lock ();
1059 add_content (content);
1061 if (Config::instance()->automatic_audio_analysis() && content->audio) {
1062 shared_ptr<Playlist> playlist (new Playlist);
1063 playlist->add (content);
1064 boost::signals2::connection c;
1065 JobManager::instance()->analyse_audio (
1066 shared_from_this (), playlist, c, bind (&Film::audio_analysis_finished, this)
1068 _audio_analysis_connections.push_back (c);
1073 Film::add_content (shared_ptr<Content> c)
1075 /* Add {video,subtitle} content after any existing {video,subtitle} content */
1077 c->set_position (_playlist->video_end ());
1078 } else if (c->subtitle) {
1079 c->set_position (_playlist->subtitle_end ());
1082 if (_template_film) {
1083 /* Take settings from the first piece of content of c's type in _template */
1084 BOOST_FOREACH (shared_ptr<Content> i, _template_film->content()) {
1085 if (typeid(i.get()) == typeid(c.get())) {
1086 c->use_template (i);
1095 Film::remove_content (shared_ptr<Content> c)
1097 _playlist->remove (c);
1101 Film::move_content_earlier (shared_ptr<Content> c)
1103 _playlist->move_earlier (c);
1107 Film::move_content_later (shared_ptr<Content> c)
1109 _playlist->move_later (c);
1112 /** @return length of the film from time 0 to the last thing on the playlist */
1114 Film::length () const
1116 return _playlist->length().ceil(video_frame_rate());
1120 Film::best_video_frame_rate () const
1122 return _playlist->best_video_frame_rate ();
1126 Film::active_frame_rate_change (DCPTime t) const
1128 return _playlist->active_frame_rate_change (t, video_frame_rate ());
1132 Film::playlist_content_changed (weak_ptr<Content> c, int p, bool frequent)
1136 if (p == ContentProperty::VIDEO_FRAME_RATE) {
1137 set_video_frame_rate (_playlist->best_video_frame_rate ());
1138 } else if (p == AudioContentProperty::STREAMS) {
1139 signal_changed (NAME);
1142 emit (boost::bind (boost::ref (ContentChanged), c, p, frequent));
1146 Film::playlist_changed ()
1148 signal_changed (CONTENT);
1149 signal_changed (NAME);
1153 Film::playlist_order_changed ()
1155 signal_changed (CONTENT_ORDER);
1159 Film::audio_frame_rate () const
1161 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
1162 if (i->audio && i->audio->has_rate_above_48k ()) {
1171 Film::set_sequence (bool s)
1174 _playlist->set_sequence (s);
1175 signal_changed (SEQUENCE);
1178 /** @return Size of the largest possible image in whatever resolution we are using */
1180 Film::full_frame () const
1182 switch (_resolution) {
1184 return dcp::Size (2048, 1080);
1186 return dcp::Size (4096, 2160);
1189 DCPOMATIC_ASSERT (false);
1190 return dcp::Size ();
1193 /** @return Size of the frame */
1195 Film::frame_size () const
1197 return fit_ratio_within (container()->ratio(), full_frame ());
1200 /** @param recipient KDM recipient certificate.
1201 * @param trusted_devices Certificates of other trusted devices (can be empty).
1202 * @param cpl_file CPL filename.
1203 * @param from KDM from time expressed as a local time with an offset from UTC.
1204 * @param until KDM to time expressed as a local time with an offset from UTC.
1205 * @param formulation KDM formulation to use.
1209 dcp::Certificate recipient,
1210 vector<dcp::Certificate> trusted_devices,
1211 boost::filesystem::path cpl_file,
1212 dcp::LocalTime from,
1213 dcp::LocalTime until,
1214 dcp::Formulation formulation
1217 shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
1218 shared_ptr<const dcp::CertificateChain> signer = Config::instance()->signer_chain ();
1219 if (!signer->valid ()) {
1220 throw InvalidSignerError ();
1223 /* Find keys that have been added to imported, encrypted DCP content */
1224 list<dcp::DecryptedKDMKey> imported_keys;
1225 BOOST_FOREACH (shared_ptr<Content> i, content()) {
1226 shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
1227 if (d && d->kdm()) {
1228 dcp::DecryptedKDM kdm (d->kdm().get(), Config::instance()->decryption_chain()->key().get());
1229 list<dcp::DecryptedKDMKey> keys = kdm.keys ();
1230 copy (keys.begin(), keys.end(), back_inserter (imported_keys));
1234 map<shared_ptr<const dcp::ReelMXF>, dcp::Key> keys;
1236 BOOST_FOREACH(shared_ptr<const dcp::ReelAsset> i, cpl->reel_assets ()) {
1237 shared_ptr<const dcp::ReelMXF> mxf = boost::dynamic_pointer_cast<const dcp::ReelMXF> (i);
1238 if (!mxf || !mxf->key_id()) {
1242 /* Get any imported key for this ID */
1244 BOOST_FOREACH (dcp::DecryptedKDMKey j, imported_keys) {
1245 if (j.id() == mxf->key_id().get()) {
1246 LOG_GENERAL ("Using imported key for %1", mxf->key_id().get());
1247 keys[mxf] = j.key();
1253 /* No imported key; it must be an asset that we encrypted */
1254 LOG_GENERAL ("Using our own key for %1", mxf->key_id().get());
1259 return dcp::DecryptedKDM (
1260 cpl->id(), keys, from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
1261 ).encrypt (signer, recipient, trusted_devices, formulation);
1264 /** @param screens Screens to make KDMs for.
1265 * @param cpl_file Path to CPL to make KDMs for.
1266 * @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema.
1267 * @param until KDM to time expressed as a local time in the time zone of the Screen's Cinema.
1268 * @param formulation KDM formulation to use.
1272 list<shared_ptr<Screen> > screens,
1273 boost::filesystem::path cpl_file,
1274 boost::posix_time::ptime from,
1275 boost::posix_time::ptime until,
1276 dcp::Formulation formulation
1279 list<ScreenKDM> kdms;
1281 BOOST_FOREACH (shared_ptr<Screen> i, screens) {
1283 dcp::EncryptedKDM const kdm = make_kdm (
1287 dcp::LocalTime (from, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
1288 dcp::LocalTime (until, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
1292 kdms.push_back (ScreenKDM (i, kdm));
1299 /** @return The approximate disk space required to encode a DCP of this film with the
1300 * current settings, in bytes.
1303 Film::required_disk_space () const
1305 return _playlist->required_disk_space (j2k_bandwidth(), audio_channels(), audio_frame_rate());
1308 /** This method checks the disk that the Film is on and tries to decide whether or not
1309 * there will be enough space to make a DCP for it. If so, true is returned; if not,
1310 * false is returned and required and available are filled in with the amount of disk space
1311 * required and available respectively (in Gb).
1313 * Note: the decision made by this method isn't, of course, 100% reliable.
1316 Film::should_be_enough_disk_space (double& required, double& available, bool& can_hard_link) const
1318 /* Create a test file and see if we can hard-link it */
1319 boost::filesystem::path test = internal_video_asset_dir() / "test";
1320 boost::filesystem::path test2 = internal_video_asset_dir() / "test2";
1321 can_hard_link = true;
1322 FILE* f = fopen_boost (test, "w");
1325 boost::system::error_code ec;
1326 boost::filesystem::create_hard_link (test, test2, ec);
1328 can_hard_link = false;
1330 boost::filesystem::remove (test);
1331 boost::filesystem::remove (test2);
1334 boost::filesystem::space_info s = boost::filesystem::space (internal_video_asset_dir ());
1335 required = double (required_disk_space ()) / 1073741824.0f;
1336 if (!can_hard_link) {
1339 available = double (s.available) / 1073741824.0f;
1340 return (available - required) > 1;
1344 Film::subtitle_language () const
1346 set<string> languages;
1348 ContentList cl = content ();
1349 BOOST_FOREACH (shared_ptr<Content>& c, cl) {
1351 languages.insert (c->subtitle->language ());
1356 BOOST_FOREACH (string s, languages) {
1357 if (!all.empty ()) {
1367 /** Change the gains of the supplied AudioMapping to make it a default
1368 * for this film. The defaults are guessed based on what processor (if any)
1369 * is in use, the number of input channels and any filename supplied.
1372 Film::make_audio_mapping_default (AudioMapping& mapping, optional<boost::filesystem::path> filename) const
1374 static string const regex[] = {
1375 ".*[\\._-]L[\\._-].*",
1376 ".*[\\._-]R[\\._-].*",
1377 ".*[\\._-]C[\\._-].*",
1378 ".*[\\._-]Lfe[\\._-].*",
1379 ".*[\\._-]Ls[\\._-].*",
1380 ".*[\\._-]Rs[\\._-].*"
1383 static int const regexes = sizeof(regex) / sizeof(*regex);
1385 if (audio_processor ()) {
1386 audio_processor()->make_audio_mapping_default (mapping);
1388 mapping.make_zero ();
1389 if (mapping.input_channels() == 1) {
1390 bool guessed = false;
1392 /* See if we can guess where this stream should go */
1394 for (int i = 0; i < regexes; ++i) {
1395 boost::regex e (regex[i], boost::regex::icase);
1396 if (boost::regex_match (filename->string(), e) && i < mapping.output_channels()) {
1397 mapping.set (0, i, 1);
1404 /* If we have no idea, just put it on centre */
1405 mapping.set (0, static_cast<int> (dcp::CENTRE), 1);
1409 for (int i = 0; i < min (mapping.input_channels(), mapping.output_channels()); ++i) {
1410 mapping.set (i, i, 1);
1416 /** @return The names of the channels that audio contents' outputs are passed into;
1417 * this is either the DCP or a AudioProcessor.
1420 Film::audio_output_names () const
1422 if (audio_processor ()) {
1423 return audio_processor()->input_names ();
1426 DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
1430 for (int i = 0; i < audio_channels(); ++i) {
1431 n.push_back (short_audio_channel_name (i));
1438 Film::repeat_content (ContentList c, int n)
1440 _playlist->repeat (c, n);
1444 Film::remove_content (ContentList c)
1446 _playlist->remove (c);
1450 Film::audio_analysis_finished ()
1456 Film::reels () const
1458 list<DCPTimePeriod> p;
1459 DCPTime const len = length();
1461 switch (reel_type ()) {
1462 case REELTYPE_SINGLE:
1463 p.push_back (DCPTimePeriod (DCPTime (), len));
1465 case REELTYPE_BY_VIDEO_CONTENT:
1467 optional<DCPTime> last_split;
1468 shared_ptr<Content> last_video;
1469 BOOST_FOREACH (shared_ptr<Content> c, content ()) {
1471 BOOST_FOREACH (DCPTime t, c->reel_split_points()) {
1473 p.push_back (DCPTimePeriod (last_split.get(), t));
1481 DCPTime video_end = last_video ? last_video->end() : DCPTime(0);
1483 /* Definitely go from the last split to the end of the video content */
1484 p.push_back (DCPTimePeriod (last_split.get(), video_end));
1487 if (video_end < len) {
1488 /* And maybe go after that as well if there is any non-video hanging over the end */
1489 p.push_back (DCPTimePeriod (video_end, len));
1493 case REELTYPE_BY_LENGTH:
1496 /* Integer-divide reel length by the size of one frame to give the number of frames per reel */
1497 Frame const reel_in_frames = _reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8);
1498 while (current < len) {
1499 DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ()));
1500 p.push_back (DCPTimePeriod (current, end));
1510 /** @param period A period within the DCP
1511 * @return Name of the content which most contributes to the given period.
1514 Film::content_summary (DCPTimePeriod period) const
1516 return _playlist->content_summary (period);
1520 Film::fix_conflicting_settings ()
1524 list<boost::filesystem::path> was_referencing;
1525 BOOST_FOREACH (shared_ptr<Content> i, content()) {
1526 shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
1528 list<string> reasons;
1530 if (!d->can_reference_video(reasons) && d->reference_video()) {
1531 d->set_reference_video (false);
1534 if (!d->can_reference_audio(reasons) && d->reference_audio()) {
1535 d->set_reference_audio (false);
1538 if (!d->can_reference_subtitle(reasons) && d->reference_subtitle()) {
1539 d->set_reference_subtitle (false);
1543 was_referencing.push_back (d->path(0).parent_path().filename());
1548 BOOST_FOREACH (boost::filesystem::path d, was_referencing) {
1549 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()));
1556 Film::use_template (string name)
1558 _template_film.reset (new Film (optional<boost::filesystem::path>()));
1559 _template_film->read_metadata (Config::instance()->template_path (name));
1560 _use_isdcf_name = _template_film->_use_isdcf_name;
1561 _dcp_content_type = _template_film->_dcp_content_type;
1562 _container = _template_film->_container;
1563 _resolution = _template_film->_resolution;
1564 _j2k_bandwidth = _template_film->_j2k_bandwidth;
1565 _video_frame_rate = _template_film->_video_frame_rate;
1566 _signed = _template_film->_signed;
1567 _encrypted = _template_film->_encrypted;
1568 _audio_channels = _template_film->_audio_channels;
1569 _sequence = _template_film->_sequence;
1570 _three_d = _template_film->_three_d;
1571 _interop = _template_film->_interop;
1572 _audio_processor = _template_film->_audio_processor;
1573 _reel_type = _template_film->_reel_type;
1574 _reel_length = _template_film->_reel_length;
1575 _upload_after_make_dcp = _template_film->_upload_after_make_dcp;
1578 pair<double, double>
1579 Film::speed_up_range (int dcp_frame_rate) const
1581 return _playlist->speed_up_range (dcp_frame_rate);
1585 Film::copy_from (shared_ptr<const Film> film)
1587 read_metadata (film->file (metadata_file));