2 Copyright (C) 2012-2016 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 <libxml++/libxml++.h>
61 #include <boost/filesystem.hpp>
62 #include <boost/algorithm/string.hpp>
63 #include <boost/foreach.hpp>
64 #include <boost/regex.hpp>
85 using std::runtime_error;
86 using boost::shared_ptr;
87 using boost::weak_ptr;
88 using boost::dynamic_pointer_cast;
89 using boost::optional;
90 using boost::is_any_of;
91 using dcp::raw_convert;
93 #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
94 #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
97 * AudioMapping XML changed.
99 * Subtitle offset changed to subtitle y offset, and subtitle x offset added.
101 * Use <Scale> tag in <VideoContent> rather than <Ratio>.
105 * Subtitle X and Y scale.
107 * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
111 * Changed <Period> to <Subtitle> in FFmpegSubtitleStream
113 * Content only contains audio/subtitle-related tags if those things
116 * VideoFrameType in VideoContent is a string rather than an integer.
118 * EffectColour rather than OutlineColour in Subtitle.
120 int const Film::current_state_version = 36;
122 /** Construct a Film object in a given directory.
124 * @param dir Film directory.
127 Film::Film (boost::filesystem::path dir, bool log)
128 : _playlist (new Playlist)
129 , _use_isdcf_name (true)
130 , _dcp_content_type (Config::instance()->default_dcp_content_type ())
131 , _container (Config::instance()->default_container ())
132 , _resolution (RESOLUTION_2K)
135 , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
136 , _isdcf_metadata (Config::instance()->default_isdcf_metadata ())
137 , _video_frame_rate (24)
138 , _audio_channels (Config::instance()->default_dcp_audio_channels ())
141 , _interop (Config::instance()->default_interop ())
142 , _audio_processor (0)
143 , _reel_type (REELTYPE_SINGLE)
144 , _reel_length (2000000000)
145 , _upload_after_make_dcp (false)
146 , _state_version (current_state_version)
149 set_isdcf_date_today ();
151 _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this));
152 _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
153 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3));
155 /* Make state.directory a complete path without ..s (where possible)
156 (Code swiped from Adam Bowen on stackoverflow)
157 XXX: couldn't/shouldn't this just be boost::filesystem::canonical?
160 boost::filesystem::path p (boost::filesystem::system_complete (dir));
161 boost::filesystem::path result;
162 for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
164 if (boost::filesystem::is_symlink (result) || result.filename() == "..") {
167 result = result.parent_path ();
169 } else if (*i != ".") {
174 set_directory (result.make_preferred ());
176 _log.reset (new FileLog (file ("log")));
178 _log.reset (new NullLog);
181 _playlist->set_sequence (_sequence);
186 BOOST_FOREACH (boost::signals2::connection& i, _job_connections) {
190 BOOST_FOREACH (boost::signals2::connection& i, _audio_analysis_connections) {
196 Film::video_identifier () const
198 DCPOMATIC_ASSERT (container ());
200 string s = container()->id()
201 + "_" + resolution_to_string (_resolution)
202 + "_" + _playlist->video_identifier()
203 + "_" + raw_convert<string>(_video_frame_rate)
204 + "_" + raw_convert<string>(j2k_bandwidth());
225 /** @return The file to write video frame info to */
226 boost::filesystem::path
227 Film::info_file (DCPTimePeriod period) const
229 boost::filesystem::path p;
231 p /= video_identifier () + "_" + raw_convert<string> (period.from.get()) + "_" + raw_convert<string> (period.to.get());
235 boost::filesystem::path
236 Film::internal_video_asset_dir () const
238 return dir ("video");
241 boost::filesystem::path
242 Film::internal_video_asset_filename (DCPTimePeriod p) const
244 return video_identifier() + "_" + raw_convert<string> (p.from.get()) + "_" + raw_convert<string> (p.to.get()) + ".mxf";
247 boost::filesystem::path
248 Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
250 boost::filesystem::path p = dir ("analysis");
253 BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
258 digester.add (i->digest ());
259 digester.add (i->audio->mapping().digest ());
260 if (playlist->content().size() != 1) {
261 /* Analyses should be considered equal regardless of gain
262 if they were made from just one piece of content. This
263 is because we can fake any gain change in a single-content
264 analysis at the plotting stage rather than having to
267 digester.add (i->audio->gain ());
271 if (audio_processor ()) {
272 digester.add (audio_processor()->id ());
275 p /= digester.get ();
279 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
283 if (dcp_name().find ("/") != string::npos) {
284 throw BadSettingError (_("name"), _("cannot contain slashes"));
287 set_isdcf_date_today ();
289 BOOST_FOREACH (string i, environment_info ()) {
293 BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
294 LOG_GENERAL ("Content: %1", i->technical_summary());
296 LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
297 if (Config::instance()->only_servers_encode ()) {
298 LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
300 LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads());
302 LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
304 if (container() == 0) {
305 throw MissingSettingError (_("container"));
308 if (content().empty()) {
309 throw runtime_error (_("You must add some content to the DCP before creating it"));
312 if (dcp_content_type() == 0) {
313 throw MissingSettingError (_("content type"));
316 if (name().empty()) {
317 throw MissingSettingError (_("name"));
320 JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
323 /** Start a job to send our DCP to the configured TMS */
325 Film::send_dcp_to_tms ()
327 shared_ptr<Job> j (new UploadJob (shared_from_this()));
328 JobManager::instance()->add (j);
331 shared_ptr<xmlpp::Document>
332 Film::metadata () const
334 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
335 xmlpp::Element* root = doc->create_root_node ("Metadata");
337 root->add_child("Version")->add_child_text (raw_convert<string> (current_state_version));
338 root->add_child("Name")->add_child_text (_name);
339 root->add_child("UseISDCFName")->add_child_text (_use_isdcf_name ? "1" : "0");
341 if (_dcp_content_type) {
342 root->add_child("DCPContentType")->add_child_text (_dcp_content_type->isdcf_name ());
346 root->add_child("Container")->add_child_text (_container->id ());
349 root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
350 root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
351 _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata"));
352 root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
353 root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
354 root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
355 root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
356 root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0");
357 root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
358 root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
359 root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
360 root->add_child("Key")->add_child_text (_key.hex ());
361 if (_audio_processor) {
362 root->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
364 root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
365 root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
366 root->add_child("UploadAfterMakeDCP")->add_child_text (_upload_after_make_dcp ? "1" : "0");
367 _playlist->as_xml (root->add_child ("Playlist"));
372 /** Write state to our `metadata' file */
374 Film::write_metadata () const
376 boost::filesystem::create_directories (directory ());
377 shared_ptr<xmlpp::Document> doc = metadata ();
378 doc->write_to_file_formatted (file("metadata.xml").string ());
382 /** Read state from our metadata file.
383 * @return Notes about things that the user should know about, or empty.
386 Film::read_metadata ()
388 if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
389 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!"));
392 cxml::Document f ("Metadata");
393 f.read_file (file ("metadata.xml"));
395 _state_version = f.number_child<int> ("Version");
396 if (_state_version > current_state_version) {
397 throw runtime_error (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version. Sorry!"));
400 _name = f.string_child ("Name");
401 if (_state_version >= 9) {
402 _use_isdcf_name = f.bool_child ("UseISDCFName");
403 _isdcf_metadata = ISDCFMetadata (f.node_child ("ISDCFMetadata"));
404 _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("ISDCFDate"));
406 _use_isdcf_name = f.bool_child ("UseDCIName");
407 _isdcf_metadata = ISDCFMetadata (f.node_child ("DCIMetadata"));
408 _isdcf_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
412 optional<string> c = f.optional_string_child ("DCPContentType");
414 _dcp_content_type = DCPContentType::from_isdcf_name (c.get ());
419 optional<string> c = f.optional_string_child ("Container");
421 _container = Ratio::from_id (c.get ());
425 _resolution = string_to_resolution (f.string_child ("Resolution"));
426 _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
427 _video_frame_rate = f.number_child<int> ("VideoFrameRate");
428 _signed = f.optional_bool_child("Signed").get_value_or (true);
429 _encrypted = f.bool_child ("Encrypted");
430 _audio_channels = f.number_child<int> ("AudioChannels");
431 /* We used to allow odd numbers (and zero) channels, but it's just not worth
434 if (_audio_channels == 0) {
436 } else if ((_audio_channels % 2) == 1) {
440 if (f.optional_bool_child("SequenceVideo")) {
441 _sequence = f.bool_child("SequenceVideo");
443 _sequence = f.bool_child("Sequence");
446 _three_d = f.bool_child ("ThreeD");
447 _interop = f.bool_child ("Interop");
448 _key = dcp::Key (f.string_child ("Key"));
450 if (f.optional_string_child ("AudioProcessor")) {
451 _audio_processor = AudioProcessor::from_id (f.string_child ("AudioProcessor"));
453 _audio_processor = 0;
456 _reel_type = static_cast<ReelType> (f.optional_number_child<int>("ReelType").get_value_or (static_cast<int>(REELTYPE_SINGLE)));
457 _reel_length = f.optional_number_child<int64_t>("ReelLength").get_value_or (2000000000);
458 _upload_after_make_dcp = f.optional_bool_child("UploadAfterMakeDCP").get_value_or (false);
461 /* This method is the only one that can return notes (so far) */
462 _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version, notes);
464 /* Write backtraces to this film's directory, until another film is loaded */
465 set_backtrace_file (file ("backtrace.txt"));
471 /** Given a directory name, return its full path within the Film's directory.
472 * The directory (and its parents) will be created if they do not exist.
474 boost::filesystem::path
475 Film::dir (boost::filesystem::path d) const
477 boost::filesystem::path p;
481 boost::filesystem::create_directories (p);
486 /** Given a file or directory name, return its full path within the Film's directory.
487 * Any required parent directories will be created.
489 boost::filesystem::path
490 Film::file (boost::filesystem::path f) const
492 boost::filesystem::path p;
496 boost::filesystem::create_directories (p.parent_path ());
502 Film::mapped_audio_channels () const
506 if (audio_processor ()) {
507 /* Processors are mapped 1:1 to DCP outputs so we can work out mappings from there */
508 for (int i = 0; i < audio_processor()->out_channels(); ++i) {
509 mapped.push_back (i);
512 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
514 list<int> c = i->audio->mapping().mapped_output_channels ();
515 copy (c.begin(), c.end(), back_inserter (mapped));
526 /** @return a ISDCF-compliant name for a DCP of this film */
528 Film::isdcf_name (bool if_created_now) const
532 string raw_name = name ();
534 /* Split the raw name up into words */
535 vector<string> words;
536 split (words, raw_name, is_any_of (" _-"));
540 /* Add each word to fixed_name */
541 for (vector<string>::const_iterator i = words.begin(); i != words.end(); ++i) {
544 /* First letter is always capitalised */
545 w[0] = toupper (w[0]);
547 /* Count caps in w */
549 for (size_t i = 0; i < w.size(); ++i) {
550 if (isupper (w[i])) {
555 /* If w is all caps make the rest of it lower case, otherwise
558 if (caps == w.size ()) {
559 for (size_t i = 1; i < w.size(); ++i) {
560 w[i] = tolower (w[i]);
564 for (size_t i = 0; i < w.size(); ++i) {
569 if (fixed_name.length() > 14) {
570 fixed_name = fixed_name.substr (0, 14);
575 if (dcp_content_type()) {
576 d += "_" + dcp_content_type()->isdcf_name();
577 d += "-" + raw_convert<string>(isdcf_metadata().content_version);
580 ISDCFMetadata const dm = isdcf_metadata ();
582 if (dm.temp_version) {
586 if (dm.pre_release) {
594 if (!dm.chain.empty ()) {
602 if (dm.two_d_version_of_three_d) {
606 if (!dm.mastered_luminance.empty ()) {
607 d += "-" + dm.mastered_luminance;
610 if (video_frame_rate() != 24) {
611 d += "-" + raw_convert<string>(video_frame_rate());
615 d += "_" + container()->isdcf_name();
618 /* XXX: this uses the first bit of content only */
620 /* The standard says we don't do this for trailers, for some strange reason */
621 if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
622 Ratio const * content_ratio = 0;
623 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
625 /* Here's the first piece of video content */
626 if (i->video->scale().ratio ()) {
627 content_ratio = i->video->scale().ratio ();
629 content_ratio = Ratio::from_ratio (i->video->size().ratio ());
635 if (content_ratio && content_ratio != container()) {
636 d += "-" + content_ratio->isdcf_name();
640 if (!dm.audio_language.empty ()) {
641 d += "_" + dm.audio_language;
642 if (!dm.subtitle_language.empty()) {
644 bool burnt_in = true;
645 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
650 if (i->subtitle->use() && !i->subtitle->burn()) {
655 string language = dm.subtitle_language;
656 if (burnt_in && language != "XX") {
657 transform (language.begin(), language.end(), language.begin(), ::tolower);
659 transform (language.begin(), language.end(), language.begin(), ::toupper);
668 if (!dm.territory.empty ()) {
669 d += "_" + dm.territory;
670 if (dm.rating.empty ()) {
673 d += "-" + dm.rating;
677 /* Count mapped audio channels */
682 BOOST_FOREACH (int i, mapped_audio_channels ()) {
683 if (i >= audio_channels()) {
684 /* This channel is mapped but is not included in the DCP */
688 if (static_cast<dcp::Channel> (i) == dcp::LFE) {
696 d += String::compose("_%1%2", non_lfe, lfe);
701 d += "_" + resolution_to_string (_resolution);
703 if (!dm.studio.empty ()) {
704 d += "_" + dm.studio;
707 if (if_created_now) {
708 d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
710 d += "_" + boost::gregorian::to_iso_string (_isdcf_date);
713 if (!dm.facility.empty ()) {
714 d += "_" + dm.facility;
728 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
729 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
730 if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) {
744 /** @return name to give the DCP */
746 Film::dcp_name (bool if_created_now) const
749 if (use_isdcf_name()) {
750 unfiltered = isdcf_name (if_created_now);
752 unfiltered = name ();
755 /* Filter out `bad' characters which cause problems with some systems.
756 There's no apparent list of what really is allowed, so this is a guess.
760 string const allowed = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
761 for (size_t i = 0; i < unfiltered.size(); ++i) {
762 if (allowed.find (unfiltered[i]) != string::npos) {
763 filtered += unfiltered[i];
771 Film::set_directory (boost::filesystem::path d)
778 Film::set_name (string n)
781 signal_changed (NAME);
785 Film::set_use_isdcf_name (bool u)
788 signal_changed (USE_ISDCF_NAME);
792 Film::set_dcp_content_type (DCPContentType const * t)
794 _dcp_content_type = t;
795 signal_changed (DCP_CONTENT_TYPE);
799 Film::set_container (Ratio const * c)
802 signal_changed (CONTAINER);
806 Film::set_resolution (Resolution r)
809 signal_changed (RESOLUTION);
813 Film::set_j2k_bandwidth (int b)
816 signal_changed (J2K_BANDWIDTH);
820 Film::set_isdcf_metadata (ISDCFMetadata m)
823 signal_changed (ISDCF_METADATA);
827 Film::set_video_frame_rate (int f)
829 _video_frame_rate = f;
830 signal_changed (VIDEO_FRAME_RATE);
834 Film::set_audio_channels (int c)
837 signal_changed (AUDIO_CHANNELS);
841 Film::set_three_d (bool t)
844 signal_changed (THREE_D);
846 if (_three_d && _isdcf_metadata.two_d_version_of_three_d) {
847 _isdcf_metadata.two_d_version_of_three_d = false;
848 signal_changed (ISDCF_METADATA);
853 Film::set_interop (bool i)
856 signal_changed (INTEROP);
860 Film::set_audio_processor (AudioProcessor const * processor)
862 _audio_processor = processor;
863 signal_changed (AUDIO_PROCESSOR);
864 signal_changed (AUDIO_CHANNELS);
868 Film::set_reel_type (ReelType t)
871 signal_changed (REEL_TYPE);
874 /** @param r Desired reel length in bytes */
876 Film::set_reel_length (int64_t r)
879 signal_changed (REEL_LENGTH);
883 Film::set_upload_after_make_dcp (bool u)
885 _upload_after_make_dcp = u;
886 signal_changed (UPLOAD_AFTER_MAKE_DCP);
890 Film::signal_changed (Property p)
896 set_video_frame_rate (_playlist->best_video_frame_rate ());
898 case Film::VIDEO_FRAME_RATE:
900 _playlist->maybe_sequence ();
906 emit (boost::bind (boost::ref (Changed), p));
910 Film::set_isdcf_date_today ()
912 _isdcf_date = boost::gregorian::day_clock::local_day ();
915 boost::filesystem::path
916 Film::j2c_path (int reel, Frame frame, Eyes eyes, bool tmp) const
918 boost::filesystem::path p;
920 p /= video_identifier ();
923 snprintf(buffer, sizeof(buffer), "%08d_%08" PRId64, reel, frame);
926 if (eyes == EYES_LEFT) {
928 } else if (eyes == EYES_RIGHT) {
942 /** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs */
946 vector<CPLSummary> out;
948 boost::filesystem::path const dir = directory ();
949 for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
951 boost::filesystem::is_directory (*i) &&
952 i->path().leaf() != "j2c" && i->path().leaf() != "video" && i->path().leaf() != "info" && i->path().leaf() != "analysis"
958 DCPOMATIC_ASSERT (dcp.cpls().front()->file());
961 i->path().leaf().string(),
962 dcp.cpls().front()->id(),
963 dcp.cpls().front()->annotation_text(),
964 dcp.cpls().front()->file().get()
977 Film::set_signed (bool s)
980 signal_changed (SIGNED);
984 Film::set_encrypted (bool e)
987 signal_changed (ENCRYPTED);
991 Film::set_key (dcp::Key key)
994 signal_changed (KEY);
998 Film::content () const
1000 return _playlist->content ();
1004 Film::examine_content (shared_ptr<Content> c)
1006 shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
1007 JobManager::instance()->add (j);
1011 Film::examine_and_add_content (shared_ptr<Content> c)
1013 if (dynamic_pointer_cast<FFmpegContent> (c) && !_directory.empty ()) {
1014 run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
1017 shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
1019 _job_connections.push_back (
1020 j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr<Job> (j), weak_ptr<Content> (c)))
1023 JobManager::instance()->add (j);
1027 Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
1029 shared_ptr<Job> job = j.lock ();
1030 if (!job || !job->finished_ok ()) {
1034 shared_ptr<Content> content = c.lock ();
1039 add_content (content);
1040 if (Config::instance()->automatic_audio_analysis() && content->audio) {
1041 shared_ptr<Playlist> playlist (new Playlist);
1042 playlist->add (content);
1043 boost::signals2::connection c;
1044 JobManager::instance()->analyse_audio (
1045 shared_from_this (), playlist, c, bind (&Film::audio_analysis_finished, this)
1047 _audio_analysis_connections.push_back (c);
1052 Film::add_content (shared_ptr<Content> c)
1054 /* Add {video,subtitle} content after any existing {video,subtitle} content */
1056 c->set_position (_playlist->video_end ());
1057 } else if (c->subtitle) {
1058 c->set_position (_playlist->subtitle_end ());
1065 Film::remove_content (shared_ptr<Content> c)
1067 _playlist->remove (c);
1071 Film::move_content_earlier (shared_ptr<Content> c)
1073 _playlist->move_earlier (c);
1077 Film::move_content_later (shared_ptr<Content> c)
1079 _playlist->move_later (c);
1082 /** @return length of the film from time 0 to the last thing on the playlist */
1084 Film::length () const
1086 return _playlist->length ();
1090 Film::best_video_frame_rate () const
1092 return _playlist->best_video_frame_rate ();
1096 Film::active_frame_rate_change (DCPTime t) const
1098 return _playlist->active_frame_rate_change (t, video_frame_rate ());
1102 Film::playlist_content_changed (weak_ptr<Content> c, int p, bool frequent)
1106 if (p == ContentProperty::VIDEO_FRAME_RATE) {
1107 set_video_frame_rate (_playlist->best_video_frame_rate ());
1108 } else if (p == AudioContentProperty::STREAMS) {
1109 signal_changed (NAME);
1112 emit (boost::bind (boost::ref (ContentChanged), c, p, frequent));
1116 Film::playlist_changed ()
1118 signal_changed (CONTENT);
1119 signal_changed (NAME);
1123 Film::playlist_order_changed ()
1125 signal_changed (CONTENT_ORDER);
1129 Film::audio_frame_rate () const
1131 BOOST_FOREACH (shared_ptr<Content> i, content ()) {
1132 if (i->audio && i->audio->has_rate_above_48k ()) {
1141 Film::set_sequence (bool s)
1144 _playlist->set_sequence (s);
1145 signal_changed (SEQUENCE);
1148 /** @return Size of the largest possible image in whatever resolution we are using */
1150 Film::full_frame () const
1152 switch (_resolution) {
1154 return dcp::Size (2048, 1080);
1156 return dcp::Size (4096, 2160);
1159 DCPOMATIC_ASSERT (false);
1160 return dcp::Size ();
1163 /** @return Size of the frame */
1165 Film::frame_size () const
1167 return fit_ratio_within (container()->ratio(), full_frame ());
1170 /** @param from KDM from time expressed as a local time with an offset from UTC
1171 * @param to KDM to time expressed as a local time with an offset from UTC
1175 dcp::Certificate recipient,
1176 vector<dcp::Certificate> trusted_devices,
1177 boost::filesystem::path cpl_file,
1178 dcp::LocalTime from,
1179 dcp::LocalTime until,
1180 dcp::Formulation formulation
1183 shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
1184 shared_ptr<const dcp::CertificateChain> signer = Config::instance()->signer_chain ();
1185 if (!signer->valid ()) {
1186 throw InvalidSignerError ();
1189 return dcp::DecryptedKDM (
1190 cpl, key(), from, until, cpl->content_title_text(), cpl->content_title_text(), dcp::LocalTime().as_string()
1191 ).encrypt (signer, recipient, trusted_devices, formulation);
1194 /** @param from KDM from time expressed as a local time in the time zone of the Screen's Cinema.
1195 * @param to KDM to time expressed as a local time in the time zone of the Screen's Cinema.
1199 list<shared_ptr<Screen> > screens,
1200 boost::filesystem::path dcp,
1201 boost::posix_time::ptime from,
1202 boost::posix_time::ptime until,
1203 dcp::Formulation formulation
1206 list<ScreenKDM> kdms;
1208 BOOST_FOREACH (shared_ptr<Screen> i, screens) {
1210 dcp::EncryptedKDM const kdm = make_kdm (
1214 dcp::LocalTime (from, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
1215 dcp::LocalTime (until, i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
1219 kdms.push_back (ScreenKDM (i, kdm));
1226 /** @return The approximate disk space required to encode a DCP of this film with the
1227 * current settings, in bytes.
1230 Film::required_disk_space () const
1232 return _playlist->required_disk_space (j2k_bandwidth(), audio_channels(), audio_frame_rate());
1235 /** This method checks the disk that the Film is on and tries to decide whether or not
1236 * there will be enough space to make a DCP for it. If so, true is returned; if not,
1237 * false is returned and required and available are filled in with the amount of disk space
1238 * required and available respectively (in Gb).
1240 * Note: the decision made by this method isn't, of course, 100% reliable.
1243 Film::should_be_enough_disk_space (double& required, double& available, bool& can_hard_link) const
1245 /* Create a test file and see if we can hard-link it */
1246 boost::filesystem::path test = internal_video_asset_dir() / "test";
1247 boost::filesystem::path test2 = internal_video_asset_dir() / "test2";
1248 can_hard_link = true;
1249 FILE* f = fopen_boost (test, "w");
1252 boost::system::error_code ec;
1253 boost::filesystem::create_hard_link (test, test2, ec);
1255 can_hard_link = false;
1257 boost::filesystem::remove (test);
1258 boost::filesystem::remove (test2);
1261 boost::filesystem::space_info s = boost::filesystem::space (internal_video_asset_dir ());
1262 required = double (required_disk_space ()) / 1073741824.0f;
1263 if (!can_hard_link) {
1266 available = double (s.available) / 1073741824.0f;
1267 return (available - required) > 1;
1271 Film::subtitle_language () const
1273 set<string> languages;
1275 ContentList cl = content ();
1276 BOOST_FOREACH (shared_ptr<Content>& c, cl) {
1278 languages.insert (c->subtitle->language ());
1283 BOOST_FOREACH (string s, languages) {
1284 if (!all.empty ()) {
1294 /** Change the gains of the supplied AudioMapping to make it a default
1295 * for this film. The defaults are guessed based on what processor (if any)
1296 * is in use, the number of input channels and any filename supplied.
1299 Film::make_audio_mapping_default (AudioMapping& mapping, optional<boost::filesystem::path> filename) const
1301 static string const regex[] = {
1302 ".*[\\._-]L[\\._-].*",
1303 ".*[\\._-]R[\\._-].*",
1304 ".*[\\._-]C[\\._-].*",
1305 ".*[\\._-]Lfe[\\._-].*",
1306 ".*[\\._-]Ls[\\._-].*",
1307 ".*[\\._-]Rs[\\._-].*"
1310 static int const regexes = sizeof(regex) / sizeof(*regex);
1312 if (audio_processor ()) {
1313 audio_processor()->make_audio_mapping_default (mapping);
1315 mapping.make_zero ();
1316 if (mapping.input_channels() == 1) {
1317 bool guessed = false;
1319 /* See if we can guess where this stream should go */
1321 for (int i = 0; i < regexes; ++i) {
1322 boost::regex e (regex[i], boost::regex::icase);
1323 if (boost::regex_match (filename->string(), e) && i < mapping.output_channels()) {
1324 mapping.set (0, i, 1);
1331 /* If we have no idea, just put it on centre */
1332 mapping.set (0, static_cast<int> (dcp::CENTRE), 1);
1336 for (int i = 0; i < min (mapping.input_channels(), mapping.output_channels()); ++i) {
1337 mapping.set (i, i, 1);
1343 /** @return The names of the channels that audio contents' outputs are passed into;
1344 * this is either the DCP or a AudioProcessor.
1347 Film::audio_output_names () const
1349 if (audio_processor ()) {
1350 return audio_processor()->input_names ();
1353 DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
1356 n.push_back (_("L"));
1357 n.push_back (_("R"));
1358 n.push_back (_("C"));
1359 n.push_back (_("Lfe"));
1360 n.push_back (_("Ls"));
1361 n.push_back (_("Rs"));
1362 n.push_back (_("HI"));
1363 n.push_back (_("VI"));
1364 n.push_back (_("Lc"));
1365 n.push_back (_("Rc"));
1366 n.push_back (_("BsL"));
1367 n.push_back (_("BsR"));
1368 n.push_back (_("DBP"));
1369 n.push_back (_("DBS"));
1373 return vector<string> (n.begin(), n.begin() + audio_channels ());
1377 Film::repeat_content (ContentList c, int n)
1379 _playlist->repeat (c, n);
1383 Film::remove_content (ContentList c)
1385 _playlist->remove (c);
1389 Film::audio_analysis_finished ()
1395 Film::reels () const
1397 list<DCPTimePeriod> p;
1398 DCPTime const len = length().round_up (video_frame_rate ());
1400 switch (reel_type ()) {
1401 case REELTYPE_SINGLE:
1402 p.push_back (DCPTimePeriod (DCPTime (), len));
1404 case REELTYPE_BY_VIDEO_CONTENT:
1406 optional<DCPTime> last_split;
1407 shared_ptr<Content> last_video;
1408 BOOST_FOREACH (shared_ptr<Content> c, content ()) {
1410 BOOST_FOREACH (DCPTime t, c->reel_split_points()) {
1412 p.push_back (DCPTimePeriod (last_split.get(), t));
1420 DCPTime video_end = last_video ? last_video->end() : DCPTime(0);
1422 /* Definitely go from the last split to the end of the video content */
1423 p.push_back (DCPTimePeriod (last_split.get(), video_end));
1426 if (video_end < len) {
1427 /* And maybe go after that as well if there is any non-video hanging over the end */
1428 p.push_back (DCPTimePeriod (video_end, len));
1432 case REELTYPE_BY_LENGTH:
1435 /* Integer-divide reel length by the size of one frame to give the number of frames per reel */
1436 Frame const reel_in_frames = _reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8);
1437 while (current < len) {
1438 DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ()));
1439 p.push_back (DCPTimePeriod (current, end));
1450 Film::content_summary (DCPTimePeriod period) const
1452 return _playlist->content_summary (period);
1456 Film::fix_conflicting_settings ()
1460 list<boost::filesystem::path> was_referencing;
1461 BOOST_FOREACH (shared_ptr<Content> i, content()) {
1462 shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
1464 list<string> reasons;
1466 if (!d->can_reference_video(reasons) && d->reference_video()) {
1467 d->set_reference_video (false);
1470 if (!d->can_reference_audio(reasons) && d->reference_audio()) {
1471 d->set_reference_audio (false);
1474 if (!d->can_reference_subtitle(reasons) && d->reference_subtitle()) {
1475 d->set_reference_subtitle (false);
1479 was_referencing.push_back (d->path(0).parent_path().filename());
1484 BOOST_FOREACH (boost::filesystem::path d, was_referencing) {
1485 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()));