Cleanup: make a temporary for the content list.
[dcpomatic.git] / src / lib / film.cc
index 718d6c61db1d6281f4f33c96a80bdd29cbe8b9a4..0ce56a4362821a7316ee91e38a089c9d8c995c86 100644 (file)
 #include "audio_content.h"
 #include "audio_processor.h"
 #include "change_signaller.h"
-#include "check_content_change_job.h"
 #include "cinema.h"
 #include "compose.hpp"
 #include "config.h"
+#include "constants.h"
 #include "cross.h"
 #include "dcp_content.h"
 #include "dcp_content_type.h"
@@ -57,7 +57,6 @@
 #include "text_content.h"
 #include "transcode_job.h"
 #include "upload_job.h"
-#include "util.h"
 #include "video_content.h"
 #include "version.h"
 #include <libcxml/cxml.h>
@@ -157,7 +156,7 @@ Film::Film (optional<boost::filesystem::path> dir)
        : _playlist (new Playlist)
        , _use_isdcf_name (Config::instance()->use_isdcf_name_by_default())
        , _dcp_content_type (Config::instance()->default_dcp_content_type ())
-       , _container (Config::instance()->default_container ())
+       , _container(Ratio::from_id("185"))
        , _resolution (Resolution::TWO_K)
        , _encrypted (false)
        , _context_id (dcp::make_uuid ())
@@ -175,8 +174,10 @@ Film::Film (optional<boost::filesystem::path> dir)
        , _user_explicit_container (false)
        , _user_explicit_resolution (false)
        , _name_language (dcp::LanguageTag("en-US"))
+       , _release_territory(Config::instance()->default_territory())
        , _version_number (1)
        , _status (dcp::Status::FINAL)
+       , _audio_language(Config::instance()->default_audio_language())
        , _state_version (current_state_version)
        , _dirty (false)
        , _tolerant (false)
@@ -377,69 +378,6 @@ Film::subtitle_analysis_path (shared_ptr<const Content> content) const
 }
 
 
-/** Add suitable Jobs to the JobManager to create a DCP for this Film */
-void
-Film::make_dcp (TranscodeJob::ChangedBehaviour behaviour)
-{
-       if (dcp_name().find ("/") != string::npos) {
-               throw BadSettingError (_("name"), _("Cannot contain slashes"));
-       }
-
-       if (container() == nullptr) {
-               throw MissingSettingError (_("container"));
-       }
-
-       if (content().empty()) {
-               throw runtime_error (_("You must add some content to the DCP before creating it"));
-       }
-
-       if (length() == DCPTime()) {
-               throw runtime_error (_("The DCP is empty, perhaps because all the content has zero length."));
-       }
-
-       if (dcp_content_type() == nullptr) {
-               throw MissingSettingError (_("content type"));
-       }
-
-       if (name().empty()) {
-               set_name ("DCP");
-       }
-
-       for (auto i: content ()) {
-               if (!i->paths_valid()) {
-                       throw runtime_error (_("some of your content is missing"));
-               }
-               auto dcp = dynamic_pointer_cast<const DCPContent>(i);
-               if (dcp && dcp->needs_kdm()) {
-                       throw runtime_error (_("Some of your content needs a KDM"));
-               }
-               if (dcp && dcp->needs_assets()) {
-                       throw runtime_error (_("Some of your content needs an OV"));
-               }
-       }
-
-       set_isdcf_date_today ();
-
-       for (auto i: environment_info ()) {
-               LOG_GENERAL_NC (i);
-       }
-
-       for (auto i: content ()) {
-               LOG_GENERAL ("Content: %1", i->technical_summary());
-       }
-       LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
-       if (Config::instance()->only_servers_encode ()) {
-               LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
-       } else {
-               LOG_GENERAL ("%1 threads", Config::instance()->master_encoding_threads());
-       }
-       LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
-
-       auto tj = make_shared<TranscodeJob>(shared_from_this(), behaviour);
-       tj->set_encoder (make_shared<DCPEncoder>(shared_from_this(), tj));
-       JobManager::instance()->add (tj);
-}
-
 /** Start a job to send our DCP to the configured TMS */
 void
 Film::send_dcp_to_tms ()
@@ -471,6 +409,7 @@ Film::metadata (bool with_content_paths) const
        root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution));
        root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
        root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
