Remove default DCP audio channel setting.
[dcpomatic.git] / src / lib / config.cc
index c80ef224e922391c142014fe909f3e5b08732030..5401839e37743fbe88551e4b5386b852a17f3f6f 100644 (file)
 */
 
 
-#include "cinema.h"
+#include "cinema_list.h"
 #include "colour_conversion.h"
 #include "compose.hpp"
 #include "config.h"
 #include "constants.h"
 #include "cross.h"
 #include "dcp_content_type.h"
-#include "dkdm_recipient.h"
+#include "dkdm_recipient_list.h"
 #include "dkdm_wrapper.h"
 #include "film.h"
 #include "filter.h"
 #include "log.h"
 #include "ratio.h"
 #include "unzipper.h"
+#include "variant.h"
 #include "zipper.h"
 #include <dcp/certificate_chain.h>
 #include <dcp/name_format.h>
@@ -107,7 +108,8 @@ Config::set_defaults ()
        _default_still_length = 10;
        _default_dcp_content_type = DCPContentType::from_isdcf_name ("FTR");
        _default_dcp_audio_channels = 8;
-       _default_j2k_bandwidth = 150000000;
+       _default_video_bit_rate[VideoEncoding::JPEG2000] = 150000000;
+       _default_video_bit_rate[VideoEncoding::MPEG2] = 5000000;
        _default_audio_delay = 0;
        _default_interop = false;
        _default_metadata.clear ();
@@ -126,7 +128,8 @@ Config::set_defaults ()
        _notification_bcc = "";
        _check_for_updates = false;
        _check_for_test_updates = false;
-       _maximum_j2k_bandwidth = 250000000;
+       _maximum_video_bit_rate[VideoEncoding::JPEG2000] = 250000000;
+       _maximum_video_bit_rate[VideoEncoding::MPEG2] = 50000000;
        _log_types = LogEntry::TYPE_GENERAL | LogEntry::TYPE_WARNING | LogEntry::TYPE_ERROR | LogEntry::TYPE_DISK;
        _analyse_ebur128 = true;
        _automatic_audio_analysis = false;
@@ -136,8 +139,8 @@ Config::set_defaults ()
        /* At the moment we don't write these files anywhere new after a version change, so they will be read from
         * ~/.config/dcpomatic2 (or equivalent) and written back there.
         */
-       _cinemas_file = read_path ("cinemas.xml");
-       _dkdm_recipients_file = read_path ("dkdm_recipients.xml");
+       _cinemas_file = read_path("cinemas.sqlite3");
+       _dkdm_recipients_file = read_path("dkdm_recipients.sqlite3");
        _show_hints_before_make_dcp = true;
        _confirm_kdm_email = true;
        _kdm_container_name_format = dcp::NameFormat("KDM_%f_%c");
@@ -200,6 +203,7 @@ Config::set_defaults ()
        _initial_paths["CinemaDatabasePath"] = boost::none;
        _initial_paths["ConfigFilePath"] = boost::none;
        _initial_paths["Preferences"] = boost::none;
+       _initial_paths["SaveVerificationReport"] = boost::none;
        _use_isdcf_name_by_default = true;
        _write_kdms_to_disk = true;
        _email_kdms = false;
@@ -272,7 +276,7 @@ Config::backup ()
                copy_file(path_to_copy, add_number(path_to_copy, n), ec);
        };
 