+       root->add_child("AudioFrameRate")->add_child_text(raw_convert<string>(_audio_frame_rate));
        root->add_child("ISDCFDate")->add_child_text (boost::gregorian::to_iso_string (_isdcf_date));
        root->add_child("AudioChannels")->add_child_text (raw_convert<string> (_audio_channels));
        root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
@@ -544,12 +483,12 @@ Film::write_metadata (boost::filesystem::path path) const
 
 /** Write state to our `metadata' file */
 void
-Film::write_metadata () const
+Film::write_metadata ()
 {
        DCPOMATIC_ASSERT (directory());
        boost::filesystem::create_directories (directory().get());
        metadata()->write_to_file_formatted(file(metadata_file).string());
-       _dirty = false;
+       set_dirty (false);
 }
 
 /** Write a template from this film */
@@ -597,6 +536,8 @@ Film::read_metadata (optional<boost::filesystem::path> path)
                }
        }
 
+       _last_written_by = f.optional_string_child("LastWrittenBy");
+
        _name = f.string_child ("Name");
        if (_state_version >= 9) {
                _use_isdcf_name = f.bool_child ("UseISDCFName");
@@ -624,6 +565,7 @@ Film::read_metadata (optional<boost::filesystem::path> path)
        _resolution = string_to_resolution (f.string_child ("Resolution"));
        _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
        _video_frame_rate = f.number_child<int> ("VideoFrameRate");
+       _audio_frame_rate = f.optional_number_child<int>("AudioFrameRate").get_value_or(48000);
        _encrypted = f.bool_child ("Encrypted");
        _audio_channels = f.number_child<int> ("AudioChannels");
        /* We used to allow odd numbers (and zero) channels, but it's just not worth
@@ -690,7 +632,7 @@ Film::read_metadata (optional<boost::filesystem::path> path)
                _sign_language_video_language = dcp::LanguageTag(*sign_language_video_language);
        }
 
-       _version_number = f.optional_number_child<int>("VersionNumber").get_value_or(0);
+       _version_number = f.optional_number_child<int>("VersionNumber").get_value_or(1);
 
        auto status = f.optional_string_child("Status");
        if (status) {
@@ -766,7 +708,7 @@ Film::read_metadata (optional<boost::filesystem::path> path)
                set_backtrace_file (file ("backtrace.txt"));
        }
 
-       _dirty = false;
+       set_dirty (false);
        return notes;
 }
 
@@ -863,10 +805,15 @@ Film::subtitle_languages () const
 string
 Film::isdcf_name (bool if_created_now) const
 {
-       string d;
+       string isdcf_name;
 
        auto raw_name = name ();
 
+       auto to_upper = [](string s) {
+               transform(s.begin(), s.end(), s.begin(), ::toupper);
+               return s;
+       };
+
        /* Split the raw name up into words */
        vector<string> words;
        split (words, raw_name, is_any_of (" _-"));
@@ -902,14 +849,12 @@ Film::isdcf_name (bool if_created_now) const
                }
        }
 
-       if (fixed_name.length() > 14) {
-               fixed_name = fixed_name.substr (0, 14);
-       }
+       fixed_name = fixed_name.substr(0, 14);
 