-       /* Make a backup copy of any config.xml, cinemas.xml, dkdm_recipients.xml that we might be about
+       /* Make a backup copy of any config.xml, cinemas.sqlite3, dkdm_recipients.sqlite3 that we might be about
         * to write over.  This is more intended for the situation where we have a corrupted config.xml,
         * and decide to overwrite it with a new one (possibly losing important details in the corrupted
         * file).  But we might as well back up the other files while we're about it.
@@ -292,15 +296,6 @@ Config::backup ()
 
 void
 Config::read ()
-{
-       read_config();
-       read_cinemas();
-       read_dkdm_recipients();
-}
-
-
-void
-Config::read_config()
 try
 {
        cxml::Document f ("Config");
@@ -373,7 +368,12 @@ try
        _dcp_j2k_comment = f.optional_string_child("DCPJ2KComment").get_value_or("");
 
        _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
-       _default_j2k_bandwidth = f.optional_number_child<int>("DefaultJ2KBandwidth").get_value_or (200000000);
+       if (auto j2k = f.optional_number_child<int>("DefaultJ2KBandwidth")) {
+               _default_video_bit_rate[VideoEncoding::JPEG2000] = *j2k;
+       } else {
+               _default_video_bit_rate[VideoEncoding::JPEG2000] = f.optional_number_child<int64_t>("DefaultJ2KVideoBitRate").get_value_or(200000000);
+       }
+       _default_video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("DefaultMPEG2VideoBitRate").get_value_or(5000000);
        _default_audio_delay = f.optional_number_child<int>("DefaultAudioDelay").get_value_or (0);
        _default_interop = f.optional_bool_child("DefaultInterop").get_value_or (false);
 
@@ -397,11 +397,6 @@ try
 
        _default_kdm_directory = f.optional_string_child("DefaultKDMDirectory");
 
-       /* Read any cinemas that are still lying around in the config file
-        * from an old version.
-        */
-       read_cinemas (f);
-
        _mail_server = f.string_child ("MailServer");
        _mail_port = f.optional_number_child<int> ("MailPort").get_value_or (25);
 
@@ -432,7 +427,7 @@ try
        _kdm_bcc = f.optional_string_child ("KDMBCC").get_value_or ("");
        _kdm_email = f.string_child ("KDMEmail");
 
-       _notification_subject = f.optional_string_child("NotificationSubject").get_value_or(_("DCP-o-matic notification"));
+       _notification_subject = f.optional_string_child("NotificationSubject").get_value_or(variant::insert_dcpomatic(_("%1 notification")));
        _notification_from = f.optional_string_child("NotificationFrom").get_value_or("");
        _notification_to = f.optional_string_child("NotificationTo").get_value_or("");
        for (auto i: f.node_children("NotificationCC")) {
@@ -448,7 +443,12 @@ try
        _check_for_updates = f.optional_bool_child("CheckForUpdates").get_value_or (false);
        _check_for_test_updates = f.optional_bool_child("CheckForTestUpdates").get_value_or (false);
 
-       _maximum_j2k_bandwidth = f.optional_number_child<int> ("MaximumJ2KBandwidth").get_value_or (250000000);
+       if (auto j2k = f.optional_number_child<int>("MaximumJ2KBandwidth")) {
+               _maximum_video_bit_rate[VideoEncoding::JPEG2000] = *j2k;
+       } else {
+               _maximum_video_bit_rate[VideoEncoding::JPEG2000] = f.optional_number_child<int64_t>("MaximumJ2KVideoBitRate").get_value_or(250000000);
+       }
+       _maximum_video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("MaximumMPEG2VideoBitRate").get_value_or(50000000);
        _allow_any_dcp_frame_rate = f.optional_bool_child ("AllowAnyDCPFrameRate").get_value_or (false);
        _allow_any_container = f.optional_bool_child ("AllowAnyContainer").get_value_or (false);
        _allow_96khz_audio = f.optional_bool_child("Allow96kHzAudio").get_value_or(false);
@@ -534,8 +534,8 @@ try
                        _dkdms->add (DKDMBase::read (i));
                }
        }
-       _cinemas_file = f.optional_string_child("CinemasFile").get_value_or(read_path("cinemas.xml").string());
-       _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(read_path("dkdm_recipients.xml").string());
+       _cinemas_file = f.optional_string_child("CinemasFile").get_value_or(read_path("cinemas.sqlite3").string());
+       _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(read_path("dkdm_recipients.sqlite3").string());
        _show_hints_before_make_dcp = f.optional_bool_child("ShowHintsBeforeMakeDCP").get_value_or (true);
        _confirm_kdm_email = f.optional_bool_child("ConfirmKDMEmail").get_value_or (true);
        _kdm_container_name_format = dcp::NameFormat (f.optional_string_child("KDMContainerNameFormat").get_value_or ("KDM %f %c"));