-       d += fixed_name;
+       isdcf_name += fixed_name;
 
        if (dcp_content_type()) {
-               d += "_" + dcp_content_type()->isdcf_name();
+               isdcf_name += "_" + dcp_content_type()->isdcf_name();
                string version = "1";
                if (_interop) {
                        if (!_content_versions.empty()) {
@@ -921,66 +866,78 @@ Film::isdcf_name (bool if_created_now) const
                } else {
                        version = dcp::raw_convert<string>(_version_number);
                }
-               d += "-" + version;
+               isdcf_name += "-" + version;
        }
 
        if (_temp_version) {
-               d += "-Temp";
+               isdcf_name += "-Temp";
        }
 
        if (_pre_release) {
-               d += "-Pre";
+               isdcf_name += "-Pre";
        }
 
        if (_red_band) {
-               d += "-RedBand";
+               isdcf_name += "-RedBand";
        }
 
        if (_chain && !_chain->empty()) {
-               d += "-" + *_chain;
+               isdcf_name += "-" + *_chain;
        }
 
        if (three_d ()) {
-               d += "-3D";
+               isdcf_name += "-3D";
        }
 
        if (_two_d_version_of_three_d) {
-               d += "-2D";
+               isdcf_name += "-2D";
        }
 
        if (_luminance) {
                auto fl = _luminance->value_in_foot_lamberts();
                char buffer[64];
                snprintf (buffer, sizeof(buffer), "%.1f", fl);
-               d += String::compose("-%1fl", buffer);
+               isdcf_name += String::compose("-%1fl", buffer);
        }
 
        if (video_frame_rate() != 24) {
-               d += "-" + raw_convert<string>(video_frame_rate());
+               isdcf_name += "-" + raw_convert<string>(video_frame_rate());
        }
 
        if (container()) {
-               d += "_" + container()->isdcf_name();
+               isdcf_name += "_" + container()->isdcf_name();
        }
 
+       auto content_list = content();
+
        /* XXX: this uses the first bit of content only */
 
        /* Interior aspect ratio.  The standard says we don't do this for trailers, for some strange reason */
        if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::ContentKind::TRAILER) {
-               auto cl = content();
-               auto first_video = std::find_if(cl.begin(), cl.end(), [](shared_ptr<Content> c) { return static_cast<bool>(c->video); });
-               if (first_video != cl.end()) {
+               auto first_video = std::find_if(content_list.begin(), content_list.end(), [](shared_ptr<Content> c) { return static_cast<bool>(c->video); });
+               if (first_video != content_list.end()) {
                        auto first_ratio = lrintf((*first_video)->video->scaled_size(frame_size()).ratio() * 100);
                        auto container_ratio = lrintf(container()->ratio() * 100);
                        if (first_ratio != container_ratio) {
-                               d += "-" + dcp::raw_convert<string>(first_ratio);
+                               isdcf_name += "-" + dcp::raw_convert<string>(first_ratio);
                        }
                }
        }
 
-       auto audio_language = (_audio_language && _audio_language->language()) ? _audio_language->language()->subtag() : "XX";
+       auto entry_for_language = [](dcp::LanguageTag const& tag) {
+               /* Look up what we should be using for this tag in the DCNC name */
+               for (auto const& dcnc: dcp::dcnc_tags()) {
+                       if (tag.to_string() == dcnc.first) {
+                               return dcnc.second;
+                       }
+               }
+               /* Fallback to the language subtag, if there is one */
+               return tag.language() ? tag.language()->subtag() : "XX";
+       };
+
+       auto audio_language = _audio_language ? entry_for_language(*_audio_language) : "XX";
 
-       d += "_" + to_upper (audio_language);
+       isdcf_name += "_" + to_upper (audio_language);
 
        /* I'm not clear on the precise details of the convention for CCAP labelling;
           for now I'm just appending -CCAP if we have any closed captions.
@@ -988,7 +945,7 @@ Film::isdcf_name (bool if_created_now) const
 
        auto burnt_in = true;
        auto ccap = false;
-       for (auto i: content()) {
+       for (auto i: content_list) {
                for (auto j: i->text) {
                        if (j->type() == TextType::OPEN_SUBTITLE && j->use() && !j->burn()) {
                                burnt_in = false;
@@ -1000,29 +957,30 @@ Film::isdcf_name (bool if_created_now) const
 
        auto sub_langs = subtitle_languages();
        if (sub_langs.first && sub_langs.first->language()) {
-               auto lang = sub_langs.first->language()->subtag();
+               auto lang = entry_for_language(*sub_langs.first);
                if (burnt_in) {
                        transform (lang.begin(), lang.end(), lang.begin(), ::tolower);
                } else {
                        lang = to_upper (lang);
                }
 
-               d += "-" + lang;
+               isdcf_name += "-" + lang;
                if (ccap) {
-                       d += "-CCAP";
+                       isdcf_name += "-CCAP";
                }
        } else {
                /* No subtitles */
-               d += "-XX";
+               isdcf_name += "-XX";
        }
 
        if (_release_territory) {
                auto territory = _release_territory->subtag();
-               d += "_" + to_upper (territory);
-               if (_ratings.empty ()) {
-                       d += "-NR";
-               } else {
-                       d += "-" + _ratings[0].label;
+               isdcf_name += "_" + to_upper (territory);
+               if (!_ratings.empty()) {
+                       auto label = _ratings[0].label;
+                       boost::erase_all(label, "+");
+                       boost::erase_all(label, "-");
+                       isdcf_name += "-" + label;
                }
        }
 
@@ -1032,46 +990,46 @@ Film::isdcf_name (bool if_created_now) const
 
        auto ch = audio_channel_types (mapped, audio_channels());
        if (!ch.first && !ch.second) {
-               d += "_MOS";
+               isdcf_name += "_MOS";
        } else if (ch.first) {
-               d += String::compose("_%1%2", ch.first, ch.second);
+               isdcf_name += String::compose("_%1%2", ch.first, ch.second);
        }
 
        if (audio_channels() > static_cast<int>(dcp::Channel::HI) && find(mapped.begin(), mapped.end(), static_cast<int>(dcp::Channel::HI)) != mapped.end()) {
-               d += "-HI";
+               isdcf_name += "-HI";
        }
        if (audio_channels() > static_cast<int>(dcp::Channel::VI) && find(mapped.begin(), mapped.end(), static_cast<int>(dcp::Channel::VI)) != mapped.end()) {
-               d += "-VI";
+               isdcf_name += "-VI";
        }
 
-       d += "_" + resolution_to_string (_resolution);
+       isdcf_name += "_" + resolution_to_string (_resolution);
 
        if (_studio && _studio->length() >= 2) {
-               d += "_" + to_upper (_studio->substr(0, 4));
+               isdcf_name += "_" + to_upper (_studio->substr(0, 4));
        }
 
        if (if_created_now) {
-               d += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
+               isdcf_name += "_" + boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ());
        } else {
-               d += "_" + boost::gregorian::to_iso_string (_isdcf_date);
+               isdcf_name += "_" + boost::gregorian::to_iso_string (_isdcf_date);
        }
 
        if (_facility && _facility->length() >= 3) {
-               d += "_" + to_upper(_facility->substr(0, 3));
+               isdcf_name += "_" + to_upper(_facility->substr(0, 3));
        }
 
        if (_interop) {
-               d += "_IOP";
+               isdcf_name += "_IOP";
        } else {
-               d += "_SMPTE";
+               isdcf_name += "_SMPTE";
        }
 
        if (three_d ()) {
-               d += "-3D";
+               isdcf_name += "-3D";
        }
 
        auto vf = false;