@@ -674,40 +674,6 @@ catch (...) {
 }
 
 
-void
-Config::read_cinemas()
-{
-       if (dcp::filesystem::exists(_cinemas_file)) {
-               try {
-                       cxml::Document f("Cinemas");
-                       f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
-                       read_cinemas(f);
-               } catch (...) {
-                       backup();
-                       FailedToLoad(LoadFailure::CINEMAS);
-                       write_cinemas();
-               }
-       }
-}
-
-
-void
-Config::read_dkdm_recipients()
-{
-       if (dcp::filesystem::exists(_dkdm_recipients_file)) {
-               try {
-                       cxml::Document f("DKDMRecipients");
-                       f.read_file(dcp::filesystem::fix_long_path(_dkdm_recipients_file));
-                       read_dkdm_recipients(f);
-               } catch (...) {
-                       backup();
-                       FailedToLoad(LoadFailure::DKDM_RECIPIENTS);
-                       write_dkdm_recipients();
-               }
-       }
-}
-
-
 /** @return Singleton instance */
 Config *
 Config::instance ()
@@ -715,6 +681,32 @@ Config::instance ()
        if (_instance == nullptr) {
                _instance = new Config;
                _instance->read ();
+
+               auto cinemas_file = _instance->cinemas_file();
+               if (cinemas_file.extension() == ".xml") {
+                       auto sqlite = cinemas_file;
+                       sqlite.replace_extension(".sqlite3");
+                       bool const had_sqlite = dcp::filesystem::exists(sqlite);
+
+                       _instance->set_cinemas_file(sqlite);
+
+                       if (dcp::filesystem::exists(cinemas_file) && !had_sqlite) {
+                               CinemaList cinemas;
+                               cinemas.read_legacy_file(cinemas_file);
+                       }
+               }
+
+               auto dkdm_recipients_file = _instance->dkdm_recipients_file();
+               if (dkdm_recipients_file.extension() == ".xml") {
+                       auto sqlite = dkdm_recipients_file;
+                       sqlite.replace_extension(".sqlite3");
+
+                       if (dcp::filesystem::exists(dkdm_recipients_file) && !dcp::filesystem::exists(sqlite)) {
+                               _instance->set_dkdm_recipients_file(sqlite);
+                               DKDMRecipientList recipients;
+                               recipients.read_legacy_file(dkdm_recipients_file);
+                       }
+               }
        }
 
        return _instance;
@@ -725,8 +717,6 @@ void
 Config::write () const
 {
        write_config ();
-       write_cinemas ();
-       write_dkdm_recipients ();
 }
 
 void
@@ -780,14 +770,6 @@ Config::write_config () const
                /* [XML:opt] Language Language to use in the GUI e.g. <code>fr_FR</code>. */
                cxml::add_text_child(root, "Language", _language.get());
        }
-       if (_default_dcp_content_type) {
-               /* [XML:opt] DefaultDCPContentType Default content type to use when creating new films (<code>FTR</code>, <code>SHR</code>,
-                  <code>TLR</code>, <code>TST</code>, <code>XSN</code>, <code>RTG</code>, <code>TSR</code>, <code>POL</code>,
-                  <code>PSA</code> or <code>ADV</code>). */
-               cxml::add_text_child(root, "DefaultDCPContentType", _default_dcp_content_type->isdcf_name());
-       }
-       /* [XML] DefaultDCPAudioChannels Default number of audio channels to use when creating new films. */
-       cxml::add_text_child(root, "DefaultDCPAudioChannels", raw_convert<string>(_default_dcp_audio_channels));
        /* [XML] DCPIssuer Issuer text to write into CPL files. */
        cxml::add_text_child(root, "DCPIssuer", _dcp_issuer);
        /* [XML] DCPCreator Creator text to write into CPL files. */
@@ -805,8 +787,10 @@ Config::write_config () const
 
        /* [XML] DefaultStillLength Default length (in seconds) for still images in new films. */
        cxml::add_text_child(root, "DefaultStillLength", raw_convert<string>(_default_still_length));
-       /* [XML] DefaultJ2KBandwidth Default bitrate (in bits per second) for JPEG2000 data in new films. */
-       cxml::add_text_child(root, "DefaultJ2KBandwidth", raw_convert<string>(_default_j2k_bandwidth));
+       /* [XML] DefaultJ2KVideoBitRate Default bitrate (in bits per second) for JPEG2000 data in new films. */
+       cxml::add_text_child(root, "DefaultJ2KVideoBitRate", raw_convert<string>(_default_video_bit_rate[VideoEncoding::JPEG2000]));
+       /* [XML] DefaultMPEG2VideoBitRate Default bitrate (in bits per second) for MPEG2 data in new films. */
+       cxml::add_text_child(root, "DefaultMPEG2VideoBitRate", raw_convert<string>(_default_video_bit_rate[VideoEncoding::MPEG2]));
        /* [XML] DefaultAudioDelay Default delay to apply to audio (positive moves audio later) in milliseconds. */
        cxml::add_text_child(root, "DefaultAudioDelay", raw_convert<string>(_default_audio_delay));
        /* [XML] DefaultInterop 1 to default new films to Interop, 0 for SMPTE. */
@@ -886,8 +870,10 @@ Config::write_config () const
        /* [XML] CheckForUpdates 1 to check dcpomatic.com for new text versions, 0 to check only on request. */
        cxml::add_text_child(root, "CheckForTestUpdates", _check_for_test_updates ? "1" : "0");
 
-       /* [XML] MaximumJ2KBandwidth Maximum J2K bandwidth (in bits per second) that can be specified in the GUI. */
-       cxml::add_text_child(root, "MaximumJ2KBandwidth", raw_convert<string>(_maximum_j2k_bandwidth));
+       /* [XML] MaximumJ2KVideoBitRate Maximum video bit rate (in bits per second) that can be specified in the GUI for JPEG2000 encodes. */
+       cxml::add_text_child(root, "MaximumJ2KVideoBitRate", raw_convert<string>(_maximum_video_bit_rate[VideoEncoding::JPEG2000]));
+       /* [XML] MaximumMPEG2VideoBitRate Maximum video bit rate (in bits per second) that can be specified in the GUI for MPEG2 encodes. */
+       cxml::add_text_child(root, "MaximumMPEG2VideoBitRate", raw_convert<string>(_maximum_video_bit_rate[VideoEncoding::MPEG2]));
        /* [XML] AllowAnyDCPFrameRate 1 to allow users to specify any frame rate when creating DCPs, 0 to limit the GUI to standard rates. */
        cxml::add_text_child(root, "AllowAnyDCPFrameRate", _allow_any_dcp_frame_rate ? "1" : "0");
        /* [XML] AllowAnyContainer 1 to allow users to user any container ratio for their DCP, 0 to limit the GUI to DCI Flat/Scope */
@@ -1204,20 +1190,6 @@ write_file (string root_node, string node, string version, list<shared_ptr<T>> t
 }
 
 