-       for (auto i: content()) {
+       for (auto i: content_list) {
                auto dc = dynamic_pointer_cast<const DCPContent>(i);
                if (!dc) {
                        continue;
@@ -1089,12 +1047,12 @@ Film::isdcf_name (bool if_created_now) const
        }
 
        if (vf) {
-               d += "_VF";
+               isdcf_name += "_VF";
        } else {
-               d += "_OV";
+               isdcf_name += "_OV";
        }
 
-       return d;
+       return isdcf_name;
 }
 
 /** @return name to give the DCP */
@@ -1113,7 +1071,7 @@ void
 Film::set_directory (boost::filesystem::path d)
 {
        _directory = d;
-       _dirty = true;
+       set_dirty (true);
 }
 
 void
@@ -1256,7 +1214,7 @@ void
 Film::signal_change (ChangeType type, Property p)
 {
        if (type == ChangeType::DONE) {
-               _dirty = true;
+               set_dirty (true);
 
                if (p == Property::CONTENT) {
                        if (!_user_explicit_video_frame_rate) {
@@ -1536,7 +1494,7 @@ Film::playlist_content_change (ChangeType type, weak_ptr<Content> c, int p, bool
                ContentChange (type, c, p, frequent);
        }
 
-       _dirty = true;
+       set_dirty (true);
 }
 
 void
@@ -1555,7 +1513,7 @@ Film::playlist_change (ChangeType type)
                check_settings_consistency ();
        }
 
-       _dirty = true;
+       set_dirty (true);
 }
 
 /** Check for (and if necessary fix) impossible settings combinations, like
@@ -1617,14 +1575,6 @@ Film::playlist_order_changed ()
        signal_change (ChangeType::DONE, Property::CONTENT_ORDER);
 }
 
-int
-Film::audio_frame_rate () const
-{
-       /* It seems that nobody makes 96kHz DCPs at the moment, so let's avoid them.
-          See #1436.
-       */
-       return 48000;
-}
 
 void
 Film::set_sequence (bool s)
@@ -1776,9 +1726,9 @@ Film::should_be_enough_disk_space (double& required, double& available, bool& ca
        boost::filesystem::path test = internal_video_asset_dir() / "test";
        boost::filesystem::path test2 = internal_video_asset_dir() / "test2";
        can_hard_link = true;
-       auto f = fopen_boost (test, "w");
+       dcp::File f(test, "w");
        if (f) {
-               fclose (f);
+               f.close();
                boost::system::error_code ec;
                boost::filesystem::create_hard_link (test, test2, ec);
                if (ec) {
@@ -1812,7 +1762,7 @@ Film::audio_output_names () const
        vector<NamedChannel> n;
 
        for (int i = 0; i < audio_channels(); ++i) {
-               if (i != 8 && i != 9 && i != 15) {
+               if (Config::instance()->use_all_audio_channels() || (i != 8 && i != 9 && i != 15)) {
                        n.push_back (NamedChannel(short_audio_channel_name(i), i));
                }
        }
@@ -2129,34 +2079,15 @@ Film::info_file_handle (DCPTimePeriod period, bool read) const
        return std::make_shared<InfoFileHandle>(_info_file_mutex, info_file(period), read);
 }
 
-InfoFileHandle::InfoFileHandle (boost::mutex& mutex, boost::filesystem::path file, bool read)
+InfoFileHandle::InfoFileHandle (boost::mutex& mutex, boost::filesystem::path path, bool read)
        : _lock (mutex)
-       , _file (file)
+       , _file (path, read ? "rb" : (boost::filesystem::exists(path) ? "r+b" : "wb"))
 {
-       if (read) {
-               _handle = fopen_boost (file, "rb");
-               if (!_handle) {
-                       throw OpenFileError (file, errno, OpenFileError::READ);
-               }
-       } else {
-               auto const exists = boost::filesystem::exists (file);
-               if (exists) {
-                       _handle = fopen_boost (file, "r+b");
-               } else {
-                       _handle = fopen_boost (file, "wb");
-               }
-
-               if (!_handle) {
-                       throw OpenFileError (file, errno, exists ? OpenFileError::READ_WRITE : OpenFileError::WRITE);
-               }
+       if (!_file) {
+               throw OpenFileError (path, errno, read ? OpenFileError::READ : (boost::filesystem::exists(path) ? OpenFileError::READ_WRITE : OpenFileError::WRITE));
        }
 }
 
-InfoFileHandle::~InfoFileHandle ()
-{
-       fclose (_handle);
-}
-
 
 /** Add FFOC and LFOC markers to a list if they are not already there */
 void
@@ -2212,6 +2143,14 @@ Film::set_audio_language (optional<dcp::LanguageTag> language)
 }
 
 
+void
+Film::set_audio_frame_rate (int rate)
+{
+       FilmChangeSignaller ch (this, Property::AUDIO_FRAME_RATE);
+       _audio_frame_rate = rate;
+}
+
+
 bool
 Film::has_sign_language_video_channel () const
 {
@@ -2226,3 +2165,52 @@ Film::set_sign_language_video_language (optional<dcp::LanguageTag> lang)
        _sign_language_video_language = lang;
 }
 
+
+void
+Film::set_dirty (bool dirty)
+{
+       auto const changed = dirty != _dirty;
+       _dirty = dirty;
+       if (changed) {
+               emit (boost::bind(boost::ref(DirtyChange), _dirty));
+       }
+}
+
+
+/** @return true if the metadata was (probably) last written by a version earlier
+ *  than the given one; false if it definitely was not.
+ */
+bool
+Film::last_written_by_earlier_than(int major, int minor, int micro) const
+{
+       if (!_last_written_by) {
+               return true;
+       }
+
+       vector<string> parts;
+       boost::split(parts, *_last_written_by, boost::is_any_of("."));
+
+       if (parts.size() != 3) {
+               /* Not sure what's going on, so let's say it was written by an old version */
+               return true;
+       }
+
+       if (boost::ends_with(parts[2], "pre")) {
+               parts[2] = parts[2].substr(0, parts[2].length() - 3);
+       }
+
+       int our_major = dcp::raw_convert<int>(parts[0]);
+       int our_minor = dcp::raw_convert<int>(parts[1]);
+       int our_micro = dcp::raw_convert<int>(parts[2]);
+
+       if (our_major != major) {
+               return our_major < major;
+       }
+
+       if (our_minor != minor) {
+               return our_minor < minor;
+       }
+
+       return our_micro < micro;
+}
+