-void
-Config::write_cinemas () const
-{
-       write_file ("Cinemas", "Cinema", "1", _cinemas, _cinemas_file);
-}
-
-
-void
-Config::write_dkdm_recipients () const
-{
-       write_file ("DKDMRecipients", "DKDMRecipient", "1", _dkdm_recipients, _dkdm_recipients_file);
-}
-
-
 boost::filesystem::path
 Config::default_directory_or (boost::filesystem::path a) const
 {
@@ -1264,20 +1236,20 @@ Config::set_kdm_email_to_default ()
 {
        _kdm_subject = _("KDM delivery: $CPL_NAME");
 
-       _kdm_email = _(
+       _kdm_email = variant::insert_dcpomatic(_(
                "Dear Projectionist\n\n"
                "Please find attached KDMs for $CPL_NAME.\n\n"
                "Cinema: $CINEMA_NAME\n"
                "Screen(s): $SCREENS\n\n"
                "The KDMs are valid from $START_TIME until $END_TIME.\n\n"
-               "Best regards,\nDCP-o-matic"
-               );
+               "Best regards,\n%1"
+               ));
 }
 
 void
 Config::set_notification_email_to_default ()
 {
-       _notification_subject = _("DCP-o-matic notification");
+       _notification_subject = variant::insert_dcpomatic(_("%1 notification"));
 
        _notification_email = _(
                "$JOB_NAME: $JOB_STATUS"
@@ -1348,7 +1320,7 @@ Config::add_to_history_internal (vector<boost::filesystem::path>& h, boost::file
 
        h.insert (h.begin (), p);
        if (h.size() > HISTORY_SIZE) {
-               h.pop_back ();
+               h.resize(HISTORY_SIZE);
        }
 
        changed (HISTORY);
@@ -1378,20 +1350,6 @@ Config::have_existing (string file)
 }
 
 
-void
-Config::read_cinemas (cxml::Document const & f)
-{
-       _cinemas.clear ();
-       for (auto i: f.node_children("Cinema")) {
-               /* Slightly grotty two-part construction of Cinema here so that we can use
-                  shared_from_this.
-               */
-               auto cinema = make_shared<Cinema>(i);
-               cinema->read_screens (i);
-               _cinemas.push_back (cinema);
-       }
-}
-
 void
 Config::set_cinemas_file (boost::filesystem::path file)
 {
@@ -1401,25 +1359,27 @@ Config::set_cinemas_file (boost::filesystem::path file)
 
        _cinemas_file = file;
 
-       if (dcp::filesystem::exists(_cinemas_file)) {
-               /* Existing file; read it in */
-               cxml::Document f ("Cinemas");
-               f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
-               read_cinemas (f);
-       }
-
-       changed (CINEMAS);
        changed (OTHER);
 }
 
 
 void
-Config::read_dkdm_recipients (cxml::Document const & f)
+Config::set_dkdm_recipients_file(boost::filesystem::path file)
 {
-       _dkdm_recipients.clear ();
-       for (auto i: f.node_children("DKDMRecipient")) {
-               _dkdm_recipients.push_back (make_shared<DKDMRecipient>(i));
+       if (file == _dkdm_recipients_file) {
+               return;
        }
+
+       _dkdm_recipients_file = file;
+
+       changed(OTHER);
+}
+
+
+void
+Config::save_default_template(shared_ptr<const Film> film) const
+{
+       film->write_template(write_path("default.xml"));
 }
 
 
@@ -1430,14 +1390,14 @@ Config::save_template (shared_ptr<const Film> film, string name) const
 }
 
 
-list<string>
+vector<string>
 Config::templates () const
 {
        if (!dcp::filesystem::exists(read_path("templates"))) {
                return {};
        }
 
-       list<string> n;
+       vector<string> n;
        for (auto const& i: dcp::filesystem::directory_iterator(read_path("templates"))) {
                n.push_back (i.path().filename().string());
        }
@@ -1458,6 +1418,18 @@ Config::template_read_path (string name) const
 }
 
 
+boost::filesystem::path
+Config::default_template_read_path() const
+{
+       if (!boost::filesystem::exists(read_path("default.xml"))) {
+               auto film = std::make_shared<const Film>(optional<boost::filesystem::path>());
+               save_default_template(film);
+       }
+
+       return read_path("default.xml");
+}
+
+
 boost::filesystem::path
 Config::template_write_path (string name) const
 {
@@ -1652,10 +1624,10 @@ save_all_config_as_zip (boost::filesystem::path zip_file)
        auto config = Config::instance();
        zipper.add ("config.xml", dcp::file_to_string(config->config_read_file()));
        if (dcp::filesystem::exists(config->cinemas_file())) {
-               zipper.add ("cinemas.xml", dcp::file_to_string(config->cinemas_file()));
+               zipper.add("cinemas.sqlite3", dcp::file_to_string(config->cinemas_file()));
        }
        if (dcp::filesystem::exists(config->dkdm_recipients_file())) {
-               zipper.add ("dkdm_recipients.xml", dcp::file_to_string(config->dkdm_recipients_file()));
+               zipper.add("dkdm_recipients.sqlite3", dcp::file_to_string(config->dkdm_recipients_file()));
        }
 
        zipper.close ();
@@ -1663,22 +1635,58 @@ save_all_config_as_zip (boost::filesystem::path zip_file)
 
 
 void
-Config::load_from_zip(boost::filesystem::path zip_file)
+Config::load_from_zip(boost::filesystem::path zip_file, CinemasAction action)
 {
+       backup();
+
+       auto const current_cinemas = cinemas_file();
+       /* This is (unfortunately) a full path, and the user can't change it, so
+        * we always want to use that same path in the future no matter what is in the
+        * config.xml that we are about to load.
+        */
+       auto const current_dkdm_recipients = dkdm_recipients_file();
+
        Unzipper unzipper(zip_file);
        dcp::write_string_to_file(unzipper.get("config.xml"), config_write_file());
 
-       try {
-               dcp::write_string_to_file(unzipper.get("cinemas.xml"), cinemas_file());
-               dcp::write_string_to_file(unzipper.get("dkdm_recipient.xml"), dkdm_recipients_file());
-       } catch (std::runtime_error&) {}
+       if (action == CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG) {
+               /* Read the zipped config, so that the cinemas file path is the new one and
+                * we write the cinemas to it.
+                */
+               read();
+               boost::filesystem::create_directories(cinemas_file().parent_path());
+               set_dkdm_recipients_file(current_dkdm_recipients);
+       }
+
+       if (unzipper.contains("cinemas.xml") && action != CinemasAction::IGNORE) {
+               CinemaList cinemas;
+               cinemas.clear();
+               cinemas.read_legacy_string(unzipper.get("cinemas.xml"));
+       }
+
+       if (unzipper.contains("dkdm_recipients.xml")) {
+               DKDMRecipientList recipients;
+               recipients.clear();
+               recipients.read_legacy_string(unzipper.get("dkdm_recipients.xml"));
+       }
+
+       if (unzipper.contains("cinemas.sqlite3") && action != CinemasAction::IGNORE) {
+               dcp::write_string_to_file(unzipper.get("cinemas.sqlite3"), cinemas_file());
+       }
+
+       if (unzipper.contains("dkdm_recipients.sqlite3")) {
+               dcp::write_string_to_file(unzipper.get("dkdm_recipients.sqlite3"), dkdm_recipients_file());
+       }
 
-       read();
+       if (action != CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG) {
+               /* Read the zipped config, then reset the cinemas file to be the old one */
+               read();
+               set_cinemas_file(current_cinemas);
+               set_dkdm_recipients_file(current_dkdm_recipients);
+       }
 
        changed(Property::USE_ANY_SERVERS);
        changed(Property::SERVERS);
-       changed(Property::CINEMAS);
-       changed(Property::DKDM_RECIPIENTS);
        changed(Property::SOUND);
        changed(Property::SOUND_OUTPUT);
        changed(Property::PLAYER_CONTENT_DIRECTORY);
@@ -1715,6 +1723,25 @@ Config::initial_path(string id) const
 }
 
 
+bool
+Config::zip_contains_cinemas(boost::filesystem::path zip)
+{
+       Unzipper unzipper(zip);
+       return unzipper.contains("cinemas.sqlite3") || unzipper.contains("cinemas.xml");
+}
+
+
+boost::filesystem::path
+Config::cinemas_file_from_zip(boost::filesystem::path zip)
+{
+       Unzipper unzipper(zip);
+       DCPOMATIC_ASSERT(unzipper.contains("config.xml"));
+       cxml::Document document("Config");
+       document.read_string(unzipper.get("config.xml"));
+       return document.string_child("CinemasFile");
+}
+
+
 #ifdef DCPOMATIC_GROK
 
 Config::Grok::Grok(cxml::ConstNodePtr node)