From: Carl Hetherington Date: Tue, 17 Sep 2013 22:39:05 +0000 (+0100) Subject: Merge 1.0 in. X-Git-Tag: v2.0.48~1346 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=373f010a7f04add1f49169cbaa60cb7ae5f508d4;hp=-c Merge 1.0 in. --- 373f010a7f04add1f49169cbaa60cb7ae5f508d4 diff --combined src/lib/config.cc index a74c36f73,9f981c619..5b96d108c --- a/src/lib/config.cc +++ b/src/lib/config.cc @@@ -22,36 -22,119 +22,120 @@@ #include #include #include + #include + #include #include "config.h" #include "server.h" #include "scaler.h" #include "filter.h" + #include "ratio.h" + #include "dcp_content_type.h" #include "sound_processor.h" - #include "cinema.h" + #include "colour_conversion.h" + + #include "i18n.h" using std::vector; using std::ifstream; using std::string; using std::ofstream; using std::list; + using std::max; using boost::shared_ptr; + using boost::lexical_cast; + using boost::optional; Config* Config::_instance = 0; /** Construct default configuration */ Config::Config () - : _num_local_encoding_threads (2) + : _num_local_encoding_threads (max (2U, boost::thread::hardware_concurrency())) , _server_port (6192) - , _reference_scaler (Scaler::from_id ("bicubic")) - , _tms_path (".") - , _sound_processor (SoundProcessor::from_id ("dolby_cp750")) + , _tms_path (N_(".")) + , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750"))) + , _default_still_length (10) + , _default_container (Ratio::from_id ("185")) + , _default_dcp_content_type (DCPContentType::from_dci_name ("TST")) + , _default_j2k_bandwidth (200000000) { - ifstream f (read_file().c_str ()); - string line; + _allowed_dcp_frame_rates.push_back (24); + _allowed_dcp_frame_rates.push_back (25); + _allowed_dcp_frame_rates.push_back (30); + _allowed_dcp_frame_rates.push_back (48); + _allowed_dcp_frame_rates.push_back (50); + _allowed_dcp_frame_rates.push_back (60); + + _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6)); + _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6)); + } + + void + Config::read () + { + if (!boost::filesystem::exists (file (false))) { + read_old_metadata (); + return; + } - shared_ptr cinema; - shared_ptr screen; + cxml::Document f ("Config"); + f.read_file (file (false)); + optional c; + + _num_local_encoding_threads = f.number_child ("NumLocalEncodingThreads"); + _default_directory = f.string_child ("DefaultDirectory"); + _server_port = f.number_child ("ServerPort"); + list > servers = f.node_children ("Server"); + for (list >::iterator i = servers.begin(); i != servers.end(); ++i) { + _servers.push_back (ServerDescription (*i)); + } + + _tms_ip = f.string_child ("TMSIP"); + _tms_path = f.string_child ("TMSPath"); + _tms_user = f.string_child ("TMSUser"); + _tms_password = f.string_child ("TMSPassword"); + + c = f.optional_string_child ("SoundProcessor"); + if (c) { + _sound_processor = SoundProcessor::from_id (c.get ()); + } + + _language = f.optional_string_child ("Language"); + + c = f.optional_string_child ("DefaultContainer"); + if (c) { + _default_container = Ratio::from_id (c.get ()); + } + + c = f.optional_string_child ("DefaultDCPContentType"); + if (c) { + _default_dcp_content_type = DCPContentType::from_dci_name (c.get ()); + } + + _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or (""); + _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or (""); + + _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); + _default_still_length = f.optional_number_child("DefaultStillLength").get_value_or (10); + _default_j2k_bandwidth = f.optional_number_child("DefaultJ2KBandwidth").get_value_or (200000000); + + list > cc = f.node_children ("ColourConversion"); + + if (!cc.empty ()) { + _colour_conversions.clear (); + } + + for (list >::iterator i = cc.begin(); i != cc.end(); ++i) { + _colour_conversions.push_back (PresetColourConversion (*i)); + } + } + + void + Config::read_old_metadata () + { + ifstream f (file(true).c_str ()); + string line; ++ while (getline (f, line)) { if (line.empty ()) { continue; @@@ -69,96 -152,74 +153,85 @@@ string const k = line.substr (0, s); string const v = line.substr (s + 1); - if (k == "num_local_encoding_threads") { + if (k == N_("num_local_encoding_threads")) { _num_local_encoding_threads = atoi (v.c_str ()); - } else if (k == "default_directory") { + } else if (k == N_("default_directory")) { _default_directory = v; - } else if (k == "server_port") { + } else if (k == N_("server_port")) { _server_port = atoi (v.c_str ()); - } else if (k == "reference_scaler") { - _reference_scaler = Scaler::from_id (v); - } else if (k == "reference_filter") { - _reference_filters.push_back (Filter::from_id (v)); - } else if (k == "server") { - _servers.push_back (ServerDescription::create_from_metadata (v)); - } else if (k == "tms_ip") { + } else if (k == N_("server")) { + optional server = ServerDescription::create_from_metadata (v); + if (server) { + _servers.push_back (server.get ()); + } + } else if (k == N_("tms_ip")) { _tms_ip = v; - } else if (k == "tms_path") { + } else if (k == N_("tms_path")) { _tms_path = v; - } else if (k == "tms_user") { + } else if (k == N_("tms_user")) { _tms_user = v; - } else if (k == "tms_password") { + } else if (k == N_("tms_password")) { _tms_password = v; - } else if (k == "sound_processor") { + } else if (k == N_("sound_processor")) { _sound_processor = SoundProcessor::from_id (v); - } else if (k == "cinema") { - if (cinema) { - _cinemas.push_back (cinema); - } - cinema.reset (new Cinema (v, "")); - } else if (k == "cinema_email") { - assert (cinema); - cinema->email = v; - } else if (k == "screen") { - assert (cinema); - if (screen) { - cinema->screens.push_back (screen); - } - screen.reset (new Screen (v, shared_ptr ())); - } else if (k == "screen_certificate") { - assert (screen); - shared_ptr c (new libdcp::Certificate); - c->set_from_string (v); + } else if (k == "language") { + _language = v; + } else if (k == "default_container") { + _default_container = Ratio::from_id (v); + } else if (k == "default_dcp_content_type") { + _default_dcp_content_type = DCPContentType::from_dci_name (v); + } else if (k == "dcp_metadata_issuer") { + _dcp_metadata.issuer = v; + } else if (k == "dcp_metadata_creator") { + _dcp_metadata.creator = v; + } else if (k == "dcp_metadata_issue_date") { + _dcp_metadata.issue_date = v; } - } - if (cinema) { - _cinemas.push_back (cinema); + _default_dci_metadata.read_old_metadata (k, v); } } /** @return Filename to write configuration to */ string - Config::write_file () const + Config::file (bool old) const { boost::filesystem::path p; p /= g_get_user_config_dir (); - p /= "dvdomatic"; - boost::filesystem::create_directory (p); - p /= "config"; - return p.string (); - } - - string - Config::read_file () const - { - if (boost::filesystem::exists (write_file ())) { - return write_file (); + boost::system::error_code ec; + boost::filesystem::create_directory (p, ec); + if (old) { + p /= ".dvdomatic"; + } else { + p /= "dcpomatic.xml"; } - - boost::filesystem::path p; - p /= g_get_user_config_dir (); - p /= ".dvdomatic"; return p.string (); } +string +Config::crypt_chain_directory () const +{ + boost::filesystem::path p; + p /= g_get_user_config_dir (); + p /= "dvdomatic"; + p /= "crypt"; + boost::filesystem::create_directories (p); + return p.string (); +} + /** @return Singleton instance */ Config * Config::instance () { if (_instance == 0) { _instance = new Config; + try { + _instance->read (); + } catch (...) { + /* configuration load failed; never mind, just + stick with the default. + */ + } } return _instance; @@@ -168,33 -229,46 +241,46 @@@ void Config::write () const { - ofstream f (write_file().c_str ()); - f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n" - << "default_directory " << _default_directory << "\n" - << "server_port " << _server_port << "\n" - << "reference_scaler " << _reference_scaler->id () << "\n"; + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("Config"); - for (vector::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) { - f << "reference_filter " << (*i)->id () << "\n"; - } + root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast (_num_local_encoding_threads)); + root->add_child("DefaultDirectory")->add_child_text (_default_directory); + root->add_child("ServerPort")->add_child_text (lexical_cast (_server_port)); - for (vector::const_iterator i = _servers.begin(); i != _servers.end(); ++i) { - f << "server " << (*i)->as_metadata () << "\n"; + for (vector::const_iterator i = _servers.begin(); i != _servers.end(); ++i) { + i->as_xml (root->add_child ("Server")); } - f << "tms_ip " << _tms_ip << "\n"; - f << "tms_path " << _tms_path << "\n"; - f << "tms_user " << _tms_user << "\n"; - f << "tms_password " << _tms_password << "\n"; - f << "sound_processor " << _sound_processor->id () << "\n"; + root->add_child("TMSIP")->add_child_text (_tms_ip); + root->add_child("TMSPath")->add_child_text (_tms_path); + root->add_child("TMSUser")->add_child_text (_tms_user); + root->add_child("TMSPassword")->add_child_text (_tms_password); + if (_sound_processor) { + root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ()); + } + if (_language) { + root->add_child("Language")->add_child_text (_language.get()); + } + if (_default_container) { + root->add_child("DefaultContainer")->add_child_text (_default_container->id ()); + } + if (_default_dcp_content_type) { + root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ()); + } + root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer); + root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator); - for (list >::const_iterator i = _cinemas.begin(); i != _cinemas.end(); ++i) { - f << "cinema " << (*i)->name << "\n"; - f << "cinema_email " << (*i)->email << "\n"; - for (list >::iterator j = (*i)->screens.begin(); j != (*i)->screens.end(); ++j) { - f << "screen " << (*j)->name << "\n"; - } + _default_dci_metadata.as_xml (root->add_child ("DCIMetadata")); + + root->add_child("DefaultStillLength")->add_child_text (lexical_cast (_default_still_length)); + root->add_child("DefaultJ2KBandwidth")->add_child_text (lexical_cast (_default_j2k_bandwidth)); + + for (vector::const_iterator i = _colour_conversions.begin(); i != _colour_conversions.end(); ++i) { + i->as_xml (root->add_child ("ColourConversion")); } + + doc.write_to_file_formatted (file (false)); } string @@@ -206,3 -280,10 +292,10 @@@ Config::default_directory_or (string a return _default_directory; } + + void + Config::drop () + { + delete _instance; + _instance = 0; + } diff --combined src/lib/config.h index ee4e4eaec,bce6bfd7e..48eabd54c --- a/src/lib/config.h +++ b/src/lib/config.h @@@ -21,23 -21,28 +21,29 @@@ * @brief Class holding configuration. */ - #ifndef DVDOMATIC_CONFIG_H - #define DVDOMATIC_CONFIG_H + #ifndef DCPOMATIC_CONFIG_H + #define DCPOMATIC_CONFIG_H #include #include #include + #include + #include "dci_metadata.h" + #include "colour_conversion.h" + #include "server.h" class ServerDescription; class Scaler; class Filter; class SoundProcessor; + class DCPContentType; + class Ratio; +class Cinema; /** @class Config * @brief A singleton class holding configuration. */ - class Config + class Config : public boost::noncopyable { public: @@@ -58,18 -63,10 +64,10 @@@ } /** @return J2K encoding servers to use */ - std::vector servers () const { + std::vector servers () const { return _servers; } - Scaler const * reference_scaler () const { - return _reference_scaler; - } - - std::vector reference_filters () const { - return _reference_filters; - } - /** @return The IP address of a TMS that we can copy DCPs to */ std::string tms_ip () const { return _tms_ip; @@@ -95,9 -92,41 +93,45 @@@ return _sound_processor; } + std::list > cinemas () const { + return _cinemas; + } ++ + std::list allowed_dcp_frame_rates () const { + return _allowed_dcp_frame_rates; + } + + DCIMetadata default_dci_metadata () const { + return _default_dci_metadata; + } + + boost::optional language () const { + return _language; + } + + int default_still_length () const { + return _default_still_length; + } + + Ratio const * default_container () const { + return _default_container; + } + + DCPContentType const * default_dcp_content_type () const { + return _default_dcp_content_type; + } + + libdcp::XMLMetadata dcp_metadata () const { + return _dcp_metadata; + } + + int default_j2k_bandwidth () const { + return _default_j2k_bandwidth; + } + + std::vector colour_conversions () const { + return _colour_conversions; + } /** @param n New number of local encoding threads */ void set_num_local_encoding_threads (int n) { @@@ -114,7 -143,7 +148,7 @@@ } /** @param s New list of servers */ - void set_servers (std::vector s) { + void set_servers (std::vector s) { _servers = s; } @@@ -146,24 -175,56 +180,66 @@@ _tms_password = p; } + void add_cinema (boost::shared_ptr c) { + _cinemas.push_back (c); + } + + void remove_cinema (boost::shared_ptr c) { + _cinemas.remove (c); + } ++ + void set_allowed_dcp_frame_rates (std::list const & r) { + _allowed_dcp_frame_rates = r; + } + + void set_default_dci_metadata (DCIMetadata d) { + _default_dci_metadata = d; + } + + void set_language (std::string l) { + _language = l; + } + + void unset_language () { + _language = boost::none; + } + + void set_default_still_length (int s) { + _default_still_length = s; + } + + void set_default_container (Ratio const * c) { + _default_container = c; + } + + void set_default_dcp_content_type (DCPContentType const * t) { + _default_dcp_content_type = t; + } + + void set_dcp_metadata (libdcp::XMLMetadata m) { + _dcp_metadata = m; + } + + void set_default_j2k_bandwidth (int b) { + _default_j2k_bandwidth = b; + } + + void set_colour_conversions (std::vector const & c) { + _colour_conversions = c; + } void write () const; + std::string crypt_chain_directory () const; + static Config* instance (); + static void drop (); private: Config (); - std::string read_file () const; - std::string write_file () const; + std::string file (bool) const; + void read (); + void read_old_metadata (); /** number of threads to use for J2K encoding on the local machine */ int _num_local_encoding_threads; @@@ -173,7 -234,7 +249,7 @@@ int _server_port; /** J2K encoding servers to use */ - std::vector _servers; + std::vector _servers; /** Scaler to use for the "A" part of A/B comparisons */ Scaler const * _reference_scaler; /** Filters to use for the "A" part of A/B comparisons */ @@@ -188,9 -249,17 +264,19 @@@ std::string _tms_password; /** Our sound processor */ SoundProcessor const * _sound_processor; + std::list _allowed_dcp_frame_rates; + /** Default DCI metadata for newly-created Films */ + DCIMetadata _default_dci_metadata; + boost::optional _language; + int _default_still_length; + Ratio const * _default_container; + DCPContentType const * _default_dcp_content_type; + libdcp::XMLMetadata _dcp_metadata; + int _default_j2k_bandwidth; + std::vector _colour_conversions; + std::list > _cinemas; + /** Singleton instance, or 0 */ static Config* _instance; }; diff --combined src/lib/exceptions.h index 06177863a,f587f055f..b04d973dc --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@@ -17,13 -17,21 +17,21 @@@ */ + #ifndef DCPOMATIC_EXCEPTIONS_H + #define DCPOMATIC_EXCEPTIONS_H + /** @file src/exceptions.h * @brief Our exceptions. */ #include - #include #include + #include + #include + #include + extern "C" { + #include + } /** @class StringError * @brief A parent class for exceptions using messages held in a std::string @@@ -80,7 -88,7 +88,7 @@@ public /** @param m Error message. * @param f Name of the file that this exception concerns. */ - FileError (std::string m, std::string f) + FileError (std::string m, boost::filesystem::path f) : StringError (m) , _file (f) {} @@@ -88,13 -96,13 +96,13 @@@ virtual ~FileError () throw () {} /** @return name of the file that this exception concerns */ - std::string file () const { + boost::filesystem::path file () const { return _file; } private: /** name of the file that this exception concerns */ - std::string _file; + boost::filesystem::path _file; }; @@@ -105,9 -113,7 +113,7 @@@ class OpenFileError : public FileErro { public: /** @param f File that we were trying to open */ - OpenFileError (std::string f) - : FileError ("could not open file " + f, f) - {} + OpenFileError (boost::filesystem::path f); }; /** @class CreateFileError. @@@ -117,9 -123,7 +123,7 @@@ class CreateFileError : public FileErro { public: /** @param f File that we were trying to create */ - CreateFileError (std::string f) - : FileError ("could not create file " + f, f) - {} + CreateFileError (boost::filesystem::path f); }; @@@ -132,16 -136,7 +136,7 @@@ public /** @param f File that we were trying to read from. * @param e errno value, or 0. */ - ReadFileError (std::string f, int e = 0) - : FileError ("", f) - { - std::stringstream s; - s << "could not read from file " << f; - if (e) { - s << " (" << strerror (e) << ")"; - } - _what = s.str (); - } + ReadFileError (boost::filesystem::path f, int e = 0); }; /** @class WriteFileError. @@@ -153,16 -148,7 +148,7 @@@ public /** @param f File that we were trying to write to. * @param e errno value, or 0. */ - WriteFileError (std::string f, int e) - : FileError ("", f) - { - std::stringstream s; - s << "could not write to file " << f; - if (e) { - s << " (" << strerror (e) << ")"; - } - _what = s.str (); - } + WriteFileError (boost::filesystem::path f, int e); }; /** @class SettingError. @@@ -197,9 -183,7 +183,7 @@@ class MissingSettingError : public Sett { public: /** @param s Name of setting that was required */ - MissingSettingError (std::string s) - : SettingError (s, "missing required setting " + s) - {} + MissingSettingError (std::string s); }; /** @class BadSettingError @@@ -225,10 -209,37 +209,45 @@@ public {} }; +class KDMError : public StringError +{ +public: + KDMError (std::string s) + : StringError (s) + {} +}; ++ + class PixelFormatError : public StringError + { + public: + PixelFormatError (std::string o, AVPixelFormat f); + }; + + class ExceptionStore + { + public: + bool thrown () const { + boost::mutex::scoped_lock lm (_mutex); + return _exception; + } + + void rethrow () { + boost::mutex::scoped_lock lm (_mutex); + boost::rethrow_exception (_exception); + } + + protected: + + void store_current () { + boost::mutex::scoped_lock lm (_mutex); + _exception = boost::current_exception (); + } + + private: + boost::exception_ptr _exception; + mutable boost::mutex _mutex; + }; + + + + #endif diff --combined src/lib/film.cc index fb3423bb4,940e94fa7..e885fe5fd --- a/src/lib/film.cc +++ b/src/lib/film.cc @@@ -1,5 -1,5 +1,5 @@@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2013 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@@ -30,33 -30,27 +30,30 @@@ #include #include #include + #include +#include - #include - #include "cinema.h" ++#include #include "film.h" - #include "format.h" #include "job.h" - #include "filter.h" - #include "transcoder.h" #include "util.h" #include "job_manager.h" - #include "ab_transcode_job.h" #include "transcode_job.h" #include "scp_dcp_job.h" - #include "make_dcp_job.h" #include "log.h" - #include "options.h" #include "exceptions.h" #include "examine_content_job.h" #include "scaler.h" - #include "decoder_factory.h" #include "config.h" - #include "check_hashes_job.h" #include "version.h" #include "ui_signaller.h" - #include "video_decoder.h" - #include "audio_decoder.h" - #include "external_audio_decoder.h" + #include "playlist.h" + #include "player.h" + #include "dcp_content_type.h" + #include "ratio.h" + #include "cross.h" ++#include "cinema.h" + + #include "i18n.h" using std::string; using std::stringstream; @@@ -69,52 -63,53 +66,54 @@@ using std::ofstream using std::setfill; using std::min; using std::make_pair; - using std::list; + using std::endl; using std::cout; + using std::list; using boost::shared_ptr; + using boost::weak_ptr; using boost::lexical_cast; + using boost::dynamic_pointer_cast; using boost::to_upper_copy; using boost::ends_with; using boost::starts_with; using boost::optional; + using libdcp::Size; - int const Film::state_version = 1; + int const Film::state_version = 4; - /** Construct a Film object in a given directory, reading any metadata - * file that exists in that directory. An exception will be thrown if - * must_exist is true and the specified directory does not exist. + /** Construct a Film object in a given directory. * - * @param d Film directory. - * @param must_exist true to throw an exception if does not exist. + * @param dir Film directory. */ - Film::Film (string d, bool must_exist) - : _use_dci_name (true) - , _trust_content_header (true) - , _dcp_content_type (0) - , _format (0) + Film::Film (boost::filesystem::path dir) + : _playlist (new Playlist) + , _use_dci_name (true) + , _dcp_content_type (Config::instance()->default_dcp_content_type ()) + , _container (Config::instance()->default_container ()) + , _resolution (RESOLUTION_2K) , _scaler (Scaler::from_id ("bicubic")) - , _dcp_trim_start (0) - , _dcp_trim_end (0) - , _dcp_ab (false) - , _use_content_audio (true) - , _audio_gain (0) - , _audio_delay (0) - , _still_duration (10) , _with_subtitles (false) - , _subtitle_offset (0) - , _subtitle_scale (1) + , _encrypted (false) - , _colour_lut (0) - , _j2k_bandwidth (200000000) - , _frames_per_second (0) + , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ()) + , _dci_metadata (Config::instance()->default_dci_metadata ()) + , _video_frame_rate (24) + , _audio_channels (MAX_AUDIO_CHANNELS) + , _three_d (false) + , _sequence_video (true) + , _interop (false) , _dirty (false) { + set_dci_date_today (); + + _playlist->Changed.connect (bind (&Film::playlist_changed, this)); + _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2)); + /* Make state.directory a complete path without ..s (where possible) (Code swiped from Adam Bowen on stackoverflow) */ - boost::filesystem::path p (boost::filesystem::system_complete (d)); + boost::filesystem::path p (boost::filesystem::system_complete (dir)); boost::filesystem::path result; for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) { if (*i == "..") { @@@ -129,239 -124,161 +128,161 @@@ } set_directory (result.string ()); - - if (!boost::filesystem::exists (directory())) { - if (must_exist) { - throw OpenFileError (directory()); - } else { - boost::filesystem::create_directory (directory()); - } - } + _log.reset (new FileLog (file ("log"))); - _external_audio_stream = ExternalAudioStream::create (); - - if (must_exist) { - read_metadata (); - } - - _log = new FileLog (file ("log")); - set_dci_date_today (); + _playlist->set_sequence_video (_sequence_video); } - Film::Film (Film const & o) - : boost::enable_shared_from_this (o) - , _log (0) - , _directory (o._directory) - , _name (o._name) - , _use_dci_name (o._use_dci_name) - , _content (o._content) - , _trust_content_header (o._trust_content_header) - , _dcp_content_type (o._dcp_content_type) - , _format (o._format) - , _crop (o._crop) - , _filters (o._filters) - , _scaler (o._scaler) - , _dcp_trim_start (o._dcp_trim_start) - , _dcp_trim_end (o._dcp_trim_end) - , _reel_size (o._reel_size) - , _dcp_ab (o._dcp_ab) - , _content_audio_stream (o._content_audio_stream) - , _external_audio (o._external_audio) - , _use_content_audio (o._use_content_audio) - , _audio_gain (o._audio_gain) - , _audio_delay (o._audio_delay) - , _still_duration (o._still_duration) - , _subtitle_stream (o._subtitle_stream) - , _with_subtitles (o._with_subtitles) - , _subtitle_offset (o._subtitle_offset) - , _subtitle_scale (o._subtitle_scale) - , _encrypted (o._encrypted) - , _colour_lut (o._colour_lut) - , _j2k_bandwidth (o._j2k_bandwidth) - , _audio_language (o._audio_language) - , _subtitle_language (o._subtitle_language) - , _territory (o._territory) - , _rating (o._rating) - , _studio (o._studio) - , _facility (o._facility) - , _package_type (o._package_type) - , _size (o._size) - , _length (o._length) - , _content_digest (o._content_digest) - , _content_audio_streams (o._content_audio_streams) - , _external_audio_stream (o._external_audio_stream) - , _subtitle_streams (o._subtitle_streams) - , _frames_per_second (o._frames_per_second) - , _dirty (o._dirty) + string + Film::video_identifier () const { + assert (container ()); + LocaleGuard lg; - } + stringstream s; + s << container()->id() + << "_" << resolution_to_string (_resolution) + << "_" << _playlist->video_identifier() + << "_" << _video_frame_rate + << "_" << scaler()->id() + << "_" << j2k_bandwidth(); - Film::~Film () - { - delete _log; + if (_interop) { + s << "_I"; + } else { + s << "_S"; + } + + if (_three_d) { + s << "_3D"; + } + + return s.str (); } - /** @return The path to the directory to write JPEG2000 files to */ + /** @return The path to the directory to write video frame info files to */ string - Film::j2k_dir () const + Film::info_dir () const { - assert (format()); - boost::filesystem::path p; + p /= "info"; + p /= video_identifier (); + return dir (p.string()); + } - /* Start with j2c */ - p /= "j2c"; + string + Film::internal_video_mxf_dir () const + { + return dir ("video"); + } - pair f = Filter::ffmpeg_strings (filters()); + string + Film::internal_video_mxf_filename () const + { + return video_identifier() + ".mxf"; + } - /* Write stuff to specify the filter / post-processing settings that are in use, - so that we don't get confused about J2K files generated using different - settings. - */ - stringstream s; - s << format()->id() - << "_" << content_digest() - << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom - << "_" << f.first << "_" << f.second - << "_" << scaler()->id() - << "_" << j2k_bandwidth() - << "_" << boost::lexical_cast (colour_lut()); + string + Film::video_mxf_filename () const + { + return filename_safe_name() + "_video.mxf"; + } - p /= s.str (); + string + Film::audio_mxf_filename () const + { + return filename_safe_name() + "_audio.mxf"; + } - /* Similarly for the A/B case */ - if (dcp_ab()) { - stringstream s; - pair fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); - s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; - p /= s.str (); + string + Film::filename_safe_name () const + { + string const n = name (); + string o; + for (size_t i = 0; i < n.length(); ++i) { + if (isalnum (n[i])) { + o += n[i]; + } else { + o += "_"; + } } - - return dir (p.string()); + + return o; } - /** Add suitable Jobs to the JobManager to create a DCP for this Film. - * @param true to transcode, false to use the WAV and J2K files that are already there. - */ + boost::filesystem::path + Film::audio_analysis_path (shared_ptr c) const + { + boost::filesystem::path p = dir ("analysis"); + p /= c->digest(); + return p; + } + + /** Add suitable Jobs to the JobManager to create a DCP for this Film */ void - Film::make_dcp (bool transcode) + Film::make_dcp () { set_dci_date_today (); if (dcp_name().find ("/") != string::npos) { - throw BadSettingError ("name", "cannot contain slashes"); + throw BadSettingError (_("name"), _("cannot contain slashes")); } - log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary())); + log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary())); { char buffer[128]; gethostname (buffer, sizeof (buffer)); log()->log (String::compose ("Starting to make DCP on %1", buffer)); } - - log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video"))); - log()->log (String::compose ("Content length %1", length().get())); - log()->log (String::compose ("Content digest %1", content_digest())); + + ContentList cl = content (); + for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) { + log()->log (String::compose ("Content: %1", (*i)->technical_summary())); + } + log()->log (String::compose ("DCP video rate %1 fps", video_frame_rate())); log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads())); log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth())); - #ifdef DVDOMATIC_DEBUG - log()->log ("DVD-o-matic built in debug mode."); + #ifdef DCPOMATIC_DEBUG + log()->log ("DCP-o-matic built in debug mode."); #else - log()->log ("DVD-o-matic built in optimised mode."); + log()->log ("DCP-o-matic built in optimised mode."); #endif #ifdef LIBDCP_DEBUG log()->log ("libdcp built in debug mode."); #else log()->log ("libdcp built in optimised mode."); #endif - pair const c = cpu_info (); - log()->log (String::compose ("CPU: %1, %2 processors", c.first, c.second)); + log()->log (String::compose ("CPU: %1, %2 processors", cpu_info(), boost::thread::hardware_concurrency ())); + list > const m = mount_info (); + for (list >::const_iterator i = m.begin(); i != m.end(); ++i) { + log()->log (String::compose ("Mount: %1 %2", i->first, i->second)); + } - if (format() == 0) { - throw MissingSettingError ("format"); + if (container() == 0) { + throw MissingSettingError (_("container")); } - if (content().empty ()) { - throw MissingSettingError ("content"); + if (content().empty()) { + throw StringError (_("You must add some content to the DCP before creating it")); } if (dcp_content_type() == 0) { - throw MissingSettingError ("content type"); + throw MissingSettingError (_("content type")); } if (name().empty()) { - throw MissingSettingError ("name"); - } - - shared_ptr oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs"))); - oe->out_size = format()->dcp_size (); - oe->padding = format()->dcp_padding (shared_from_this ()); - if (dcp_length ()) { - oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get()); - if (audio_stream()) { - oe->audio_range = make_pair ( - - video_frames_to_audio_frames ( - oe->video_range.get().first, - dcp_audio_sample_rate (audio_stream()->sample_rate()), - dcp_frame_rate (frames_per_second()).frames_per_second - ), - - video_frames_to_audio_frames ( - oe->video_range.get().second, - dcp_audio_sample_rate (audio_stream()->sample_rate()), - dcp_frame_rate (frames_per_second()).frames_per_second - ) - ); - } - - } - - oe->video_skip = dcp_frame_rate (frames_per_second()).skip; - - shared_ptr od (new DecodeOptions); - od->decode_subtitles = with_subtitles (); - - shared_ptr r; - - if (transcode) { - if (dcp_ab()) { - r = JobManager::instance()->add (shared_ptr (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr ()))); - } else { - r = JobManager::instance()->add (shared_ptr (new TranscodeJob (shared_from_this(), od, oe, shared_ptr ()))); - } + throw MissingSettingError (_("name")); } - r = JobManager::instance()->add (shared_ptr (new CheckHashesJob (shared_from_this(), od, oe, r))); - JobManager::instance()->add (shared_ptr (new MakeDCPJob (shared_from_this(), oe, r))); - } - - /** Start a job to examine our content file */ - void - Film::examine_content () - { - if (_examine_content_job) { - return; - } - - _examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr ())); - _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this)); - JobManager::instance()->add (_examine_content_job); - } - - void - Film::examine_content_finished () - { - _examine_content_job.reset (); + JobManager::instance()->add (shared_ptr (new TranscodeJob (shared_from_this()))); } /** Start a job to send our DCP to the configured TMS */ void Film::send_dcp_to_tms () { - shared_ptr j (new SCPDCPJob (shared_from_this(), shared_ptr ())); + shared_ptr j (new SCPDCPJob (shared_from_this())); JobManager::instance()->add (j); } @@@ -371,12 -288,12 +292,12 @@@ int Film::encoded_frames () const { - if (format() == 0) { + if (container() == 0) { return 0; } int N = 0; - for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) { + for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) { ++N; boost::this_thread::interruption_point (); } @@@ -388,86 -305,43 +309,44 @@@ void Film::write_metadata () const { - boost::mutex::scoped_lock lm (_state_mutex); + if (!boost::filesystem::exists (directory())) { + boost::filesystem::create_directory (directory()); + } + + LocaleGuard lg; boost::filesystem::create_directories (directory()); - string const m = file ("metadata"); - ofstream f (m.c_str ()); - if (!f.good ()) { - throw CreateFileError (m); - } + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("Metadata"); - f << "version " << state_version << "\n"; + root->add_child("Version")->add_child_text (lexical_cast (state_version)); + root->add_child("Name")->add_child_text (_name); + root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0"); - /* User stuff */ - f << "name " << _name << "\n"; - f << "use_dci_name " << _use_dci_name << "\n"; - f << "content " << _content << "\n"; - f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n"; if (_dcp_content_type) { - f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n"; - } - if (_format) { - f << "format " << _format->as_metadata () << "\n"; - } - f << "left_crop " << _crop.left << "\n"; - f << "right_crop " << _crop.right << "\n"; - f << "top_crop " << _crop.top << "\n"; - f << "bottom_crop " << _crop.bottom << "\n"; - for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - f << "filter " << (*i)->id () << "\n"; - } - f << "scaler " << _scaler->id () << "\n"; - f << "dcp_trim_start " << _dcp_trim_start << "\n"; - f << "dcp_trim_end " << _dcp_trim_end << "\n"; - if (_reel_size) { - f << "reel_size " << _reel_size.get() << "\n"; - } - f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n"; - if (_content_audio_stream) { - f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n"; - } - for (vector::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) { - f << "external_audio " << *i << "\n"; - } - f << "use_content_audio " << (_use_content_audio ? "1" : "0") << "\n"; - f << "audio_gain " << _audio_gain << "\n"; - f << "audio_delay " << _audio_delay << "\n"; - f << "still_duration " << _still_duration << "\n"; - if (_subtitle_stream) { - f << "selected_subtitle_stream " << _subtitle_stream->to_string() << "\n"; + root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ()); } - f << "with_subtitles " << _with_subtitles << "\n"; - f << "subtitle_offset " << _subtitle_offset << "\n"; - f << "subtitle_scale " << _subtitle_scale << "\n"; - f << "encrypted " << _encrypted << "\n"; - f << "colour_lut " << _colour_lut << "\n"; - f << "j2k_bandwidth " << _j2k_bandwidth << "\n"; - f << "audio_language " << _audio_language << "\n"; - f << "subtitle_language " << _subtitle_language << "\n"; - f << "territory " << _territory << "\n"; - f << "rating " << _rating << "\n"; - f << "studio " << _studio << "\n"; - f << "facility " << _facility << "\n"; - f << "package_type " << _package_type << "\n"; - - f << "width " << _size.width << "\n"; - f << "height " << _size.height << "\n"; - f << "length " << _length.get_value_or(0) << "\n"; - f << "content_digest " << _content_digest << "\n"; - - for (vector >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { - f << "content_audio_stream " << (*i)->to_string () << "\n"; - } - - f << "external_audio_stream " << _external_audio_stream->to_string() << "\n"; - for (vector >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { - f << "subtitle_stream " << (*i)->to_string () << "\n"; + if (_container) { + root->add_child("Container")->add_child_text (_container->id ()); } - f << "frames_per_second " << _frames_per_second << "\n"; + root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution)); + root->add_child("Scaler")->add_child_text (_scaler->id ()); + root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); + root->add_child("J2KBandwidth")->add_child_text (lexical_cast (_j2k_bandwidth)); + _dci_metadata.as_xml (root->add_child ("DCIMetadata")); + root->add_child("VideoFrameRate")->add_child_text (lexical_cast (_video_frame_rate)); + root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date)); + root->add_child("AudioChannels")->add_child_text (lexical_cast (_audio_channels)); + root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); + root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0"); + root->add_child("Interop")->add_child_text (_interop ? "1" : "0"); ++ root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); + _playlist->as_xml (root->add_child ("Playlist")); + + doc.write_to_file_formatted (file ("metadata.xml")); _dirty = false; } @@@ -476,273 -350,82 +355,82 @@@ void Film::read_metadata () { - boost::mutex::scoped_lock lm (_state_mutex); - - _external_audio.clear (); - _content_audio_streams.clear (); - _subtitle_streams.clear (); - - boost::optional version; - - /* Backward compatibility things */ - boost::optional audio_sample_rate; - boost::optional audio_stream_index; - boost::optional subtitle_stream_index; + LocaleGuard lg; - ifstream f (file ("metadata").c_str()); - if (!f.good()) { - throw OpenFileError (file("metadata")); + if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) { + throw StringError (_("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!")); } - - multimap kv = read_key_value (f); - /* We need version before anything else */ - multimap::iterator v = kv.find ("version"); - if (v != kv.end ()) { - version = atoi (v->second.c_str()); - } + cxml::Document f ("Metadata"); + f.read_file (file ("metadata.xml")); - for (multimap::const_iterator i = kv.begin(); i != kv.end(); ++i) { - string const k = i->first; - string const v = i->second; + _name = f.string_child ("Name"); + _use_dci_name = f.bool_child ("UseDCIName"); - if (k == "audio_sample_rate") { - audio_sample_rate = atoi (v.c_str()); + { + optional c = f.optional_string_child ("DCPContentType"); + if (c) { + _dcp_content_type = DCPContentType::from_dci_name (c.get ()); } + } - /* User-specified stuff */ - if (k == "name") { - _name = v; - } else if (k == "use_dci_name") { - _use_dci_name = (v == "1"); - } else if (k == "content") { - _content = v; - } else if (k == "trust_content_header") { - _trust_content_header = (v == "1"); - } else if (k == "dcp_content_type") { - _dcp_content_type = DCPContentType::from_pretty_name (v); - } else if (k == "format") { - _format = Format::from_metadata (v); - } else if (k == "left_crop") { - _crop.left = atoi (v.c_str ()); - } else if (k == "right_crop") { - _crop.right = atoi (v.c_str ()); - } else if (k == "top_crop") { - _crop.top = atoi (v.c_str ()); - } else if (k == "bottom_crop") { - _crop.bottom = atoi (v.c_str ()); - } else if (k == "filter") { - _filters.push_back (Filter::from_id (v)); - } else if (k == "scaler") { - _scaler = Scaler::from_id (v); - } else if (k == "dcp_trim_start") { - _dcp_trim_start = atoi (v.c_str ()); - } else if (k == "dcp_trim_end") { - _dcp_trim_end = atoi (v.c_str ()); - } else if (k == "reel_size") { - _reel_size = boost::lexical_cast (v); - } else if (k == "dcp_ab") { - _dcp_ab = (v == "1"); - } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) { - if (!version) { - audio_stream_index = atoi (v.c_str ()); - } else { - _content_audio_stream = audio_stream_factory (v, version); - } - } else if (k == "external_audio") { - _external_audio.push_back (v); - } else if (k == "use_content_audio") { - _use_content_audio = (v == "1"); - } else if (k == "audio_gain") { - _audio_gain = atof (v.c_str ()); - } else if (k == "audio_delay") { - _audio_delay = atoi (v.c_str ()); - } else if (k == "still_duration") { - _still_duration = atoi (v.c_str ()); - } else if (k == "selected_subtitle_stream") { - if (!version) { - subtitle_stream_index = atoi (v.c_str ()); - } else { - _subtitle_stream = subtitle_stream_factory (v, version); - } - } else if (k == "with_subtitles") { - _with_subtitles = (v == "1"); - } else if (k == "subtitle_offset") { - _subtitle_offset = atoi (v.c_str ()); - } else if (k == "subtitle_scale") { - _subtitle_scale = atof (v.c_str ()); - } else if (k == "encrypted") { - _encrypted = (v == "1"); - } else if (k == "colour_lut") { - _colour_lut = atoi (v.c_str ()); - } else if (k == "j2k_bandwidth") { - _j2k_bandwidth = atoi (v.c_str ()); - } else if (k == "audio_language") { - _audio_language = v; - } else if (k == "subtitle_language") { - _subtitle_language = v; - } else if (k == "territory") { - _territory = v; - } else if (k == "rating") { - _rating = v; - } else if (k == "studio") { - _studio = v; - } else if (k == "facility") { - _facility = v; - } else if (k == "package_type") { - _package_type = v; - } - - /* Cached stuff */ - if (k == "width") { - _size.width = atoi (v.c_str ()); - } else if (k == "height") { - _size.height = atoi (v.c_str ()); - } else if (k == "length") { - int const vv = atoi (v.c_str ()); - if (vv) { - _length = vv; - } - } else if (k == "content_digest") { - _content_digest = v; - } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) { - _content_audio_streams.push_back (audio_stream_factory (v, version)); - } else if (k == "external_audio_stream") { - _external_audio_stream = audio_stream_factory (v, version); - } else if (k == "subtitle_stream") { - _subtitle_streams.push_back (subtitle_stream_factory (v, version)); - } else if (k == "frames_per_second") { - _frames_per_second = atof (v.c_str ()); + { + optional c = f.optional_string_child ("Container"); + if (c) { + _container = Ratio::from_id (c.get ()); } } - if (!version) { - if (audio_sample_rate) { - /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */ - for (vector >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { - (*i)->set_sample_rate (audio_sample_rate.get()); - } - } + _resolution = string_to_resolution (f.string_child ("Resolution")); + _scaler = Scaler::from_id (f.string_child ("Scaler")); + _with_subtitles = f.bool_child ("WithSubtitles"); + _j2k_bandwidth = f.number_child ("J2KBandwidth"); + _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); + _video_frame_rate = f.number_child ("VideoFrameRate"); + _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); + _audio_channels = f.number_child ("AudioChannels"); + _sequence_video = f.bool_child ("SequenceVideo"); + _three_d = f.bool_child ("ThreeD"); + _interop = f.bool_child ("Interop"); - /* also the selected stream was specified as an index */ - if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) { - _content_audio_stream = _content_audio_streams[audio_stream_index.get()]; - } + _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist")); - /* similarly the subtitle */ - if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) { - _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()]; - } - } - _dirty = false; } - Size - Film::cropped_size (Size s) const - { - boost::mutex::scoped_lock lm (_state_mutex); - s.width -= _crop.left + _crop.right; - s.height -= _crop.top + _crop.bottom; - return s; - } - /** Given a directory name, return its full path within the Film's directory. * The directory (and its parents) will be created if they do not exist. */ string Film::dir (string d) const { - boost::mutex::scoped_lock lm (_directory_mutex); boost::filesystem::path p; p /= _directory; p /= d; + boost::filesystem::create_directories (p); + return p.string (); } /** Given a file or directory name, return its full path within the Film's directory. - * _directory_mutex must not be locked on entry. + * Any required parent directories will be created. */ string Film::file (string f) const { - boost::mutex::scoped_lock lm (_directory_mutex); boost::filesystem::path p; p /= _directory; p /= f; - return p.string (); - } - - /** @return full path of the content (actual video) file - * of the Film. - */ - string - Film::content_path () const - { - boost::mutex::scoped_lock lm (_state_mutex); - if (boost::filesystem::path(_content).has_root_directory ()) { - return _content; - } - - return file (_content); - } - ContentType - Film::content_type () const - { - if (boost::filesystem::is_directory (_content)) { - /* Directory of images, we assume */ - return VIDEO; - } - - if (still_image_file (_content)) { - return STILL; - } - - return VIDEO; - } - - /** @return The sampling rate that we will resample the audio to */ - int - Film::target_audio_sample_rate () const - { - if (!audio_stream()) { - return 0; - } + boost::filesystem::create_directories (p.parent_path ()); - /* Resample to a DCI-approved sample rate */ - double t = dcp_audio_sample_rate (audio_stream()->sample_rate()); - - DCPFrameRate dfr = dcp_frame_rate (frames_per_second ()); - - /* Compensate for the fact that video will be rounded to the - nearest integer number of frames per second. - */ - if (dfr.run_fast) { - t *= _frames_per_second * dfr.skip / dfr.frames_per_second; - } - - return rint (t); - } - - boost::optional - Film::dcp_length () const - { - if (content_type() == STILL) { - return _still_duration * frames_per_second(); - } - - if (!length()) { - return boost::optional (); - } - - return length().get() - dcp_trim_start() - dcp_trim_end(); + return p.string (); } /** @return a DCI-compliant name for a DCP of this film */ string - Film::dci_name () const + Film::dci_name (bool if_created_now) const { stringstream d; @@@ -758,64 -441,82 +446,82 @@@ fixed_name = fixed_name.substr (0, 14); } - d << fixed_name << "_"; + d << fixed_name; if (dcp_content_type()) { - d << dcp_content_type()->dci_name() << "_"; + d << "_" << dcp_content_type()->dci_name(); + d << "-" << dci_metadata().content_version; + } + + if (three_d ()) { + d << "-3D"; + } + + if (video_frame_rate() != 24) { + d << "-" << video_frame_rate(); } - if (format()) { - d << format()->dci_name() << "_"; + if (container()) { + d << "_" << container()->dci_name(); } - if (!audio_language().empty ()) { - d << audio_language(); - if (!subtitle_language().empty() && with_subtitles()) { - d << "-" << subtitle_language(); + DCIMetadata const dm = dci_metadata (); + + if (!dm.audio_language.empty ()) { + d << "_" << dm.audio_language; + if (!dm.subtitle_language.empty() && with_subtitles()) { + d << "-" << dm.subtitle_language; } else { d << "-XX"; } - - d << "_"; } - if (!territory().empty ()) { - d << territory(); - if (!rating().empty ()) { - d << "-" << rating(); + if (!dm.territory.empty ()) { + d << "_" << dm.territory; + if (!dm.rating.empty ()) { + d << "-" << dm.rating; } - d << "_"; } - switch (audio_channels()) { + switch (audio_channels ()) { case 1: - d << "10_"; + d << "_10"; break; case 2: - d << "20_"; + d << "_20"; break; - case 6: - d << "51_"; + case 3: + d << "_30"; + break; + case 4: + d << "_40"; break; - case 8: - d << "71_"; + case 5: + d << "_50"; + break; + case 6: + d << "_51"; break; } - d << "2K_"; + d << "_" << resolution_to_string (_resolution); - if (!studio().empty ()) { - d << studio() << "_"; + if (!dm.studio.empty ()) { + d << "_" << dm.studio; } - d << boost::gregorian::to_iso_string (_dci_date) << "_"; + if (if_created_now) { + d << "_" << boost::gregorian::to_iso_string (boost::gregorian::day_clock::local_day ()); + } else { + d << "_" << boost::gregorian::to_iso_string (_dci_date); + } - if (!facility().empty ()) { - d << facility() << "_"; + if (!dm.facility.empty ()) { + d << "_" << dm.facility; } - if (!package_type().empty ()) { - d << package_type(); + if (!dm.package_type.empty ()) { + d << "_" << dm.package_type; } return d.str (); @@@ -823,10 -524,10 +529,10 @@@ /** @return name to give the DCP */ string - Film::dcp_name () const + Film::dcp_name (bool if_created_now) const { if (use_dci_name()) { - return dci_name (); + return dci_name (if_created_now); } return name(); @@@ -836,7 -537,6 +542,6 @@@ void Film::set_directory (string d) { - boost::mutex::scoped_lock lm (_state_mutex); _directory = d; _dirty = true; } @@@ -844,676 -544,335 +549,410 @@@ void Film::set_name (string n) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _name = n; - } + _name = n; signal_changed (NAME); } void Film::set_use_dci_name (bool u) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _use_dci_name = u; - } + _use_dci_name = u; signal_changed (USE_DCI_NAME); } - void - Film::set_content (string c) - { - string check = directory (); - - #if BOOST_FILESYSTEM_VERSION == 3 - boost::filesystem::path slash ("/"); - string platform_slash = slash.make_preferred().string (); - #else - #ifdef DVDOMATIC_WINDOWS - string platform_slash = "\\"; - #else - string platform_slash = "/"; - #endif - #endif - - if (!ends_with (check, platform_slash)) { - check += platform_slash; - } - - if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) { - c = c.substr (_directory.length() + 1); - } - - string old_content; - - { - boost::mutex::scoped_lock lm (_state_mutex); - if (c == _content) { - return; - } - - old_content = _content; - _content = c; - } - - /* Reset streams here in case the new content doesn't have one or the other */ - _content_audio_stream = shared_ptr (); - _subtitle_stream = shared_ptr (); - - /* Start off using content audio */ - set_use_content_audio (true); - - /* Create a temporary decoder so that we can get information - about the content. - */ - - try { - shared_ptr o (new DecodeOptions); - Decoders d = decoder_factory (shared_from_this(), o, 0); - - set_size (d.video->native_size ()); - set_frames_per_second (d.video->frames_per_second ()); - set_subtitle_streams (d.video->subtitle_streams ()); - if (d.audio) { - set_content_audio_streams (d.audio->audio_streams ()); - } - - /* Start off with the first audio and subtitle streams */ - if (d.audio && !d.audio->audio_streams().empty()) { - set_content_audio_stream (d.audio->audio_streams().front()); - } - - if (!d.video->subtitle_streams().empty()) { - set_subtitle_stream (d.video->subtitle_streams().front()); - } - - { - boost::mutex::scoped_lock lm (_state_mutex); - _content = c; - } - - signal_changed (CONTENT); - - examine_content (); - - } catch (...) { - - boost::mutex::scoped_lock lm (_state_mutex); - _content = old_content; - throw; - - } - - /* Default format */ - switch (content_type()) { - case STILL: - set_format (Format::from_id ("var-185")); - break; - case VIDEO: - set_format (Format::from_id ("185")); - break; - } - - /* Still image DCPs must use external audio */ - if (content_type() == STILL) { - set_use_content_audio (false); - } - } - - void - Film::set_trust_content_header (bool t) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _trust_content_header = t; - } - - signal_changed (TRUST_CONTENT_HEADER); - - if (!_trust_content_header && !content().empty()) { - /* We just said that we don't trust the content's header */ - examine_content (); - } - } - void Film::set_dcp_content_type (DCPContentType const * t) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_content_type = t; - } + _dcp_content_type = t; signal_changed (DCP_CONTENT_TYPE); } void - Film::set_format (Format const * f) + Film::set_container (Ratio const * c) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _format = f; - } - signal_changed (FORMAT); + _container = c; + signal_changed (CONTAINER); } void - Film::set_crop (Crop c) + Film::set_resolution (Resolution r) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _crop = c; - } - signal_changed (CROP); + _resolution = r; + signal_changed (RESOLUTION); } void - Film::set_left_crop (int c) + Film::set_scaler (Scaler const * s) { - { - boost::mutex::scoped_lock lm (_state_mutex); - - if (_crop.left == c) { - return; - } - - _crop.left = c; - } - signal_changed (CROP); + _scaler = s; + signal_changed (SCALER); } void - Film::set_right_crop (int c) + Film::set_with_subtitles (bool w) { - { - boost::mutex::scoped_lock lm (_state_mutex); - if (_crop.right == c) { - return; - } - - _crop.right = c; - } - signal_changed (CROP); + _with_subtitles = w; + signal_changed (WITH_SUBTITLES); } void - Film::set_top_crop (int c) + Film::set_j2k_bandwidth (int b) { - { - boost::mutex::scoped_lock lm (_state_mutex); - if (_crop.top == c) { - return; - } - - _crop.top = c; - } - signal_changed (CROP); + _j2k_bandwidth = b; + signal_changed (J2K_BANDWIDTH); } void - Film::set_bottom_crop (int c) + Film::set_dci_metadata (DCIMetadata m) { - { - boost::mutex::scoped_lock lm (_state_mutex); - if (_crop.bottom == c) { - return; - } - - _crop.bottom = c; - } - signal_changed (CROP); + _dci_metadata = m; + signal_changed (DCI_METADATA); } void - Film::set_filters (vector f) + Film::set_video_frame_rate (int f) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _filters = f; - } - signal_changed (FILTERS); + _video_frame_rate = f; + signal_changed (VIDEO_FRAME_RATE); } void - Film::set_scaler (Scaler const * s) + Film::set_audio_channels (int c) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _scaler = s; - } - signal_changed (SCALER); + _audio_channels = c; + signal_changed (AUDIO_CHANNELS); } void - Film::set_dcp_trim_start (int t) + Film::set_three_d (bool t) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_trim_start = t; - } - signal_changed (DCP_TRIM_START); + _three_d = t; + signal_changed (THREE_D); } void - Film::set_dcp_trim_end (int t) + Film::set_interop (bool i) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_trim_end = t; - } - signal_changed (DCP_TRIM_END); + _interop = i; + signal_changed (INTEROP); } void - Film::set_reel_size (uint64_t s) + Film::signal_changed (Property p) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _reel_size = s; - } - signal_changed (REEL_SIZE); - } + _dirty = true; - void - Film::unset_reel_size () - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _reel_size = boost::optional (); + switch (p) { + case Film::CONTENT: + set_video_frame_rate (_playlist->best_dcp_frame_rate ()); + break; + case Film::VIDEO_FRAME_RATE: + case Film::SEQUENCE_VIDEO: + _playlist->maybe_sequence_video (); + break; + default: + break; } - signal_changed (REEL_SIZE); - } - void - Film::set_dcp_ab (bool a) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _dcp_ab = a; + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (Changed), p)); } - signal_changed (DCP_AB); } void - Film::set_content_audio_stream (shared_ptr s) + Film::set_dci_date_today () { - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_audio_stream = s; - } - signal_changed (CONTENT_AUDIO_STREAM); + _dci_date = boost::gregorian::day_clock::local_day (); } - void - Film::set_external_audio (vector a) + string + Film::info_path (int f, Eyes e) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _external_audio = a; - } + boost::filesystem::path p; + p /= info_dir (); - shared_ptr o (new DecodeOptions); - shared_ptr decoder (new ExternalAudioDecoder (shared_from_this(), o, 0)); - if (decoder->audio_stream()) { - _external_audio_stream = decoder->audio_stream (); + stringstream s; + s.width (8); + s << setfill('0') << f; + + if (e == EYES_LEFT) { + s << ".L"; + } else if (e == EYES_RIGHT) { + s << ".R"; } + + s << ".md5"; - signal_changed (EXTERNAL_AUDIO); + p /= s.str(); + + /* info_dir() will already have added any initial bit of the path, + so don't call file() on this. + */ + return p.string (); } - void - Film::set_use_content_audio (bool e) + string + Film::j2c_path (int f, Eyes e, bool t) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _use_content_audio = e; - } + boost::filesystem::path p; + p /= "j2c"; + p /= video_identifier (); - signal_changed (USE_CONTENT_AUDIO); - } + stringstream s; + s.width (8); + s << setfill('0') << f; - void - Film::set_audio_gain (float g) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_gain = g; + if (e == EYES_LEFT) { + s << ".L"; + } else if (e == EYES_RIGHT) { + s << ".R"; } - signal_changed (AUDIO_GAIN); - } + + s << ".j2c"; - void - Film::set_audio_delay (int d) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_delay = d; + if (t) { + s << ".tmp"; } - signal_changed (AUDIO_DELAY); - } - void - Film::set_still_duration (int d) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _still_duration = d; - } - signal_changed (STILL_DURATION); + p /= s.str(); + return file (p.string ()); } - void - Film::set_subtitle_stream (shared_ptr s) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_stream = s; - } - signal_changed (SUBTITLE_STREAM); - } + /** Make an educated guess as to whether we have a complete DCP + * or not. + * @return true if we do. + */ - void - Film::set_with_subtitles (bool w) + bool + Film::have_dcp () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _with_subtitles = w; + try { + libdcp::DCP dcp (dir (dcp_name())); + dcp.read (); + } catch (...) { + return false; } - signal_changed (WITH_SUBTITLES); - } - void - Film::set_subtitle_offset (int o) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_offset = o; - } - signal_changed (SUBTITLE_OFFSET); + return true; } - void - Film::set_subtitle_scale (float s) + shared_ptr + Film::make_player () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_scale = s; - } - signal_changed (SUBTITLE_SCALE); + return shared_ptr (new Player (shared_from_this (), _playlist)); } +void +Film::set_encrypted (bool e) +{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _encrypted = e; - } ++ _encrypted = e; + signal_changed (ENCRYPTED); +} + - void - Film::set_colour_lut (int i) + shared_ptr + Film::playlist () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _colour_lut = i; - } - signal_changed (COLOUR_LUT); + return _playlist; } - void - Film::set_j2k_bandwidth (int b) + ContentList + Film::content () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _j2k_bandwidth = b; - } - signal_changed (J2K_BANDWIDTH); + return _playlist->content (); } void - Film::set_audio_language (string l) + Film::examine_and_add_content (shared_ptr c) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_language = l; - } - signal_changed (DCI_METADATA); + shared_ptr j (new ExamineContentJob (shared_from_this(), c)); + j->Finished.connect (bind (&Film::maybe_add_content, this, boost::weak_ptr (j), boost::weak_ptr (c))); + JobManager::instance()->add (j); } void - Film::set_subtitle_language (string l) + Film::maybe_add_content (weak_ptr j, weak_ptr c) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_language = l; + shared_ptr job = j.lock (); + if (!job || !job->finished_ok ()) { + return; } - signal_changed (DCI_METADATA); - } - - void - Film::set_territory (string t) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _territory = t; + + shared_ptr content = c.lock (); + if (content) { + add_content (content); } - signal_changed (DCI_METADATA); } void - Film::set_rating (string r) + Film::add_content (shared_ptr c) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _rating = r; + /* Add video content after any existing content */ + if (dynamic_pointer_cast (c)) { + c->set_position (_playlist->video_end ()); } - signal_changed (DCI_METADATA); + + _playlist->add (c); } void - Film::set_studio (string s) + Film::remove_content (shared_ptr c) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _studio = s; - } - signal_changed (DCI_METADATA); + _playlist->remove (c); } - void - Film::set_facility (string f) + Time + Film::length () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _facility = f; - } - signal_changed (DCI_METADATA); + return _playlist->length (); } - void - Film::set_package_type (string p) + bool + Film::has_subtitles () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _package_type = p; - } - signal_changed (DCI_METADATA); + return _playlist->has_subtitles (); } - void - Film::set_size (Size s) + OutputVideoFrame + Film::best_video_frame_rate () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _size = s; - } - signal_changed (SIZE); + return _playlist->best_dcp_frame_rate (); } void - Film::set_length (SourceFrame l) + Film::playlist_content_changed (boost::weak_ptr c, int p) { - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = l; + if (p == VideoContentProperty::VIDEO_FRAME_RATE) { + set_video_frame_rate (_playlist->best_dcp_frame_rate ()); + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); } - signal_changed (LENGTH); } void - Film::unset_length () + Film::playlist_changed () { - { - boost::mutex::scoped_lock lm (_state_mutex); - _length = boost::none; - } - signal_changed (LENGTH); + signal_changed (CONTENT); } - void - Film::set_content_digest (string d) + OutputAudioFrame + Film::time_to_audio_frames (Time t) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_digest = d; - } - _dirty = true; + return t * audio_frame_rate () / TIME_HZ; } - void - Film::set_content_audio_streams (vector > s) + OutputVideoFrame + Film::time_to_video_frames (Time t) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _content_audio_streams = s; - } - signal_changed (CONTENT_AUDIO_STREAMS); + return t * video_frame_rate () / TIME_HZ; } - void - Film::set_subtitle_streams (vector > s) + Time + Film::audio_frames_to_time (OutputAudioFrame f) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _subtitle_streams = s; - } - signal_changed (SUBTITLE_STREAMS); + return f * TIME_HZ / audio_frame_rate (); } - void - Film::set_frames_per_second (float f) + Time + Film::video_frames_to_time (OutputVideoFrame f) const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _frames_per_second = f; - } - signal_changed (FRAMES_PER_SECOND); - } - - void - Film::signal_changed (Property p) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _dirty = true; - } - - if (ui_signaller) { - ui_signaller->emit (boost::bind (boost::ref (Changed), p)); - } + return f * TIME_HZ / video_frame_rate (); } - int - Film::audio_channels () const + OutputAudioFrame + Film::audio_frame_rate () const { - shared_ptr s = audio_stream (); - if (!s) { - return 0; - } - - return s->channels (); + /* XXX */ + return 48000; } void - Film::set_dci_date_today () + Film::set_sequence_video (bool s) { - _dci_date = boost::gregorian::day_clock::local_day (); + _sequence_video = s; + _playlist->set_sequence_video (s); + signal_changed (SEQUENCE_VIDEO); } - boost::shared_ptr - Film::audio_stream () const + libdcp::Size + Film::full_frame () const { - if (use_content_audio()) { - return _content_audio_stream; + switch (_resolution) { + case RESOLUTION_2K: + return libdcp::Size (2048, 1080); + case RESOLUTION_4K: + return libdcp::Size (4096, 2160); } - return _external_audio_stream; + assert (false); + return libdcp::Size (); } + +void +Film::make_kdms ( + list > screens, + boost::posix_time::ptime from, + boost::posix_time::ptime until, + string directory + ) const +{ + string const cd = Config::instance()->crypt_chain_directory (); + if (boost::filesystem::is_empty (cd)) { + libdcp::make_crypt_chain (cd); + } + + libdcp::CertificateChain chain; + + { + boost::filesystem::path p (cd); + p /= "ca.self-signed.pem"; + chain.add (shared_ptr (new libdcp::Certificate (p.string ()))); + } + + { + boost::filesystem::path p (cd); + p /= "intermediate.signed.pem"; + chain.add (shared_ptr (new libdcp::Certificate (p.string ()))); + } + + { + boost::filesystem::path p (cd); + p /= "leaf.signed.pem"; + chain.add (shared_ptr (new libdcp::Certificate (p.string ()))); + } + + boost::filesystem::path signer_key (cd); + signer_key /= "leaf.key"; + + /* Find the DCP to make the KDM for */ + string const dir = this->directory (); + list dcps; + for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) { + if (boost::filesystem::is_directory (*i) && i->path().leaf() != "j2c" && i->path().leaf() != "wavs") { + dcps.push_back (i->path().string()); + } + } + + if (dcps.empty()) { + throw KDMError ("Could not find DCP to make KDM for"); + } else if (dcps.size() > 1) { + throw KDMError ("More than one possible DCP to make KDM for"); + } + + for (list >::iterator i = screens.begin(); i != screens.end(); ++i) { + + libdcp::DCP dcp (dcps.front ()); + dcp.read (); + + /* XXX: single CPL only */ - shared_ptr kdm = dcp.cpls().front()->make_kdm (chain, signer_key.string(), (*i)->certificate, from, until); ++ shared_ptr kdm = dcp.cpls().front()->make_kdm ( ++ chain, signer_key.string(), (*i)->certificate, from, until, _interop, libdcp::MXFMetadata (), Config::instance()->dcp_metadata () ++ ); + + boost::filesystem::path out = directory; + out /= "kdm.xml"; + kdm->write_to_file_formatted (out.string()); + } +} + diff --combined src/lib/film.h index 1a78e9d34,f5b29466a..809eabdaa --- a/src/lib/film.h +++ b/src/lib/film.h @@@ -18,60 -18,60 +18,61 @@@ */ /** @file src/film.h - * @brief A representation of a piece of video (with sound), including naming, - * the source content file, and how it should be presented in a DCP. + * @brief A representation of some audio and video content, and details of + * how they should be presented in a DCP. */ - #ifndef DVDOMATIC_FILM_H - #define DVDOMATIC_FILM_H + #ifndef DCPOMATIC_FILM_H + #define DCPOMATIC_FILM_H #include #include #include - #include - #include #include #include - #include - extern "C" { - #include - } - #include "dcp_content_type.h" + #include #include "util.h" - #include "stream.h" + #include "types.h" + #include "dci_metadata.h" - class Format; - class Job; - class Filter; + class DCPContentType; class Log; - class ExamineContentJob; - class ExternalAudioStream; + class Content; + class Player; + class Playlist; + class AudioContent; + class Scaler; +class Screen; /** @class Film - * @brief A representation of a video, maybe with sound. * - * A representation of a piece of video (maybe with sound), including naming, - * the source content file, and how it should be presented in a DCP. + * @brief A representation of some audio and video content, and details of + * how they should be presented in a DCP. + * + * The content of a Film is held in a Playlist (created and managed by the Film). */ - class Film : public boost::enable_shared_from_this + class Film : public boost::enable_shared_from_this, public boost::noncopyable { public: - Film (std::string d, bool must_exist = true); - Film (Film const &); - ~Film (); + Film (boost::filesystem::path); - std::string j2k_dir () const; + std::string info_dir () const; + std::string j2c_path (int, Eyes, bool) const; + std::string info_path (int, Eyes) const; + std::string internal_video_mxf_dir () const; + std::string internal_video_mxf_filename () const; + boost::filesystem::path audio_analysis_path (boost::shared_ptr) const; - void examine_content (); - void send_dcp_to_tms (); + std::string video_mxf_filename () const; + std::string audio_mxf_filename () const; - void make_dcp (bool); + void send_dcp_to_tms (); + void make_dcp (); /** @return Logger. * It is safe to call this from any thread. */ - Log* log () const { + boost::shared_ptr log () const { return _log; } @@@ -80,35 -80,39 +81,46 @@@ std::string file (std::string f) const; std::string dir (std::string d) const; - std::string content_path () const; - ContentType content_type () const; - - int target_audio_sample_rate () const; - - void write_metadata () const; void read_metadata (); + void write_metadata () const; - Size cropped_size (Size) const; - boost::optional dcp_length () const; - std::string dci_name () const; - std::string dcp_name () const; + std::string dci_name (bool if_created_now) const; + std::string dcp_name (bool if_created_now = false) const; /** @return true if our state has changed since we last saved it */ bool dirty () const { return _dirty; } - int audio_channels () const; + libdcp::Size full_frame () const; - void set_dci_date_today (); + bool have_dcp () const; + + boost::shared_ptr make_player () const; + boost::shared_ptr playlist () const; + + OutputAudioFrame audio_frame_rate () const; + + OutputAudioFrame time_to_audio_frames (Time) const; + OutputVideoFrame time_to_video_frames (Time) const; + Time video_frames_to_time (OutputVideoFrame) const; + Time audio_frames_to_time (OutputAudioFrame) const; + + /* Proxies for some Playlist methods */ + + ContentList content () const; + + Time length () const; + bool has_subtitles () const; + OutputVideoFrame best_video_frame_rate () const; + void make_kdms ( + std::list >, + boost::posix_time::ptime from, + boost::posix_time::ptime until, + std::string directory + ) const; + /** Identifiers for the parts of our state; used for signalling changes. */ @@@ -116,424 -120,173 +128,180 @@@ NONE, NAME, USE_DCI_NAME, + /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */ CONTENT, - TRUST_CONTENT_HEADER, DCP_CONTENT_TYPE, - FORMAT, - CROP, - FILTERS, + CONTAINER, + RESOLUTION, SCALER, - DCP_TRIM_START, - DCP_TRIM_END, - REEL_SIZE, - DCP_AB, - CONTENT_AUDIO_STREAM, - EXTERNAL_AUDIO, - USE_CONTENT_AUDIO, - AUDIO_GAIN, - AUDIO_DELAY, - STILL_DURATION, - SUBTITLE_STREAM, WITH_SUBTITLES, - SUBTITLE_OFFSET, - SUBTITLE_SCALE, + ENCRYPTED, - COLOUR_LUT, J2K_BANDWIDTH, DCI_METADATA, - SIZE, - LENGTH, - CONTENT_AUDIO_STREAMS, - SUBTITLE_STREAMS, - FRAMES_PER_SECOND, + VIDEO_FRAME_RATE, + AUDIO_CHANNELS, + /** The setting of _three_d has been changed */ + THREE_D, + SEQUENCE_VIDEO, + INTEROP, }; /* GET */ std::string directory () const { - boost::mutex::scoped_lock lm (_directory_mutex); return _directory; } std::string name () const { - boost::mutex::scoped_lock lm (_state_mutex); return _name; } bool use_dci_name () const { - boost::mutex::scoped_lock lm (_state_mutex); return _use_dci_name; } - std::string content () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _content; - } - - bool trust_content_header () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _trust_content_header; - } - DCPContentType const * dcp_content_type () const { - boost::mutex::scoped_lock lm (_state_mutex); return _dcp_content_type; } - Format const * format () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _format; + Ratio const * container () const { + return _container; } - Crop crop () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _crop; - } - - std::vector filters () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _filters; + Resolution resolution () const { + return _resolution; } Scaler const * scaler () const { - boost::mutex::scoped_lock lm (_state_mutex); return _scaler; } - SourceFrame dcp_trim_start () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_trim_start; - } - - SourceFrame dcp_trim_end () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_trim_end; - } - - boost::optional reel_size () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _reel_size; - } - - bool dcp_ab () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_ab; - } - - boost::shared_ptr content_audio_stream () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _content_audio_stream; - } - - std::vector external_audio () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _external_audio; - } - - bool use_content_audio () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _use_content_audio; - } - - float audio_gain () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_gain; - } - - int audio_delay () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_delay; - } - - int still_duration () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _still_duration; - } - - boost::shared_ptr subtitle_stream () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_stream; - } - bool with_subtitles () const { - boost::mutex::scoped_lock lm (_state_mutex); return _with_subtitles; } - int subtitle_offset () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_offset; - } - - float subtitle_scale () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_scale; - } - + bool encrypted () const { - boost::mutex::scoped_lock lm (_state_mutex); + return _encrypted; + } + - int colour_lut () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _colour_lut; - } - int j2k_bandwidth () const { - boost::mutex::scoped_lock lm (_state_mutex); return _j2k_bandwidth; } - std::string audio_language () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_language; - } - - std::string subtitle_language () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_language; - } - - std::string territory () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _territory; - } - - std::string rating () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _rating; - } - - std::string studio () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _studio; - } - - std::string facility () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _facility; - } - - std::string package_type () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _package_type; + DCIMetadata dci_metadata () const { + return _dci_metadata; } - Size size () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _size; + /** @return The frame rate of the DCP */ + int video_frame_rate () const { + return _video_frame_rate; } - boost::optional length () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _length; + int audio_channels () const { + return _audio_channels; } - - std::string content_digest () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _content_digest; + + bool three_d () const { + return _three_d; } - - std::vector > content_audio_streams () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _content_audio_streams; + + bool sequence_video () const { + return _sequence_video; } - std::vector > subtitle_streams () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _subtitle_streams; + bool interop () const { + return _interop; } - float frames_per_second () const { - boost::mutex::scoped_lock lm (_state_mutex); - if (content_type() == STILL) { - return 24; - } - - return _frames_per_second; - } - - boost::shared_ptr audio_stream () const; - /* SET */ void set_directory (std::string); void set_name (std::string); void set_use_dci_name (bool); - void set_content (std::string); - void set_trust_content_header (bool); + void examine_and_add_content (boost::shared_ptr); + void add_content (boost::shared_ptr); + void remove_content (boost::shared_ptr); void set_dcp_content_type (DCPContentType const *); - void set_format (Format const *); - void set_crop (Crop); - void set_left_crop (int); - void set_right_crop (int); - void set_top_crop (int); - void set_bottom_crop (int); - void set_filters (std::vector); + void set_container (Ratio const *); + void set_resolution (Resolution); void set_scaler (Scaler const *); - void set_dcp_trim_start (int); - void set_dcp_trim_end (int); - void set_reel_size (uint64_t); - void unset_reel_size (); - void set_dcp_ab (bool); - void set_content_audio_stream (boost::shared_ptr); - void set_external_audio (std::vector); - void set_use_content_audio (bool); - void set_audio_gain (float); - void set_audio_delay (int); - void set_still_duration (int); - void set_subtitle_stream (boost::shared_ptr); void set_with_subtitles (bool); - void set_subtitle_offset (int); - void set_subtitle_scale (float); + void set_encrypted (bool); - void set_colour_lut (int); void set_j2k_bandwidth (int); - void set_audio_language (std::string); - void set_subtitle_language (std::string); - void set_territory (std::string); - void set_rating (std::string); - void set_studio (std::string); - void set_facility (std::string); - void set_package_type (std::string); - void set_size (Size); - void set_length (SourceFrame); - void unset_length (); - void set_content_digest (std::string); - void set_content_audio_streams (std::vector >); - void set_subtitle_streams (std::vector >); - void set_frames_per_second (float); - - /** Emitted when some property has changed */ + void set_dci_metadata (DCIMetadata); + void set_video_frame_rate (int); + void set_audio_channels (int); + void set_three_d (bool); + void set_dci_date_today (); + void set_sequence_video (bool); + void set_interop (bool); + + /** Emitted when some property has of the Film has changed */ mutable boost::signals2::signal Changed; + /** Emitted when some property of our content has changed */ + mutable boost::signals2::signal, int)> ContentChanged; + /** Current version number of the state file */ static int const state_version; private: - - /** Log to write to */ - Log* _log; - - /** Any running ExamineContentJob, or 0 */ - boost::shared_ptr _examine_content_job; - - /** The date that we should use in a DCI name */ - boost::gregorian::date _dci_date; void signal_changed (Property); - void examine_content_finished (); + std::string video_identifier () const; + void playlist_changed (); + void playlist_content_changed (boost::weak_ptr, int); + std::string filename_safe_name () const; + void maybe_add_content (boost::weak_ptr, boost::weak_ptr); + + /** Log to write to */ + boost::shared_ptr _log; + boost::shared_ptr _playlist; /** Complete path to directory containing the film metadata; * must not be relative. */ std::string _directory; - /** Mutex for _directory */ - mutable boost::mutex _directory_mutex; - /** Name for DVD-o-matic */ + /** Name for DCP-o-matic */ std::string _name; /** True if a auto-generated DCI-compliant name should be used for our DCP */ bool _use_dci_name; - /** File or directory containing content; may be relative to our directory - * or an absolute path. - */ - std::string _content; - /** If this is true, we will believe the length specified by the content - * file's header; if false, we will run through the whole content file - * the first time we see it in order to obtain the length. - */ - bool _trust_content_header; /** The type of content that this Film represents (feature, trailer etc.) */ DCPContentType const * _dcp_content_type; - /** The format to present this Film in (flat, scope, etc.) */ - Format const * _format; - /** The crop to apply to the source */ - Crop _crop; - /** Video filters that should be used when generating DCPs */ - std::vector _filters; + /** The container to put this Film in (flat, scope, etc.) */ + Ratio const * _container; + /** DCP resolution (2K or 4K) */ + Resolution _resolution; /** Scaler algorithm to use */ Scaler const * _scaler; - /** Frames to trim off the start of the DCP */ - int _dcp_trim_start; - /** Frames to trim off the end of the DCP */ - int _dcp_trim_end; - /** Approximate target reel size in bytes; if not set, use a single reel */ - boost::optional _reel_size; - /** true to create an A/B comparison DCP, where the left half of the image - is the video without any filters or post-processing, and the right half - has the specified filters and post-processing. - */ - bool _dcp_ab; - /** The audio stream to use from our content */ - boost::shared_ptr _content_audio_stream; - /** List of filenames of external audio files, in channel order - (L, R, C, Lfe, Ls, Rs) - */ - std::vector _external_audio; - /** true to use audio from our content file; false to use external audio */ - bool _use_content_audio; - /** Gain to apply to audio in dB */ - float _audio_gain; - /** Delay to apply to audio (positive moves audio later) in milliseconds */ - int _audio_delay; - /** Duration to make still-sourced films (in seconds) */ - int _still_duration; - boost::shared_ptr _subtitle_stream; /** True if subtitles should be shown for this film */ bool _with_subtitles; - /** y offset for placing subtitles, in source pixels; +ve is further down - the frame, -ve is further up. - */ - int _subtitle_offset; - /** scale factor to apply to subtitles */ - float _subtitle_scale; + bool _encrypted; - - /** index of colour LUT to use when converting RGB to XYZ. - * 0: sRGB - * 1: Rec 709 - */ - int _colour_lut; /** bandwidth for J2K files in bits per second */ int _j2k_bandwidth; - - /* DCI naming stuff */ - std::string _audio_language; - std::string _subtitle_language; - std::string _territory; - std::string _rating; - std::string _studio; - std::string _facility; - std::string _package_type; - - /* Data which are cached to speed things up */ - - /** Size, in pixels, of the source (ignoring cropping) */ - Size _size; - /** The length of the source, in video frames (as far as we know) */ - boost::optional _length; - /** MD5 digest of our content file */ - std::string _content_digest; - /** The audio streams in our content */ - std::vector > _content_audio_streams; - /** A stream to represent possible external audio (will always exist) */ - boost::shared_ptr _external_audio_stream; - /** the subtitle streams that we can use */ - std::vector > _subtitle_streams; - /** Frames per second of the source */ - float _frames_per_second; + /** DCI naming stuff */ + DCIMetadata _dci_metadata; + /** Frames per second to run our DCP at */ + int _video_frame_rate; + /** The date that we should use in a DCI name */ + boost::gregorian::date _dci_date; + /** Number of audio channels to put in the DCP */ + int _audio_channels; + /** If true, the DCP will be written in 3D mode; otherwise in 2D. + This will be regardless of what content is on the playlist. + */ + bool _three_d; + bool _sequence_video; + bool _interop; /** true if our state has changed since we last saved it */ mutable bool _dirty; - /** Mutex for all state except _directory */ - mutable boost::mutex _state_mutex; - friend class paths_test; + friend class film_metadata_test; }; #endif diff --combined src/lib/util.cc index ef6f46575,4180bbfd7..b8bc1fc9e --- a/src/lib/util.cc +++ b/src/lib/util.cc @@@ -26,7 -26,8 +26,8 @@@ #include #include #include - #ifdef DVDOMATIC_POSIX + #include + #ifdef DCPOMATIC_POSIX #include #include #endif @@@ -38,12 -39,12 +39,13 @@@ #include #include #include + #include #include #include #include #include #include +#include extern "C" { #include #include @@@ -55,15 -56,44 +57,44 @@@ #include "util.h" #include "exceptions.h" #include "scaler.h" - #include "format.h" #include "dcp_content_type.h" #include "filter.h" #include "sound_processor.h" + #include "config.h" + #include "ratio.h" + #include "job.h" + #ifdef DCPOMATIC_WINDOWS + #include "stack.hpp" + #endif - using namespace std; - using namespace boost; - - thread::id ui_thread; + #include "i18n.h" + + using std::string; + using std::stringstream; + using std::setfill; + using std::ostream; + using std::endl; + using std::vector; + using std::hex; + using std::setw; + using std::ifstream; + using std::ios; + using std::min; + using std::max; + using std::list; + using std::multimap; + using std::istream; + using std::numeric_limits; + using std::pair; + using std::ofstream; + using boost::shared_ptr; + using boost::thread; + using boost::lexical_cast; + using boost::optional; + using libdcp::Size; + + static boost::thread::id ui_thread; + static boost::filesystem::path backtrace_file; /** Convert some number of seconds to a string representation * in hours, minutes and seconds. @@@ -81,11 -111,11 +112,11 @@@ seconds_to_hms (int s m -= (h * 60); stringstream hms; - hms << h << ":"; + hms << h << N_(":"); hms.width (2); - hms << setfill ('0') << m << ":"; + hms << std::setfill ('0') << m << N_(":"); hms.width (2); - hms << setfill ('0') << s; + hms << std::setfill ('0') << s; return hms.str (); } @@@ -105,40 -135,40 +136,40 @@@ seconds_to_approximate_hms (int s if (h > 0) { if (m > 30) { - ap << (h + 1) << " hours"; + ap << (h + 1) << N_(" ") << _("hours"); } else { if (h == 1) { - ap << "1 hour"; + ap << N_("1 ") << _("hour"); } else { - ap << h << " hours"; + ap << h << N_(" ") << _("hours"); } } } else if (m > 0) { if (m == 1) { - ap << "1 minute"; + ap << N_("1 ") << _("minute"); } else { - ap << m << " minutes"; + ap << m << N_(" ") << _("minutes"); } } else { - ap << s << " seconds"; + ap << s << N_(" ") << _("seconds"); } return ap.str (); } - #ifdef DVDOMATIC_POSIX + #ifdef DCPOMATIC_POSIX /** @param l Mangled C++ identifier. * @return Demangled version. */ static string demangle (string l) { - string::size_type const b = l.find_first_of ("("); + string::size_type const b = l.find_first_of (N_("(")); if (b == string::npos) { return l; } - string::size_type const p = l.find_last_of ("+"); + string::size_type const p = l.find_last_of (N_("+")); if (p == string::npos) { return l; } @@@ -172,16 -202,12 +203,12 @@@ voi stacktrace (ostream& out, int levels) { void *array[200]; - size_t size; - char **strings; - size_t i; - - size = backtrace (array, 200); - strings = backtrace_symbols (array, size); + size_t size = backtrace (array, 200); + char** strings = backtrace_symbols (array, size); if (strings) { - for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) { - out << " " << demangle (strings[i]) << endl; + for (size_t i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) { + out << N_(" ") << demangle (strings[i]) << "\n"; } free (strings); @@@ -196,7 -222,7 +223,7 @@@ static strin ffmpeg_version_to_string (int v) { stringstream s; - s << ((v & 0xff0000) >> 16) << "." << ((v & 0xff00) >> 8) << "." << (v & 0xff); + s << ((v & 0xff0000) >> 16) << N_(".") << ((v & 0xff00) >> 8) << N_(".") << (v & 0xff); return s.str (); } @@@ -205,16 -231,16 +232,16 @@@ strin dependency_version_summary () { stringstream s; - s << "libopenjpeg " << opj_version () << ", " - << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", " - << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", " - << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", " - << "libavutil " << ffmpeg_version_to_string (avutil_version()) << ", " - << "libpostproc " << ffmpeg_version_to_string (postproc_version()) << ", " - << "libswscale " << ffmpeg_version_to_string (swscale_version()) << ", " - << MagickVersion << ", " - << "libssh " << ssh_version (0) << ", " - << "libdcp " << libdcp::version << " git " << libdcp::git_commit; + s << N_("libopenjpeg ") << opj_version () << N_(", ") + << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ") + << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ") + << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ") + << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ") + << N_("libpostproc ") << ffmpeg_version_to_string (postproc_version()) << N_(", ") + << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ") + << MagickVersion << N_(", ") + << N_("libssh ") << ssh_version (0) << N_(", ") + << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit; return s.str (); } @@@ -225,33 -251,82 +252,82 @@@ seconds (struct timeval t return t.tv_sec + (double (t.tv_usec) / 1e6); } - /** Call the required functions to set up DVD-o-matic's static arrays, etc. + #ifdef DCPOMATIC_WINDOWS + LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *) + { + dbg::stack s; + ofstream f (backtrace_file.string().c_str()); + std::copy(s.begin(), s.end(), std::ostream_iterator(f, "\n")); + return EXCEPTION_CONTINUE_SEARCH; + } + #endif + + /** Call the required functions to set up DCP-o-matic's static arrays, etc. * Must be called from the UI thread, if there is one. */ void - dvdomatic_setup () + dcpomatic_setup () { - libdcp::init (); + #ifdef DCPOMATIC_WINDOWS + backtrace_file /= g_get_user_config_dir (); + backtrace_file /= "backtrace.txt"; + SetUnhandledExceptionFilter(exception_handler); + #endif + + avfilter_register_all (); - Format::setup_formats (); + Ratio::setup_ratios (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); Filter::setup_filters (); SoundProcessor::setup_sound_processors (); - ui_thread = this_thread::get_id (); + ui_thread = boost::this_thread::get_id (); } - /** @param start Start position for the crop within the image. - * @param size Size of the cropped area. - * @return FFmpeg crop filter string. - */ - string - crop_string (Position start, Size size) + #ifdef DCPOMATIC_WINDOWS + boost::filesystem::path + mo_path () { - stringstream s; - s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y; - return s.str (); + wchar_t buffer[512]; + GetModuleFileName (0, buffer, 512 * sizeof(wchar_t)); + boost::filesystem::path p (buffer); + p = p.parent_path (); + p = p.parent_path (); + p /= "locale"; + return p; + } + #endif + + void + dcpomatic_setup_gettext_i18n (string lang) + { + #ifdef DCPOMATIC_POSIX + lang += ".UTF8"; + #endif + + if (!lang.empty ()) { + /* Override our environment language; this is essential on + Windows. + */ + char cmd[64]; + snprintf (cmd, sizeof(cmd), "LANGUAGE=%s", lang.c_str ()); + putenv (cmd); + snprintf (cmd, sizeof(cmd), "LANG=%s", lang.c_str ()); + putenv (cmd); + } + + setlocale (LC_ALL, ""); + textdomain ("libdcpomatic"); + + #ifdef DCPOMATIC_WINDOWS + bindtextdomain ("libdcpomatic", mo_path().string().c_str()); + bind_textdomain_codeset ("libdcpomatic", "UTF8"); + #endif + + #ifdef DCPOMATIC_POSIX + bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX); + #endif } /** @param s A string. @@@ -266,7 -341,7 +342,7 @@@ split_at_spaces_considering_quotes (str for (string::size_type i = 0; i < s.length(); ++i) { if (s[i] == ' ' && !in_quotes) { out.push_back (c); - c = ""; + c = N_(""); } else if (s[i] == '"') { in_quotes = !in_quotes; } else { @@@ -289,7 -364,7 +365,7 @@@ md5_digest (void const * data, int size stringstream s; for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { - s << hex << setfill('0') << setw(2) << ((int) digest[i]); + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); } return s.str (); @@@ -299,16 -374,16 +375,16 @@@ * @return MD5 digest of file's contents. */ string - md5_digest (string file) + md5_digest (boost::filesystem::path file) { - ifstream f (file.c_str(), ios::binary); + ifstream f (file.string().c_str(), std::ios::binary); if (!f.good ()) { - throw OpenFileError (file); + throw OpenFileError (file.string()); } - f.seekg (0, ios::end); + f.seekg (0, std::ios::end); int bytes = f.tellg (); - f.seekg (0, ios::beg); + f.seekg (0, std::ios::beg); int const buffer_size = 64 * 1024; char buffer[buffer_size]; @@@ -327,275 -402,197 +403,197 @@@ stringstream s; for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { - s << hex << setfill('0') << setw(2) << ((int) digest[i]); + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); } return s.str (); } - /** @param fps Arbitrary frames-per-second value. - * @return DCPFrameRate for this frames-per-second. - */ - DCPFrameRate - dcp_frame_rate (float fps) + /** @param job Optional job for which to report progress */ + string + md5_digest_directory (boost::filesystem::path directory, shared_ptr job) { - DCPFrameRate dfr; + int const buffer_size = 64 * 1024; + char buffer[buffer_size]; - dfr.run_fast = (fps != rint (fps)); - dfr.frames_per_second = rint (fps); - dfr.skip = 1; + MD5_CTX md5_context; + MD5_Init (&md5_context); - /* XXX: somewhat arbitrary */ - if (fps == 50) { - dfr.frames_per_second = 25; - dfr.skip = 2; + int files = 0; + if (job) { + for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) { + ++files; + } } - return dfr; - } + int j = 0; + for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) { + ifstream f (i->path().string().c_str(), std::ios::binary); + if (!f.good ()) { + throw OpenFileError (i->path().string()); + } + + f.seekg (0, std::ios::end); + int bytes = f.tellg (); + f.seekg (0, std::ios::beg); + + while (bytes > 0) { + int const t = min (bytes, buffer_size); + f.read (buffer, t); + MD5_Update (&md5_context, buffer, t); + bytes -= t; + } - /** @param An arbitrary sampling rate. - * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz). - */ - int - dcp_audio_sample_rate (int fs) - { - if (fs <= 48000) { - return 48000; + if (job) { + job->set_progress (float (j) / files); + ++j; + } } - return 96000; - } + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_Final (digest, &md5_context); - int - dcp_audio_channels (int f) - { - if (f == 1) { - /* The source is mono, so to put the mono channel into - the centre we need to generate a 5.1 soundtrack. - */ - return 6; + stringstream s; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); } - return f; + return s.str (); } - - bool operator== (Size const & a, Size const & b) + static bool + about_equal (float a, float b) { - return (a.width == b.width && a.height == b.height); - } + /* A film of F seconds at f FPS will be Ff frames; + Consider some delta FPS d, so if we run the same + film at (f + d) FPS it will last F(f + d) seconds. - bool operator!= (Size const & a, Size const & b) - { - return !(a == b); - } + Hence the difference in length over the length of the film will + be F(f + d) - Ff frames + = Ff + Fd - Ff frames + = Fd frames + = Fd/f seconds + + So if we accept a difference of 1 frame, ie 1/f seconds, we can + say that - bool operator== (Crop const & a, Crop const & b) - { - return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom); - } + 1/f = Fd/f + ie 1 = Fd + ie d = 1/F + + So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable + FPS error is 1/F ~= 0.0001 ~= 10-e4 + */ - bool operator!= (Crop const & a, Crop const & b) - { - return !(a == b); + return (fabs (a - b) < 1e-4); } - /** @param index Colour LUT index. - * @return Human-readable name. + /** @param An arbitrary audio frame rate. + * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ - string - colour_lut_index_to_name (int index) + int + dcp_audio_frame_rate (int fs) { - switch (index) { - case 0: - return "sRGB"; - case 1: - return "Rec 709"; + if (fs <= 48000) { + return 48000; } - assert (false); - return ""; + return 96000; } - Socket::Socket () + Socket::Socket (int timeout) : _deadline (_io_service) , _socket (_io_service) - , _buffer_data (0) + , _timeout (timeout) { - _deadline.expires_at (posix_time::pos_infin); + _deadline.expires_at (boost::posix_time::pos_infin); check (); } void Socket::check () { - if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) { + if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) { _socket.close (); - _deadline.expires_at (posix_time::pos_infin); + _deadline.expires_at (boost::posix_time::pos_infin); } _deadline.async_wait (boost::bind (&Socket::check, this)); } - /** Blocking connect with timeout. + /** Blocking connect. * @param endpoint End-point to connect to. - * @param timeout Time-out in seconds. */ void - Socket::connect (asio::ip::basic_resolver_entry const & endpoint, int timeout) + Socket::connect (boost::asio::ip::basic_resolver_entry const & endpoint) { - _deadline.expires_from_now (posix_time::seconds (timeout)); - system::error_code ec = asio::error::would_block; - _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1); + _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); + boost::system::error_code ec = boost::asio::error::would_block; + _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1); do { _io_service.run_one(); - } while (ec == asio::error::would_block); + } while (ec == boost::asio::error::would_block); if (ec || !_socket.is_open ()) { - throw NetworkError ("connect timed out"); + throw NetworkError (_("connect timed out")); } } - /** Blocking write with timeout. + /** Blocking write. * @param data Buffer to write. * @param size Number of bytes to write. - * @param timeout Time-out, in seconds. */ void - Socket::write (uint8_t const * data, int size, int timeout) + Socket::write (uint8_t const * data, int size) { - _deadline.expires_from_now (posix_time::seconds (timeout)); - system::error_code ec = asio::error::would_block; + _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); + boost::system::error_code ec = boost::asio::error::would_block; - asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1); + boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1); + do { _io_service.run_one (); - } while (ec == asio::error::would_block); - - if (ec) { - throw NetworkError ("write timed out"); - } - } - - /** Blocking read with timeout. - * @param data Buffer to read to. - * @param size Number of bytes to read. - * @param timeout Time-out, in seconds. - */ - int - Socket::read (uint8_t* data, int size, int timeout) - { - _deadline.expires_from_now (posix_time::seconds (timeout)); - system::error_code ec = asio::error::would_block; + } while (ec == boost::asio::error::would_block); - int amount_read = 0; - - _socket.async_read_some ( - asio::buffer (data, size), - (lambda::var(ec) = lambda::_1, lambda::var(amount_read) = lambda::_2) - ); - - do { - _io_service.run_one (); - } while (ec == asio::error::would_block); - if (ec) { - amount_read = 0; + throw NetworkError (ec.message ()); } - - return amount_read; } - /** Mark some data as being `consumed', so that it will not be returned - * as data again. - * @param size Amount of data to consume, in bytes. - */ void - Socket::consume (int size) + Socket::write (uint32_t v) { - assert (_buffer_data >= size); - - _buffer_data -= size; - if (_buffer_data > 0) { - /* Shift still-valid data to the start of the buffer */ - memmove (_buffer, _buffer + size, _buffer_data); - } + v = htonl (v); + write (reinterpret_cast (&v), 4); } - /** Read a definite amount of data from our socket, and mark - * it as consumed. - * @param data Where to put the data. + /** Blocking read. + * @param data Buffer to read to. * @param size Number of bytes to read. */ void - Socket::read_definite_and_consume (uint8_t* data, int size, int timeout) - { - int const from_buffer = min (_buffer_data, size); - if (from_buffer > 0) { - /* Get data from our buffer */ - memcpy (data, _buffer, from_buffer); - consume (from_buffer); - /* Update our output state */ - data += from_buffer; - size -= from_buffer; - } - - /* read() the rest */ - while (size > 0) { - int const n = read (data, size, timeout); - if (n <= 0) { - throw NetworkError ("could not read"); - } - - data += n; - size -= n; - } - } - - /** Read as much data as is available, up to some limit. - * @param data Where to put the data. - * @param size Maximum amount of data to read. - */ - void - Socket::read_indefinite (uint8_t* data, int size, int timeout) + Socket::read (uint8_t* data, int size) { - assert (size < int (sizeof (_buffer))); + _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); + boost::system::error_code ec = boost::asio::error::would_block; - /* Amount of extra data we need to read () */ - int to_read = size - _buffer_data; - while (to_read > 0) { - /* read as much of it as we can (into our buffer) */ - int const n = read (_buffer + _buffer_data, to_read, timeout); - if (n <= 0) { - throw NetworkError ("could not read"); - } + boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1); - to_read -= n; - _buffer_data += n; + do { + _io_service.run_one (); + } while (ec == boost::asio::error::would_block); + + if (ec) { + throw NetworkError (ec.message ()); } - - assert (_buffer_data >= size); - - /* copy data into the output buffer */ - assert (size >= _buffer_data); - memcpy (data, _buffer, size); } - /** @param other A Rect. - * @return The intersection of this with `other'. - */ - Rect - Rect::intersection (Rect const & other) const + uint32_t + Socket::read_uint32 () { - int const tx = max (x, other.x); - int const ty = max (y, other.y); - - return Rect ( - tx, ty, - min (x + width, other.x + other.width) - tx, - min (y + height, other.y + other.height) - ty - ); + uint32_t v; + read (reinterpret_cast (&v), 4); + return ntohl (v); } /** Round a number up to the nearest multiple of another number. @@@ -611,12 -608,6 +609,6 @@@ stride_round_up (int c, int const * str return a - (a % t); } - int - stride_lookup (int c, int const * stride) - { - return stride[c]; - } - /** Read a sequence of key / value pairs from a text stream; * the keys are the first words on the line, and the values are * the remainder of the line following the key. Lines beginning @@@ -658,13 -649,13 +650,13 @@@ strin get_required_string (multimap const & kv, string k) { if (kv.count (k) > 1) { - throw StringError ("unexpected multiple keys in key-value set"); + throw StringError (N_("unexpected multiple keys in key-value set")); } multimap::const_iterator i = kv.find (k); if (i == kv.end ()) { - throw StringError (String::compose ("missing key %1 in key-value set", k)); + throw StringError (String::compose (_("missing key %1 in key-value set"), k)); } return i->second; @@@ -688,12 -679,12 +680,12 @@@ strin get_optional_string (multimap const & kv, string k) { if (kv.count (k) > 1) { - throw StringError ("unexpected multiple keys in key-value set"); + throw StringError (N_("unexpected multiple keys in key-value set")); } multimap::const_iterator i = kv.find (k); if (i == kv.end ()) { - return ""; + return N_(""); } return i->second; @@@ -703,7 -694,7 +695,7 @@@ in get_optional_int (multimap const & kv, string k) { if (kv.count (k) > 1) { - throw StringError ("unexpected multiple keys in key-value set"); + throw StringError (N_("unexpected multiple keys in key-value set")); } multimap::const_iterator i = kv.find (k); @@@ -714,193 -705,97 +706,97 @@@ return lexical_cast (i->second); } - /** Construct an AudioBuffers. Audio data is undefined after this constructor. - * @param channels Number of channels. - * @param frames Number of frames to reserve space for. - */ - AudioBuffers::AudioBuffers (int channels, int frames) - : _channels (channels) - , _frames (frames) - , _allocated_frames (frames) - { - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[frames]; - } - } - - /** Copy constructor. - * @param other Other AudioBuffers; data is copied. - */ - AudioBuffers::AudioBuffers (AudioBuffers const & other) - : _channels (other._channels) - , _frames (other._frames) - , _allocated_frames (other._frames) - { - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[_frames]; - memcpy (_data[i], other._data[i], _frames * sizeof (float)); - } - } - - /** AudioBuffers destructor */ - AudioBuffers::~AudioBuffers () + /** Trip an assert if the caller is not in the UI thread */ + void + ensure_ui_thread () { - for (int i = 0; i < _channels; ++i) { - delete[] _data[i]; - } - - delete[] _data; + assert (boost::this_thread::get_id() == ui_thread); } - /** @param c Channel index. - * @return Buffer for this channel. + /** @param v Content video frame. + * @param audio_sample_rate Source audio sample rate. + * @param frames_per_second Number of video frames per second. + * @return Equivalent number of audio frames for `v'. */ - float* - AudioBuffers::data (int c) const + int64_t + video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second) { - assert (c >= 0 && c < _channels); - return _data[c]; + return ((int64_t) v * audio_sample_rate / frames_per_second); } - /** Set the number of frames that these AudioBuffers will report themselves - * as having. - * @param f Frames; must be less than or equal to the number of allocated frames. - */ - void - AudioBuffers::set_frames (int f) + string + audio_channel_name (int c) { - assert (f <= _allocated_frames); - _frames = f; - } + assert (MAX_AUDIO_CHANNELS == 6); - /** Make all samples on all channels silent */ - void - AudioBuffers::make_silent () - { - for (int i = 0; i < _channels; ++i) { - make_silent (i); - } + /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency + enhancement channel (sub-woofer)./ + */ + string const channels[] = { + _("Left"), + _("Right"), + _("Centre"), + _("Lfe (sub)"), + _("Left surround"), + _("Right surround"), + }; + + return channels[c]; } - /** Make all samples on a given channel silent. - * @param c Channel. - */ - void - AudioBuffers::make_silent (int c) + FrameRateConversion::FrameRateConversion (float source, int dcp) + : skip (false) + , repeat (false) + , change_speed (false) { - assert (c >= 0 && c < _channels); - - for (int i = 0; i < _frames; ++i) { - _data[c][i] = 0; + if (fabs (source / 2.0 - dcp) < (fabs (source - dcp))) { + skip = true; + } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { + repeat = true; } - } - /** Copy data from another AudioBuffers to this one. All channels are copied. - * @param from AudioBuffers to copy from; must have the same number of channels as this. - * @param frames_to_copy Number of frames to copy. - * @param read_offset Offset to read from in `from'. - * @param write_offset Offset to write to in `to'. - */ - void - AudioBuffers::copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset) - { - assert (from->channels() == channels()); + change_speed = !about_equal (source * factor(), dcp); - assert (from); - assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames); - assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames); + if (!skip && !repeat && !change_speed) { + description = _("Content and DCP have the same rate.\n"); + } else { + if (skip) { + description = _("DCP will use every other frame of the content.\n"); + } else if (repeat) { + description = _("Each content frame will be doubled in the DCP.\n"); + } - for (int i = 0; i < _channels; ++i) { - memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float)); + if (change_speed) { + float const pc = dcp * 100 / (source * factor()); + description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc); + } } } - /** Move audio data around. - * @param from Offset to move from. - * @param to Offset to move to. - * @param frames Number of frames to move. - */ - - void - AudioBuffers::move (int from, int to, int frames) + LocaleGuard::LocaleGuard () + : _old (0) { - if (frames == 0) { - return; - } - - assert (from >= 0); - assert (from < _frames); - assert (to >= 0); - assert (to < _frames); - assert (frames > 0); - assert (frames <= _frames); - assert ((from + frames) <= _frames); - assert ((to + frames) <= _frames); - - for (int i = 0; i < _channels; ++i) { - memmove (_data[i] + to, _data[i] + from, frames * sizeof(float)); - } - } + char const * old = setlocale (LC_NUMERIC, 0); - /** Trip an assert if the caller is not in the UI thread */ - void - ensure_ui_thread () - { - assert (this_thread::get_id() == ui_thread); + if (old) { + _old = strdup (old); + if (strcmp (_old, "C")) { + setlocale (LC_NUMERIC, "C"); + } + } } - /** @param v Source video frame. - * @param audio_sample_rate Source audio sample rate. - * @param frames_per_second Number of video frames per second. - * @return Equivalent number of audio frames for `v'. - */ - int64_t - video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second) + LocaleGuard::~LocaleGuard () { - return ((int64_t) v * audio_sample_rate / frames_per_second); + setlocale (LC_NUMERIC, _old); + free (_old); } - /** @param f Filename. - * @return true if this file is a still image, false if it is something else. - */ bool - still_image_file (string f) + valid_image_file (boost::filesystem::path f) { - #if BOOST_FILESYSTEM_VERSION == 3 - string ext = boost::filesystem::path(f).extension().string(); - #else - string ext = boost::filesystem::path(f).extension(); - #endif - + string ext = f.extension().string(); transform (ext.begin(), ext.end(), ext.begin(), ::tolower); - - return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png"); + return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".tga"); } - /** @return A pair containing CPU model name and the number of processors */ - pair - cpu_info () - { - pair info; - info.second = 0; - - #ifdef DVDOMATIC_POSIX - ifstream f ("/proc/cpuinfo"); - while (f.good ()) { - string l; - getline (f, l); - if (boost::algorithm::starts_with (l, "model name")) { - string::size_type const c = l.find (':'); - if (c != string::npos) { - info.first = l.substr (c + 2); - } - } else if (boost::algorithm::starts_with (l, "processor")) { - ++info.second; - } - } - #endif - - return info; - } diff --combined src/tools/dcpomatic.cc index 000000000,98501d3bb..f61ef19e2 mode 000000,100644..100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@@ -1,0 -1,596 +1,624 @@@ + /* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + + #include + #include + #include + #ifdef __WXMSW__ + #include + #endif + #ifdef __WXOSX__ + #include + #endif + #include + #include + #include + #include "wx/film_viewer.h" + #include "wx/film_editor.h" + #include "wx/job_manager_view.h" + #include "wx/config_dialog.h" + #include "wx/job_wrapper.h" + #include "wx/wx_util.h" + #include "wx/new_film_dialog.h" + #include "wx/properties_dialog.h" + #include "wx/wx_ui_signaller.h" + #include "wx/about_dialog.h" ++#include "wx/kdm_dialog.h" + #include "lib/film.h" + #include "lib/config.h" + #include "lib/util.h" + #include "lib/version.h" + #include "lib/ui_signaller.h" + #include "lib/log.h" + #include "lib/job_manager.h" + #include "lib/transcode_job.h" ++#include "lib/exceptions.h" + + using std::cout; + using std::string; + using std::wstring; + using std::stringstream; + using std::map; + using std::make_pair; + using std::list; + using std::exception; + using std::ofstream; + using boost::shared_ptr; + using boost::dynamic_pointer_cast; + + static FilmEditor* film_editor = 0; + static FilmViewer* film_viewer = 0; + static shared_ptr film; + static std::string log_level; + static std::string film_to_load; + static std::string film_to_create; + static wxMenu* jobs_menu = 0; + + static void set_menu_sensitivity (); + + class FilmChangedDialog + { + public: + FilmChangedDialog () + { + _dialog = new wxMessageDialog ( + 0, + wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (film->name ()).data()), + _("Film changed"), + wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION + ); + } + + ~FilmChangedDialog () + { + _dialog->Destroy (); + } + + int run () + { + return _dialog->ShowModal (); + } + + private: + /* Not defined */ + FilmChangedDialog (FilmChangedDialog const &); + + wxMessageDialog* _dialog; + }; + + + void + maybe_save_then_delete_film () + { + if (!film) { + return; + } + + if (film->dirty ()) { + FilmChangedDialog d; + switch (d.run ()) { + case wxID_NO: + break; + case wxID_YES: + film->write_metadata (); + break; + } + } + + film.reset (); + } + + #define ALWAYS 0x0 + #define NEEDS_FILM 0x1 + #define NOT_DURING_DCP_CREATION 0x2 + + map menu_items; + + void + add_item (wxMenu* menu, wxString text, int id, int sens) + { + wxMenuItem* item = menu->Append (id, text); + menu_items.insert (make_pair (item, sens)); + } + + void + set_menu_sensitivity () + { + list > jobs = JobManager::instance()->get (); + list >::iterator i = jobs.begin(); + while (i != jobs.end() && dynamic_pointer_cast (*i) == 0) { + ++i; + } + bool const dcp_creation = (i != jobs.end ()); + + for (map::iterator j = menu_items.begin(); j != menu_items.end(); ++j) { + + bool enabled = true; + + if ((j->second & NEEDS_FILM) && film == 0) { + enabled = false; + } + + if ((j->second & NOT_DURING_DCP_CREATION) && dcp_creation) { + enabled = false; + } + + j->first->Enable (enabled); + } + } + + enum { + ID_file_new = 1, + ID_file_open, + ID_file_save, + ID_file_properties, + ID_jobs_make_dcp, ++ ID_jobs_make_kdms, + ID_jobs_send_dcp_to_tms, + ID_jobs_show_dcp, + }; + + void + setup_menu (wxMenuBar* m) + { + wxMenu* file = new wxMenu; + add_item (file, _("New..."), ID_file_new, ALWAYS); + add_item (file, _("&Open..."), ID_file_open, ALWAYS); + file->AppendSeparator (); + add_item (file, _("&Save"), ID_file_save, NEEDS_FILM); + file->AppendSeparator (); + add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM); + #ifndef __WXOSX__ + file->AppendSeparator (); + #endif + + #ifdef __WXOSX__ + add_item (file, _("&Exit"), wxID_EXIT, ALWAYS); + #else + add_item (file, _("&Quit"), wxID_EXIT, ALWAYS); + #endif + + + #ifdef __WXOSX__ + add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS); + #else + wxMenu* edit = new wxMenu; + add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS); + #endif + + jobs_menu = new wxMenu; + add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION); ++ add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM); + add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION); + add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION); + + wxMenu* help = new wxMenu; + #ifdef __WXOSX__ + add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS); + #else + add_item (help, _("About"), wxID_ABOUT, ALWAYS); + #endif + + m->Append (file, _("&File")); + #ifndef __WXOSX__ + m->Append (edit, _("&Edit")); + #endif + m->Append (jobs_menu, _("&Jobs")); + m->Append (help, _("&Help")); + } + + class Frame : public wxFrame + { + public: + Frame (wxString const & title) + : wxFrame (NULL, -1, title) + { + wxMenuBar* bar = new wxMenuBar; + setup_menu (bar); + SetMenuBar (bar); + + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_new, this), ID_file_new); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_open, this), ID_file_open); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_save, this), ID_file_save); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_properties, this), ID_file_properties); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_exit, this), wxID_EXIT); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::edit_preferences, this), wxID_PREFERENCES); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_make_dcp, this), ID_jobs_make_dcp); ++ Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_make_kdms, this), ID_jobs_make_kdms); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_send_dcp_to_tms, this), ID_jobs_send_dcp_to_tms); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::jobs_show_dcp, this), ID_jobs_show_dcp); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::help_about, this), wxID_ABOUT); + + Bind (wxEVT_MENU_OPEN, boost::bind (&Frame::menu_opened, this, _1)); + Bind (wxEVT_CLOSE_WINDOW, boost::bind (&Frame::close, this, _1)); + + /* Use a panel as the only child of the Frame so that we avoid + the dark-grey background on Windows. + */ + wxPanel* overall_panel = new wxPanel (this, wxID_ANY); + + film_editor = new FilmEditor (film, overall_panel); + film_viewer = new FilmViewer (film, overall_panel); + JobManagerView* job_manager_view = new JobManagerView (overall_panel, static_cast (0)); + + wxBoxSizer* right_sizer = new wxBoxSizer (wxVERTICAL); + right_sizer->Add (film_viewer, 2, wxEXPAND | wxALL, 6); + right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6); + + wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL); + main_sizer->Add (film_editor, 1, wxEXPAND | wxALL, 6); + main_sizer->Add (right_sizer, 2, wxEXPAND | wxALL, 6); + + set_menu_sensitivity (); + + film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1)); + if (film) { + file_changed (film->directory ()); + } else { + file_changed (""); + } + + JobManager::instance()->ActiveJobsChanged.connect (boost::bind (set_menu_sensitivity)); + + set_film (); + overall_panel->SetSizer (main_sizer); + } + + private: + + void menu_opened (wxMenuEvent& ev) + { + if (ev.GetMenu() != jobs_menu) { + return; + } + - bool const have_dcp = film && film->have_dcp(); ++ bool const have_dcp = false;//film && film->have_dcp(); + jobs_menu->Enable (ID_jobs_send_dcp_to_tms, have_dcp); + jobs_menu->Enable (ID_jobs_show_dcp, have_dcp); + } + + void set_film () + { + film_viewer->set_film (film); + film_editor->set_film (film); + set_menu_sensitivity (); + } + + void file_changed (string f) + { + stringstream s; + s << wx_to_std (_("DCP-o-matic")); + if (!f.empty ()) { + s << " - " << f; + } + + SetTitle (std_to_wx (s.str())); + } + + void file_new () + { + NewFilmDialog* d = new NewFilmDialog (this); + int const r = d->ShowModal (); + + if (r == wxID_OK) { + + if (boost::filesystem::is_directory (d->get_path()) && !boost::filesystem::is_empty(d->get_path())) { + if (!confirm_dialog ( + this, + std_to_wx ( + String::compose (wx_to_std (_("The directory %1 already exists and is not empty. " + "Are you sure you want to use it?")), + d->get_path().c_str()) + ) + )) { + return; + } + } else if (boost::filesystem::is_regular_file (d->get_path())) { + error_dialog ( + this, + String::compose (wx_to_std (_("%1 already exists as a file, so you cannot use it for a new film.")), d->get_path().c_str()) + ); + return; + } + + maybe_save_then_delete_film (); + film.reset (new Film (d->get_path ())); + film->write_metadata (); + film->log()->set_level (log_level); + film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string()); + set_film (); + } + + d->Destroy (); + } + + void file_open () + { + wxDirDialog* c = new wxDirDialog (this, _("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST); + int r; + while (1) { + r = c->ShowModal (); + if (r == wxID_OK && c->GetPath() == wxStandardPaths::Get().GetDocumentsDir()) { + error_dialog (this, _("You did not select a folder. Make sure that you select a folder before clicking Open.")); + } else { + break; + } + } + + if (r == wxID_OK) { + maybe_save_then_delete_film (); + try { + film.reset (new Film (wx_to_std (c->GetPath ()))); + film->read_metadata (); + film->log()->set_level (log_level); + set_film (); + } catch (std::exception& e) { + wxString p = c->GetPath (); + wxCharBuffer b = p.ToUTF8 (); + error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data())); + } + } + + c->Destroy (); + } + + void file_save () + { + film->write_metadata (); + } + + void file_properties () + { + PropertiesDialog* d = new PropertiesDialog (this, film); + d->ShowModal (); + d->Destroy (); + } + + void file_exit () + { + if (!should_close ()) { + return; + } + + maybe_save_then_delete_film (); + Close (true); + } + + void edit_preferences () + { + ConfigDialog* d = new ConfigDialog (this); + d->ShowModal (); + d->Destroy (); + Config::instance()->write (); + } + + void jobs_make_dcp () + { + JobWrapper::make_dcp (this, film); + } ++ ++ void jobs_make_kdms () ++ { ++ if (!film) { ++ return; ++ } ++ ++ KDMDialog* d = new KDMDialog (this); ++ if (d->ShowModal () == wxID_OK) { ++ try { ++ film->make_kdms ( ++ d->screens (), ++ d->from (), ++ d->until (), ++ d->directory () ++ ); ++ } catch (KDMError& e) { ++ error_dialog (this, e.what ()); ++ } ++ } ++ ++ d->Destroy (); ++ } + + void jobs_send_dcp_to_tms () + { + film->send_dcp_to_tms (); + } + + void jobs_show_dcp () + { + #ifdef __WXMSW__ + string d = film->directory(); + wstring w; + w.assign (d.begin(), d.end()); + ShellExecute (0, L"open", w.c_str(), 0, 0, SW_SHOWDEFAULT); + #else + int r = system ("which nautilus"); + if (WEXITSTATUS (r) == 0) { + r = system (string ("nautilus " + film->directory()).c_str ()); + if (WEXITSTATUS (r)) { + error_dialog (this, _("Could not show DCP (could not run nautilus)")); + } + } else { + int r = system ("which konqueror"); + if (WEXITSTATUS (r) == 0) { + r = system (string ("konqueror " + film->directory()).c_str ()); + if (WEXITSTATUS (r)) { + error_dialog (this, _("Could not show DCP (could not run konqueror)")); + } + } + } + #endif + } + + void help_about () + { + AboutDialog* d = new AboutDialog (this); + d->ShowModal (); + d->Destroy (); + } + + bool should_close () + { + if (!JobManager::instance()->work_to_do ()) { + return true; + } + + wxMessageDialog* d = new wxMessageDialog ( + 0, + _("There are unfinished jobs; are you sure you want to quit?"), + _("Unfinished jobs"), + wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION + ); + + bool const r = d->ShowModal() == wxID_YES; + d->Destroy (); + return r; + } + + void close (wxCloseEvent& ev) + { + if (!should_close ()) { + ev.Veto (); + return; + } + + ev.Skip (); + } + }; + + #if wxMINOR_VERSION == 9 + static const wxCmdLineEntryDesc command_line_description[] = { + { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 } + }; + #else + static const wxCmdLineEntryDesc command_line_description[] = { + { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 } + }; + #endif + + class App : public wxApp + { + bool OnInit () + try + { + if (!wxApp::OnInit()) { + return false; + } + + #ifdef DCPOMATIC_LINUX + unsetenv ("UBUNTU_MENUPROXY"); + #endif + + #ifdef __WXOSX__ + ProcessSerialNumber serial; + GetCurrentProcess (&serial); + TransformProcessType (&serial, kProcessTransformToForegroundApplication); + #endif + + wxInitAllImageHandlers (); + + /* Enable i18n; this will create a Config object + to look for a force-configured language. This Config + object will be wrong, however, because dcpomatic_setup + hasn't yet been called and there aren't any scalers, filters etc. + set up yet. + */ + dcpomatic_setup_i18n (); + + /* Set things up, including scalers / filters etc. + which will now be internationalised correctly. + */ + dcpomatic_setup (); + + /* Force the configuration to be re-loaded correctly next + time it is needed. + */ + Config::drop (); + + if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) { + try { + film.reset (new Film (film_to_load)); + film->read_metadata (); + film->log()->set_level (log_level); + } catch (exception& e) { + error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what()))); + } + } + + if (!film_to_create.empty ()) { + film.reset (new Film (film_to_create)); + film->write_metadata (); + film->log()->set_level (log_level); + film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ()); + } + + Frame* f = new Frame (_("DCP-o-matic")); + SetTopWindow (f); + f->Maximize (); + f->Show (); + + ui_signaller = new wxUISignaller (this); + this->Bind (wxEVT_IDLE, boost::bind (&App::idle, this)); + + return true; + } + catch (exception& e) + { + error_dialog (0, wxString::Format ("DCP-o-matic could not start: %s", e.what ())); + return true; + } + + void OnInitCmdLine (wxCmdLineParser& parser) + { + parser.SetDesc (command_line_description); + parser.SetSwitchChars (wxT ("-")); + } + + bool OnCmdLineParsed (wxCmdLineParser& parser) + { + if (parser.GetParamCount() > 0) { + if (parser.Found (wxT ("new"))) { + film_to_create = wx_to_std (parser.GetParam (0)); + } else { + film_to_load = wx_to_std (parser.GetParam(0)); + } + } + + wxString log; + if (parser.Found (wxT ("log"), &log)) { + log_level = wx_to_std (log); + } + + return true; + } + + void idle () + { + ui_signaller->ui_idle (); + } + }; + + IMPLEMENT_APP (App) diff --combined src/wx/cinema_dialog.cc index 9e3b1507d,000000000..2c0b0b4a4 mode 100644,000000..100644 --- a/src/wx/cinema_dialog.cc +++ b/src/wx/cinema_dialog.cc @@@ -1,62 -1,0 +1,62 @@@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "cinema_dialog.h" +#include "wx_util.h" + +using std::string; + +CinemaDialog::CinemaDialog (wxWindow* parent, string title, string name, string email) + : wxDialog (parent, wxID_ANY, std_to_wx (title)) +{ + wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6); + table->AddGrowableCol (1, 1); + - add_label_to_sizer (table, this, "Name"); ++ add_label_to_sizer (table, this, "Name", true); + _name = new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (256, -1)); + table->Add (_name, 1, wxEXPAND); + - add_label_to_sizer (table, this, "Email address for KDM delivery"); ++ add_label_to_sizer (table, this, "Email address for KDM delivery", true); + _email = new wxTextCtrl (this, wxID_ANY, std_to_wx (email), wxDefaultPosition, wxSize (256, -1)); + table->Add (_email, 1, wxEXPAND); + + wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); + overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6); + + wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); + if (buttons) { + overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + SetSizer (overall_sizer); + overall_sizer->Layout (); + overall_sizer->SetSizeHints (this); +} + +string +CinemaDialog::name () const +{ + return wx_to_std (_name->GetValue()); +} + +string +CinemaDialog::email () const +{ + return wx_to_std (_email->GetValue()); +} diff --combined src/wx/film_editor.cc index 9326227a3,bcc63c735..56b697375 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@@ -1,5 -1,5 +1,5 @@@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2013 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@@ -25,26 -25,33 +25,33 @@@ #include #include #include + #include #include #include #include - #include "lib/format.h" #include "lib/film.h" #include "lib/transcode_job.h" #include "lib/exceptions.h" - #include "lib/ab_transcode_job.h" #include "lib/job_manager.h" #include "lib/filter.h" + #include "lib/ratio.h" #include "lib/config.h" - #include "lib/ffmpeg_decoder.h" - #include "lib/external_audio_decoder.h" - #include "filter_dialog.h" + #include "lib/still_image_content.h" + #include "lib/moving_image_content.h" + #include "lib/ffmpeg_content.h" + #include "lib/sndfile_content.h" + #include "lib/dcp_content_type.h" + #include "lib/sound_processor.h" + #include "lib/scaler.h" + #include "timecode.h" #include "wx_util.h" #include "film_editor.h" - #include "gain_calculator_dialog.h" - #include "sound_processor.h" - #include "dci_name_dialog.h" - #include "scaler.h" + #include "dci_metadata_dialog.h" + #include "timeline_dialog.h" + #include "timing_panel.h" + #include "subtitle_panel.h" + #include "audio_panel.h" + #include "video_panel.h" using std::string; using std::cout; @@@ -54,533 -61,294 +61,309 @@@ using std::fixed using std::setprecision; using std::list; using std::vector; + using std::max; using boost::shared_ptr; + using boost::weak_ptr; using boost::dynamic_pointer_cast; + using boost::lexical_cast; /** @param f Film to edit */ FilmEditor::FilmEditor (shared_ptr f, wxWindow* parent) : wxPanel (parent) - , _film (f) + , _menu (f, this) , _generally_sensitive (true) + , _timeline_dialog (0) { wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); - SetSizer (s); - _notebook = new wxNotebook (this, wxID_ANY); - s->Add (_notebook, 1); - - make_film_panel (); - _notebook->AddPage (_film_panel, _("Film"), true); - make_video_panel (); - _notebook->AddPage (_video_panel, _("Video"), false); - make_audio_panel (); - _notebook->AddPage (_audio_panel, _("Audio"), false); - make_subtitle_panel (); - _notebook->AddPage (_subtitle_panel, _("Subtitles"), false); - - set_film (_film); + + _main_notebook = new wxNotebook (this, wxID_ANY); + s->Add (_main_notebook, 1); + + make_content_panel (); + _main_notebook->AddPage (_content_panel, _("Content"), true); + make_dcp_panel (); + _main_notebook->AddPage (_dcp_panel, _("DCP"), false); + + set_film (f); connect_to_widgets (); JobManager::instance()->ActiveJobsChanged.connect ( bind (&FilmEditor::active_jobs_changed, this, _1) ); - setup_visibility (); - setup_formats (); + SetSizerAndFit (s); } void - FilmEditor::make_film_panel () + FilmEditor::make_dcp_panel () { - _film_panel = new wxPanel (_notebook); - _film_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_film_sizer, 0, wxALL, 8); - _film_panel->SetSizer (pad); - - add_label_to_sizer (_film_sizer, _film_panel, "Name"); - _name = new wxTextCtrl (_film_panel, wxID_ANY); - _film_sizer->Add (_name, 1, wxEXPAND); - - add_label_to_sizer (_film_sizer, _film_panel, "DCP Name"); - _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK); - - _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Use DCI name")); - _film_sizer->Add (_use_dci_name, 1, wxEXPAND); - _edit_dci_button = new wxButton (_film_panel, wxID_ANY, wxT ("Details...")); - _film_sizer->Add (_edit_dci_button, 0); - - add_label_to_sizer (_film_sizer, _film_panel, "Content"); - _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*")); - _film_sizer->Add (_content, 1, wxEXPAND); - - _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header")); - video_control (_trust_content_header); - _film_sizer->Add (_trust_content_header, 1); - _film_sizer->AddSpacer (0); - - add_label_to_sizer (_film_sizer, _film_panel, "Content Type"); - _dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); - _film_sizer->Add (_dcp_content_type); - - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second")); - _frames_per_second = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL); + _dcp_panel = new wxPanel (_main_notebook); + _dcp_sizer = new wxBoxSizer (wxVERTICAL); + _dcp_panel->SetSizer (_dcp_sizer); + + wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8); + + int r = 0; - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Original Size")); - _original_size = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0)); + _name = new wxTextCtrl (_dcp_panel, wxID_ANY); + grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT); + ++r; - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Length")); - _length = new wxStaticText (_film_panel, wxID_ANY, wxT ("")); - _film_sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL); - + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, wxGBPosition (r, 0)); + _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT ("")); + grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + ++r; + + int flags = wxALIGN_CENTER_VERTICAL; + #ifdef __WXOSX__ + flags |= wxALIGN_RIGHT; + #endif + + _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name")); + grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, flags); + _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details...")); + grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan); + ++r; + + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0)); + _container = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND); + ++r; + + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, wxGBPosition (r, 0)); + _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_dcp_content_type, wxGBPosition (r, 1)); + ++r; { - video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames")); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Frame Rate"), true, wxGBPosition (r, 0)); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - video_control (add_label_to_sizer (s, _film_panel, "Start")); - _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_dcp_trim_start)); - video_control (add_label_to_sizer (s, _film_panel, "End")); - _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (video_control (_dcp_trim_end)); - - _film_sizer->Add (s); + _frame_rate = new wxChoice (_dcp_panel, wxID_ANY); + s->Add (_frame_rate, 1, wxALIGN_CENTER_VERTICAL); + _best_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best")); + s->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); + grid->Add (s, wxGBPosition (r, 1)); } + ++r; - _encrypted = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Encrypted")); - _film_sizer->Add (_encrypted, 1); - _film_sizer->AddSpacer (0); ++ _encrypted = new wxCheckBox (_dcp_panel, wxID_ANY, wxT ("Encrypted")); ++ grid->Add (_encrypted, wxGBPosition (r, 0), wxGBSpan (1, 2)); ++ ++r; + - _multiple_reels = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Make multiple reels")); - _film_sizer->Add (_multiple_reels); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Audio channels"), true, wxGBPosition (r, 0)); + _audio_channels = new wxSpinCtrl (_dcp_panel, wxID_ANY); + grid->Add (_audio_channels, wxGBPosition (r, 1)); + ++r; - { - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _reel_size = new wxSpinCtrl (_film_panel, wxID_ANY); - s->Add (_reel_size); - add_label_to_sizer (s, _film_panel, "Gb each"); - _film_sizer->Add (s); - } + _three_d = new wxCheckBox (_dcp_panel, wxID_ANY, _("3D")); + grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2)); + ++r; - _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, wxT ("A/B")); - video_control (_dcp_ab); - _film_sizer->Add (_dcp_ab, 1); - _film_sizer->AddSpacer (0); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Resolution"), true, wxGBPosition (r, 0)); + _resolution = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_resolution, wxGBPosition (r, 1)); + ++r; - /* STILL-only stuff */ { - still_control (add_label_to_sizer (_film_sizer, _film_panel, "Duration")); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0)); wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _still_duration = new wxSpinCtrl (_film_panel); - still_control (_still_duration); - s->Add (_still_duration, 1, wxEXPAND); - still_control (add_label_to_sizer (s, _film_panel, "s")); - _film_sizer->Add (s); - } - - vector const ct = DCPContentType::all (); - for (vector::const_iterator i = ct.begin(); i != ct.end(); ++i) { - _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ())); - } - - _reel_size->SetRange(1, 1000); - } - - void - FilmEditor::connect_to_widgets () - { - _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this); - _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this); - _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this); - _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this); - _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this); - _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this); - _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this); - _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this); - _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this); - _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this); - _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this); - _scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this); - _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this); - _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this); - _encrypted->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::encrypted_toggled), 0, this); - _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this); - _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this); - _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this); - _multiple_reels->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::multiple_reels_toggled), 0, this); - _reel_size->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::reel_size_changed), 0, this); - _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this); - _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this); - _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this); - _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this); - _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this); - _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this); - _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this); - _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this); - _audio_gain_calculate_button->Connect ( - wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this - ); - _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this); - _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this); - _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this); - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - _external_audio[i]->Connect ( - wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this - ); - } - } - - void - FilmEditor::make_video_panel () - { - _video_panel = new wxPanel (_notebook); - _video_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_video_sizer, 0, wxALL, 8); - _video_panel->SetSizer (pad); - - add_label_to_sizer (_video_sizer, _video_panel, "Format"); - _format = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); - _video_sizer->Add (_format); - - { - add_label_to_sizer (_video_sizer, _video_panel, "Crop"); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - - add_label_to_sizer (s, _video_panel, "L"); - _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_left_crop, 0); - add_label_to_sizer (s, _video_panel, "R"); - _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_right_crop, 0); - add_label_to_sizer (s, _video_panel, "T"); - _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_top_crop, 0); - add_label_to_sizer (s, _video_panel, "B"); - _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_bottom_crop, 0); - - _video_sizer->Add (s); + _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY); + s->Add (_j2k_bandwidth, 1); + add_label_to_sizer (s, _dcp_panel, _("MBps"), false); + grid->Add (s, wxGBPosition (r, 1)); } + ++r; - /* VIDEO-only stuff */ - { - video_control (add_label_to_sizer (_video_sizer, _video_panel, "Filters")); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _filters = new wxStaticText (_video_panel, wxID_ANY, wxT ("None")); - video_control (_filters); - s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6); - _filters_button = new wxButton (_video_panel, wxID_ANY, wxT ("Edit...")); - video_control (_filters_button); - s->Add (_filters_button, 0); - _video_sizer->Add (s, 1); - } + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Standard"), true, wxGBPosition (r, 0)); + _standard = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_standard, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + ++r; - video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler")); - _scaler = new wxComboBox (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); - _video_sizer->Add (video_control (_scaler), 1); + add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), true, wxGBPosition (r, 0)); + _scaler = new wxChoice (_dcp_panel, wxID_ANY); + grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + ++r; vector const sc = Scaler::all (); for (vector::const_iterator i = sc.begin(); i != sc.end(); ++i) { _scaler->Append (std_to_wx ((*i)->name())); } - add_label_to_sizer (_video_sizer, _video_panel, "Colour look-up table"); - _colour_lut = new wxComboBox (_video_panel, wxID_ANY); - for (int i = 0; i < 2; ++i) { - _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i))); - } - _colour_lut->SetSelection (0); - _video_sizer->Add (_colour_lut, 1, wxEXPAND); - - { - add_label_to_sizer (_video_sizer, _video_panel, "JPEG2000 bandwidth"); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY); - s->Add (_j2k_bandwidth, 1); - add_label_to_sizer (s, _video_panel, "MBps"); - _video_sizer->Add (s, 1); + vector const ratio = Ratio::all (); + for (vector::const_iterator i = ratio.begin(); i != ratio.end(); ++i) { + _container->Append (std_to_wx ((*i)->nickname ())); } - _left_crop->SetRange (0, 1024); - _top_crop->SetRange (0, 1024); - _right_crop->SetRange (0, 1024); - _bottom_crop->SetRange (0, 1024); - _still_duration->SetRange (1, 60 * 60); - _dcp_trim_start->SetRange (0, 100); - _dcp_trim_end->SetRange (0, 100); - _j2k_bandwidth->SetRange (50, 250); - } - - void - FilmEditor::make_audio_panel () - { - _audio_panel = new wxPanel (_notebook); - _audio_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_audio_sizer, 0, wxALL, 8); - _audio_panel->SetSizer (pad); - - { - video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Gain")); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _audio_gain = new wxSpinCtrl (_audio_panel); - s->Add (video_control (_audio_gain), 1); - video_control (add_label_to_sizer (s, _audio_panel, "dB")); - _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate...")); - video_control (_audio_gain_calculate_button); - s->Add (_audio_gain_calculate_button, 1, wxEXPAND); - _audio_sizer->Add (s); + vector const ct = DCPContentType::all (); + for (vector::const_iterator i = ct.begin(); i != ct.end(); ++i) { + _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ())); } - { - video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Delay")); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _audio_delay = new wxSpinCtrl (_audio_panel); - s->Add (video_control (_audio_delay), 1); - video_control (add_label_to_sizer (s, _audio_panel, "ms")); - _audio_sizer->Add (s); + list const dfr = Config::instance()->allowed_dcp_frame_rates (); + for (list::const_iterator i = dfr.begin(); i != dfr.end(); ++i) { + _frame_rate->Append (std_to_wx (boost::lexical_cast (*i))); } - { - _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); - _audio_sizer->Add (video_control (_use_content_audio)); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _audio_stream = new wxComboBox (_audio_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); - s->Add (video_control (_audio_stream), 1); - _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT ("")); - s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8); - _audio_sizer->Add (s, 1, wxEXPAND); - } + _audio_channels->SetRange (0, MAX_AUDIO_CHANNELS); + _j2k_bandwidth->SetRange (1, 250); - _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio")); - _audio_sizer->Add (_use_external_audio); - _audio_sizer->AddSpacer (0); - - assert (MAX_AUDIO_CHANNELS == 6); - - char const * channels[] = { - "Left", - "Right", - "Centre", - "Lfe (sub)", - "Left surround", - "Right surround" - }; - - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]); - _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav")); - _audio_sizer->Add (_external_audio[i], 1, wxEXPAND); - } + _resolution->Append (_("2K")); + _resolution->Append (_("4K")); - _audio_gain->SetRange (-60, 60); - _audio_delay->SetRange (-1000, 1000); + _standard->Append (_("SMPTE")); + _standard->Append (_("Interop")); } void - FilmEditor::make_subtitle_panel () + FilmEditor::connect_to_widgets () { - _subtitle_panel = new wxPanel (_notebook); - _subtitle_sizer = new wxFlexGridSizer (2, 4, 4); - wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL); - pad->Add (_subtitle_sizer, 0, wxALL, 8); - _subtitle_panel->SetSizer (pad); - - _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, wxT("With Subtitles")); - video_control (_with_subtitles); - _subtitle_sizer->Add (_with_subtitles, 1); - - _subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY); - _subtitle_sizer->Add (video_control (_subtitle_stream)); - - video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset")); - _subtitle_offset = new wxSpinCtrl (_subtitle_panel); - _subtitle_sizer->Add (video_control (_subtitle_offset), 1); + _name->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&FilmEditor::name_changed, this)); + _use_dci_name->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::use_dci_name_toggled, this)); + _edit_dci_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::edit_dci_button_clicked, this)); + _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::container_changed, this)); + _content->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&FilmEditor::content_selection_changed, this)); + _content->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&FilmEditor::content_selection_changed, this)); + _content->Bind (wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, boost::bind (&FilmEditor::content_right_click, this, _1)); + _content_add_file->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_add_file_clicked, this)); + _content_add_folder->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_add_folder_clicked, this)); + _content_remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_remove_clicked, this)); + _content_timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_timeline_clicked, this)); + _scaler->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::scaler_changed, this)); + _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::dcp_content_type_changed, this)); + _frame_rate->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::frame_rate_changed, this)); + _best_frame_rate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::best_frame_rate_clicked, this)); + _audio_channels->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&FilmEditor::audio_channels_changed, this)); + _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&FilmEditor::j2k_bandwidth_changed, this)); + _resolution->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::resolution_changed, this)); + _sequence_video->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::sequence_video_changed, this)); + _three_d->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::three_d_changed, this)); + _standard->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::standard_changed, this)); + } + + void + FilmEditor::make_content_panel () + { + _content_panel = new wxPanel (_main_notebook); + _content_sizer = new wxBoxSizer (wxVERTICAL); + _content_panel->SetSizer (_content_sizer); { - video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Scale")); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _subtitle_scale = new wxSpinCtrl (_subtitle_panel); - s->Add (video_control (_subtitle_scale)); - video_control (add_label_to_sizer (s, _subtitle_panel, "%")); - _subtitle_sizer->Add (s); - } - - _subtitle_offset->SetRange (-1024, 1024); - _subtitle_scale->SetRange (1, 1000); - } - - /** Called when the left crop widget has been changed */ - void - FilmEditor::left_crop_changed (wxCommandEvent &) - { - if (!_film) { - return; - } - - _film->set_left_crop (_left_crop->GetValue ()); - } - - /** Called when the right crop widget has been changed */ - void - FilmEditor::right_crop_changed (wxCommandEvent &) - { - if (!_film) { - return; - } - - _film->set_right_crop (_right_crop->GetValue ()); - } - - /** Called when the top crop widget has been changed */ - void - FilmEditor::top_crop_changed (wxCommandEvent &) - { - if (!_film) { - return; - } + + _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL); + s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6); - _film->set_top_crop (_top_crop->GetValue ()); - } + _content->InsertColumn (0, wxT("")); + _content->SetColumnWidth (0, 512); - /** Called when the bottom crop value has been changed */ - void - FilmEditor::bottom_crop_changed (wxCommandEvent &) - { - if (!_film) { - return; - } + wxBoxSizer* b = new wxBoxSizer (wxVERTICAL); + _content_add_file = new wxButton (_content_panel, wxID_ANY, _("Add file(s)...")); + b->Add (_content_add_file, 1, wxEXPAND | wxLEFT | wxRIGHT); + _content_add_folder = new wxButton (_content_panel, wxID_ANY, _("Add folder...")); + b->Add (_content_add_folder, 1, wxEXPAND | wxLEFT | wxRIGHT); + _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove")); + b->Add (_content_remove, 1, wxEXPAND | wxLEFT | wxRIGHT); + _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline...")); + b->Add (_content_timeline, 1, wxEXPAND | wxLEFT | wxRIGHT); - _film->set_bottom_crop (_bottom_crop->GetValue ()); - } + s->Add (b, 0, wxALL, 4); - /** Called when the content filename has been changed */ - void - FilmEditor::content_changed (wxCommandEvent &) - { - if (!_film) { - return; + _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6); } - try { - _film->set_content (wx_to_std (_content->GetPath ())); - } catch (std::exception& e) { - _content->SetPath (std_to_wx (_film->directory ())); - error_dialog (this, String::compose ("Could not set content: %1", e.what ())); - } - } + _sequence_video = new wxCheckBox (_content_panel, wxID_ANY, _("Keep video in sequence")); + _content_sizer->Add (_sequence_video); - void - FilmEditor::trust_content_header_changed (wxCommandEvent &) - { - if (!_film) { - return; - } + _content_notebook = new wxNotebook (_content_panel, wxID_ANY); + _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6); - _film->set_trust_content_header (_trust_content_header->GetValue ()); - } - - void - FilmEditor::multiple_reels_toggled (wxCommandEvent &) - { - if (!_film) { - return; - } - - if (_multiple_reels->GetValue()) { - _film->set_reel_size (_reel_size->GetValue() * 1e9); - } else { - _film->unset_reel_size (); - } - - setup_reel_control_sensitivity (); + _video_panel = new VideoPanel (this); + _panels.push_back (_video_panel); + _audio_panel = new AudioPanel (this); + _panels.push_back (_audio_panel); + _subtitle_panel = new SubtitlePanel (this); + _panels.push_back (_subtitle_panel); + _timing_panel = new TimingPanel (this); + _panels.push_back (_timing_panel); } + /** Called when the name widget has been changed */ void - FilmEditor::reel_size_changed (wxCommandEvent &) + FilmEditor::name_changed () { if (!_film) { return; } - _film->set_reel_size (static_cast (_reel_size->GetValue()) * 1e9); + _film->set_name (string (_name->GetValue().mb_str())); } - /** Called when the DCP A/B switch has been toggled */ void - FilmEditor::dcp_ab_toggled (wxCommandEvent &) + FilmEditor::j2k_bandwidth_changed () { if (!_film) { return; } - _film->set_dcp_ab (_dcp_ab->GetValue ()); + _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1e6); } +void - FilmEditor::encrypted_toggled (wxCommandEvent &) ++FilmEditor::encrypted_toggled () +{ + if (!_film) { + return; + } + + _film->set_encrypted (_encrypted->GetValue ()); +} + +/** Called when the name widget has been changed */ void - FilmEditor::name_changed (wxCommandEvent &) + FilmEditor::frame_rate_changed () { if (!_film) { return; } - _film->set_name (string (_name->GetValue().mb_str())); + _film->set_video_frame_rate ( + boost::lexical_cast ( + wx_to_std (_frame_rate->GetString (_frame_rate->GetSelection ())) + ) + ); } void - FilmEditor::subtitle_offset_changed (wxCommandEvent &) + FilmEditor::audio_channels_changed () { if (!_film) { return; } - _film->set_subtitle_offset (_subtitle_offset->GetValue ()); + _film->set_audio_channels (_audio_channels->GetValue ()); } void - FilmEditor::subtitle_scale_changed (wxCommandEvent &) + FilmEditor::resolution_changed () { if (!_film) { return; } - _film->set_subtitle_scale (_subtitle_scale->GetValue() / 100.0); - } - - void - FilmEditor::colour_lut_changed (wxCommandEvent &) - { - if (!_film) { - return; - } - - _film->set_colour_lut (_colour_lut->GetSelection ()); + _film->set_resolution (_resolution->GetSelection() == 0 ? RESOLUTION_2K : RESOLUTION_4K); } void - FilmEditor::j2k_bandwidth_changed (wxCommandEvent &) + FilmEditor::standard_changed () { if (!_film) { return; } - - _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1e6); - } + _film->set_interop (_standard->GetSelection() == 1); + } /** Called when the metadata stored in the Film object has changed; * so that we can update the GUI. @@@ -596,201 -364,148 +379,151 @@@ FilmEditor::film_changed (Film::Propert } stringstream s; + + for (list::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->film_changed (p); + } switch (p) { case Film::NONE: break; case Film::CONTENT: - checked_set (_content, _film->content ()); - setup_visibility (); - setup_formats (); - setup_subtitle_control_sensitivity (); - setup_streams (); + setup_content (); break; - case Film::TRUST_CONTENT_HEADER: - checked_set (_trust_content_header, _film->trust_content_header ()); + case Film::CONTAINER: + setup_container (); break; - case Film::SUBTITLE_STREAMS: - setup_subtitle_control_sensitivity (); - setup_streams (); - break; - case Film::CONTENT_AUDIO_STREAMS: - setup_streams (); - break; - case Film::FORMAT: - { - int n = 0; - vector::iterator i = _formats.begin (); - while (i != _formats.end() && *i != _film->format ()) { - ++i; - ++n; - } - if (i == _formats.end()) { - checked_set (_format, -1); - } else { - checked_set (_format, n); - } - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); - break; - } - case Film::CROP: - checked_set (_left_crop, _film->crop().left); - checked_set (_right_crop, _film->crop().right); - checked_set (_top_crop, _film->crop().top); - checked_set (_bottom_crop, _film->crop().bottom); - break; - case Film::FILTERS: - { - pair p = Filter::ffmpeg_strings (_film->filters ()); - if (p.first.empty () && p.second.empty ()) { - _filters->SetLabel (_("None")); - } else { - string const b = p.first + " " + p.second; - _filters->SetLabel (std_to_wx (b)); - } - _film_sizer->Layout (); - break; - } case Film::NAME: checked_set (_name, _film->name()); - _film->set_dci_date_today (); - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); - break; - case Film::FRAMES_PER_SECOND: - s << fixed << setprecision(2) << _film->frames_per_second(); - _frames_per_second->SetLabel (std_to_wx (s.str ())); + setup_dcp_name (); break; - case Film::SIZE: - if (_film->size().width == 0 && _film->size().height == 0) { - _original_size->SetLabel (wxT ("")); - } else { - s << _film->size().width << " x " << _film->size().height; - _original_size->SetLabel (std_to_wx (s.str ())); - } - break; - case Film::LENGTH: - if (_film->frames_per_second() > 0 && _film->length()) { - s << _film->length().get() << " frames; " << seconds_to_hms (_film->length().get() / _film->frames_per_second()); - } else if (_film->length()) { - s << _film->length().get() << " frames"; - } - _length->SetLabel (std_to_wx (s.str ())); - if (_film->length()) { - _dcp_trim_start->SetRange (0, _film->length().get()); - _dcp_trim_end->SetRange (0, _film->length().get()); - } + case Film::WITH_SUBTITLES: + setup_dcp_name (); break; case Film::DCP_CONTENT_TYPE: checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ())); - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); - break; - case Film::DCP_AB: - checked_set (_dcp_ab, _film->dcp_ab ()); + setup_dcp_name (); break; case Film::SCALER: checked_set (_scaler, Scaler::as_index (_film->scaler ())); break; - case Film::DCP_TRIM_START: - checked_set (_dcp_trim_start, _film->dcp_trim_start()); - break; - case Film::DCP_TRIM_END: - checked_set (_dcp_trim_end, _film->dcp_trim_end()); - break; - case Film::REEL_SIZE: - if (_film->reel_size()) { - checked_set (_multiple_reels, true); - checked_set (_reel_size, _film->reel_size().get() / 1e9); - } else { - checked_set (_multiple_reels, false); - } - setup_reel_control_sensitivity (); - break; - case Film::AUDIO_GAIN: - checked_set (_audio_gain, _film->audio_gain ()); - break; - case Film::AUDIO_DELAY: - checked_set (_audio_delay, _film->audio_delay ()); - break; - case Film::STILL_DURATION: - checked_set (_still_duration, _film->still_duration ()); - break; - case Film::WITH_SUBTITLES: - checked_set (_with_subtitles, _film->with_subtitles ()); - setup_subtitle_control_sensitivity (); - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); - break; - case Film::SUBTITLE_OFFSET: - checked_set (_subtitle_offset, _film->subtitle_offset ()); - break; - case Film::SUBTITLE_SCALE: - checked_set (_subtitle_scale, _film->subtitle_scale() * 100); - break; + case Film::ENCRYPTED: + checked_set (_encrypted, _film->encrypted ()); + break; - case Film::COLOUR_LUT: - checked_set (_colour_lut, _film->colour_lut ()); + case Film::RESOLUTION: + checked_set (_resolution, _film->resolution() == RESOLUTION_2K ? 0 : 1); + setup_dcp_name (); break; case Film::J2K_BANDWIDTH: checked_set (_j2k_bandwidth, double (_film->j2k_bandwidth()) / 1e6); break; case Film::USE_DCI_NAME: checked_set (_use_dci_name, _film->use_dci_name ()); - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); + setup_dcp_name (); break; case Film::DCI_METADATA: - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); + setup_dcp_name (); break; - case Film::CONTENT_AUDIO_STREAM: - if (_film->content_audio_stream()) { - checked_set (_audio_stream, _film->content_audio_stream()->to_string()); + case Film::VIDEO_FRAME_RATE: + { + bool done = false; + for (unsigned int i = 0; i < _frame_rate->GetCount(); ++i) { + if (wx_to_std (_frame_rate->GetString(i)) == boost::lexical_cast (_film->video_frame_rate())) { + checked_set (_frame_rate, i); + done = true; + break; + } + } + + if (!done) { + checked_set (_frame_rate, -1); } - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); - setup_audio_details (); - setup_audio_control_sensitivity (); + + _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ()); break; - case Film::USE_CONTENT_AUDIO: - checked_set (_use_content_audio, _film->use_content_audio()); - checked_set (_use_external_audio, !_film->use_content_audio()); - _dcp_name->SetLabel (std_to_wx (_film->dcp_name ())); - setup_audio_details (); - setup_audio_control_sensitivity (); + } + case Film::AUDIO_CHANNELS: + _audio_channels->SetValue (_film->audio_channels ()); + setup_dcp_name (); break; - case Film::SUBTITLE_STREAM: - if (_film->subtitle_stream()) { - checked_set (_subtitle_stream, _film->subtitle_stream()->to_string()); - } + case Film::SEQUENCE_VIDEO: + checked_set (_sequence_video, _film->sequence_video ()); break; - case Film::EXTERNAL_AUDIO: - { - vector a = _film->external_audio (); - for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) { - checked_set (_external_audio[i], a[i]); - } - setup_audio_details (); + case Film::THREE_D: + checked_set (_three_d, _film->three_d ()); + setup_dcp_name (); + break; + case Film::INTEROP: + checked_set (_standard, _film->interop() ? 1 : 0); break; } + } + + void + FilmEditor::film_content_changed (weak_ptr weak_content, int property) + { + ensure_ui_thread (); + + if (!_film) { + /* We call this method ourselves (as well as using it as a signal handler) + so _film can be 0. + */ + return; + } + + shared_ptr content = weak_content.lock (); + if (!content || content != selected_content ()) { + return; + } + + for (list::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->film_content_changed (content, property); + } + + if (property == FFmpegContentProperty::AUDIO_STREAM) { + setup_dcp_name (); } } - /** Called when the format widget has been changed */ void - FilmEditor::format_changed (wxCommandEvent &) + FilmEditor::setup_container () + { + int n = 0; + vector ratios = Ratio::all (); + vector::iterator i = ratios.begin (); + while (i != ratios.end() && *i != _film->container ()) { + ++i; + ++n; + } + + if (i == ratios.end()) { + checked_set (_container, -1); + } else { + checked_set (_container, n); + } + + setup_dcp_name (); + } + + /** Called when the container widget has been changed */ + void + FilmEditor::container_changed () { if (!_film) { return; } - int const n = _format->GetSelection (); + int const n = _container->GetSelection (); if (n >= 0) { - assert (n < int (_formats.size())); - _film->set_format (_formats[n]); + vector ratios = Ratio::all (); + assert (n < int (ratios.size())); + _film->set_container (ratios[n]); } } /** Called when the DCP content type widget has been changed */ void - FilmEditor::dcp_content_type_changed (wxCommandEvent &) + FilmEditor::dcp_content_type_changed () { if (!_film) { return; @@@ -806,12 -521,17 +539,17 @@@ void FilmEditor::set_film (shared_ptr f) { - _film = f; + set_general_sensitivity (f != 0); - set_things_sensitive (_film != 0); + if (_film == f) { + return; + } + + _film = f; if (_film) { _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1)); + _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2)); } if (_film) { @@@ -819,93 -539,65 +557,67 @@@ } else { FileChanged (""); } - + film_changed (Film::NAME); film_changed (Film::USE_DCI_NAME); film_changed (Film::CONTENT); - film_changed (Film::TRUST_CONTENT_HEADER); film_changed (Film::DCP_CONTENT_TYPE); - film_changed (Film::FORMAT); - film_changed (Film::CROP); - film_changed (Film::FILTERS); + film_changed (Film::CONTAINER); + film_changed (Film::RESOLUTION); film_changed (Film::SCALER); - film_changed (Film::DCP_TRIM_START); - film_changed (Film::DCP_TRIM_END); - film_changed (Film::REEL_SIZE); - film_changed (Film::DCP_AB); - film_changed (Film::CONTENT_AUDIO_STREAM); - film_changed (Film::EXTERNAL_AUDIO); - film_changed (Film::USE_CONTENT_AUDIO); - film_changed (Film::AUDIO_GAIN); - film_changed (Film::AUDIO_DELAY); - film_changed (Film::STILL_DURATION); film_changed (Film::WITH_SUBTITLES); - film_changed (Film::SUBTITLE_OFFSET); - film_changed (Film::SUBTITLE_SCALE); + film_changed (Film::ENCRYPTED); - film_changed (Film::COLOUR_LUT); film_changed (Film::J2K_BANDWIDTH); film_changed (Film::DCI_METADATA); - film_changed (Film::SIZE); - film_changed (Film::LENGTH); - film_changed (Film::CONTENT_AUDIO_STREAMS); - film_changed (Film::SUBTITLE_STREAMS); - film_changed (Film::FRAMES_PER_SECOND); + film_changed (Film::VIDEO_FRAME_RATE); + film_changed (Film::AUDIO_CHANNELS); + film_changed (Film::SEQUENCE_VIDEO); + film_changed (Film::THREE_D); + film_changed (Film::INTEROP); + + if (!_film->content().empty ()) { + set_selection (_film->content().front ()); + } + + content_selection_changed (); } - /** Updates the sensitivity of lots of widgets to a given value. - * @param s true to make sensitive, false to make insensitive. - */ void - FilmEditor::set_things_sensitive (bool s) + FilmEditor::set_general_sensitivity (bool s) { _generally_sensitive = s; - + + /* Stuff in the Content / DCP tabs */ _name->Enable (s); _use_dci_name->Enable (s); _edit_dci_button->Enable (s); - _format->Enable (s); _content->Enable (s); - _trust_content_header->Enable (s); - _left_crop->Enable (s); - _right_crop->Enable (s); - _top_crop->Enable (s); - _bottom_crop->Enable (s); - _filters_button->Enable (s); - _scaler->Enable (s); - _audio_stream->Enable (s); + _content_add_file->Enable (s); + _content_add_folder->Enable (s); + _content_remove->Enable (s); + _content_timeline->Enable (s); _dcp_content_type->Enable (s); - _dcp_trim_start->Enable (s); - _dcp_trim_end->Enable (s); - _multiple_reels->Enable (s); - _reel_size->Enable (s); - _dcp_ab->Enable (s); + _encrypted->Enable (s); - _colour_lut->Enable (s); + _frame_rate->Enable (s); + _audio_channels->Enable (s); _j2k_bandwidth->Enable (s); - _audio_gain->Enable (s); - _audio_gain_calculate_button->Enable (s); - _audio_delay->Enable (s); - _still_duration->Enable (s); - - setup_subtitle_control_sensitivity (); - setup_audio_control_sensitivity (); - setup_reel_control_sensitivity (); - } + _container->Enable (s); + _best_frame_rate->Enable (s && _film && _film->best_video_frame_rate () != _film->video_frame_rate ()); + _sequence_video->Enable (s); + _resolution->Enable (s); + _scaler->Enable (s); + _three_d->Enable (s); + _standard->Enable (s); - /** Called when the `Edit filters' button has been clicked */ - void - FilmEditor::edit_filters_clicked (wxCommandEvent &) - { - FilterDialog* d = new FilterDialog (this, _film->filters()); - d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1)); - d->ShowModal (); - d->Destroy (); + /* Set the panels in the content notebook */ + for (list::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->Enable (s); + } } /** Called when the scaler widget has been changed */ void - FilmEditor::scaler_changed (wxCommandEvent &) + FilmEditor::scaler_changed () { if (!_film) { return; @@@ -918,322 -610,289 +630,289 @@@ } void - FilmEditor::audio_gain_changed (wxCommandEvent &) + FilmEditor::use_dci_name_toggled () { if (!_film) { return; } - _film->set_audio_gain (_audio_gain->GetValue ()); + _film->set_use_dci_name (_use_dci_name->GetValue ()); } void - FilmEditor::audio_delay_changed (wxCommandEvent &) + FilmEditor::edit_dci_button_clicked () { if (!_film) { return; } - _film->set_audio_delay (_audio_delay->GetValue ()); - } - - wxControl * - FilmEditor::video_control (wxControl* c) - { - _video_controls.push_back (c); - return c; - } - - wxControl * - FilmEditor::still_control (wxControl* c) - { - _still_controls.push_back (c); - return c; + DCIMetadataDialog* d = new DCIMetadataDialog (this, _film->dci_metadata ()); + d->ShowModal (); + _film->set_dci_metadata (d->dci_metadata ()); + d->Destroy (); } void - FilmEditor::setup_visibility () + FilmEditor::active_jobs_changed (bool a) { - ContentType c = VIDEO; - - if (_film) { - c = _film->content_type (); - } - - for (list::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) { - (*i)->Show (c == VIDEO); - } - - for (list::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) { - (*i)->Show (c == STILL); - } - - _notebook->InvalidateBestSize (); - - _film_sizer->Layout (); - _film_sizer->SetSizeHints (_film_panel); - _video_sizer->Layout (); - _video_sizer->SetSizeHints (_video_panel); - _audio_sizer->Layout (); - _audio_sizer->SetSizeHints (_audio_panel); - _subtitle_sizer->Layout (); - _subtitle_sizer->SetSizeHints (_subtitle_panel); - - _notebook->Fit (); - Fit (); + set_general_sensitivity (!a); } void - FilmEditor::still_duration_changed (wxCommandEvent &) + FilmEditor::setup_dcp_name () { - if (!_film) { - return; + string s = _film->dcp_name (true); + if (s.length() > 28) { + _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("...")); + _dcp_name->SetToolTip (std_to_wx (s)); + } else { + _dcp_name->SetLabel (std_to_wx (s)); } - - _film->set_still_duration (_still_duration->GetValue ()); } void - FilmEditor::dcp_trim_start_changed (wxCommandEvent &) + FilmEditor::best_frame_rate_clicked () { if (!_film) { return; } - - _film->set_dcp_trim_start (_dcp_trim_start->GetValue ()); + + _film->set_video_frame_rate (_film->best_video_frame_rate ()); } void - FilmEditor::dcp_trim_end_changed (wxCommandEvent &) + FilmEditor::setup_content () { - if (!_film) { - return; + string selected_summary; + int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s != -1) { + selected_summary = wx_to_std (_content->GetItemText (s)); + } + + _content->DeleteAllItems (); + + ContentList content = _film->content (); + for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { + int const t = _content->GetItemCount (); + _content->InsertItem (t, std_to_wx ((*i)->summary ())); + if ((*i)->summary() == selected_summary) { + _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } } - _film->set_dcp_trim_end (_dcp_trim_end->GetValue ()); + if (selected_summary.empty () && !content.empty ()) { + /* Select the item of content if none was selected before */ + _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } } void - FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &) + FilmEditor::content_add_file_clicked () { - GainCalculatorDialog* d = new GainCalculatorDialog (this); - d->ShowModal (); + wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE); + int const r = d->ShowModal (); + d->Destroy (); - if (d->wanted_fader() == 0 || d->actual_fader() == 0) { - d->Destroy (); + if (r != wxID_OK) { return; } - - _audio_gain->SetValue ( - Config::instance()->sound_processor()->db_for_fader_change ( - d->wanted_fader (), - d->actual_fader () - ) - ); - /* This appears to be necessary, as the change is not signalled, - I think. - */ - wxCommandEvent dummy; - audio_gain_changed (dummy); - - d->Destroy (); - } + wxArrayString paths; + d->GetPaths (paths); - void - FilmEditor::setup_formats () - { - ContentType c = VIDEO; + /* XXX: check for lots of files here and do something */ - if (_film) { - c = _film->content_type (); - } - - _formats.clear (); - - vector fmt = Format::all (); - for (vector::iterator i = fmt.begin(); i != fmt.end(); ++i) { - if (c == VIDEO && dynamic_cast (*i)) { - _formats.push_back (*i); - } else if (c == STILL && dynamic_cast (*i)) { - _formats.push_back (*i); + for (unsigned int i = 0; i < paths.GetCount(); ++i) { + boost::filesystem::path p (wx_to_std (paths[i])); + + shared_ptr c; + + if (valid_image_file (p)) { + c.reset (new StillImageContent (_film, p)); + } else if (SndfileContent::valid_file (p)) { + c.reset (new SndfileContent (_film, p)); + } else { + c.reset (new FFmpegContent (_film, p)); } - } - _format->Clear (); - for (vector::iterator i = _formats.begin(); i != _formats.end(); ++i) { - _format->Append (std_to_wx ((*i)->name ())); + _film->examine_and_add_content (c); } - - _film_sizer->Layout (); } void - FilmEditor::with_subtitles_toggled (wxCommandEvent &) + FilmEditor::content_add_folder_clicked () { - if (!_film) { + wxDirDialog* d = new wxDirDialog (this, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST); + int const r = d->ShowModal (); + d->Destroy (); + + if (r != wxID_OK) { return; } - _film->set_with_subtitles (_with_subtitles->GetValue ()); + _film->examine_and_add_content ( + shared_ptr ( + new MovingImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ()))) + ) + ); } void - FilmEditor::setup_subtitle_control_sensitivity () + FilmEditor::content_remove_clicked () { - bool h = false; - if (_generally_sensitive && _film) { - h = !_film->subtitle_streams().empty(); + shared_ptr c = selected_content (); + if (c) { + _film->remove_content (c); } - - _with_subtitles->Enable (h); - bool j = false; - if (_film) { - j = _film->with_subtitles (); - } - - _subtitle_stream->Enable (j); - _subtitle_offset->Enable (j); - _subtitle_scale->Enable (j); + content_selection_changed (); } void - FilmEditor::setup_audio_control_sensitivity () + FilmEditor::content_selection_changed () { - _use_content_audio->Enable (_generally_sensitive); - _use_external_audio->Enable (_generally_sensitive); - - bool const source = _generally_sensitive && _use_content_audio->GetValue(); - bool const external = _generally_sensitive && _use_external_audio->GetValue(); + setup_content_sensitivity (); + shared_ptr s = selected_content (); - _audio_stream->Enable (source); - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - _external_audio[i]->Enable (external); - } + /* All other sensitivity in content panels should be triggered by + one of these. + */ + film_content_changed (s, ContentProperty::POSITION); + film_content_changed (s, ContentProperty::LENGTH); + film_content_changed (s, ContentProperty::TRIM_START); + film_content_changed (s, ContentProperty::TRIM_END); + film_content_changed (s, VideoContentProperty::VIDEO_CROP); + film_content_changed (s, VideoContentProperty::VIDEO_RATIO); + film_content_changed (s, VideoContentProperty::VIDEO_FRAME_TYPE); + film_content_changed (s, VideoContentProperty::COLOUR_CONVERSION); + film_content_changed (s, AudioContentProperty::AUDIO_GAIN); + film_content_changed (s, AudioContentProperty::AUDIO_DELAY); + film_content_changed (s, AudioContentProperty::AUDIO_MAPPING); + film_content_changed (s, FFmpegContentProperty::AUDIO_STREAM); + film_content_changed (s, FFmpegContentProperty::AUDIO_STREAMS); + film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAM); + film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAMS); + film_content_changed (s, FFmpegContentProperty::FILTERS); + film_content_changed (s, SubtitleContentProperty::SUBTITLE_OFFSET); + film_content_changed (s, SubtitleContentProperty::SUBTITLE_SCALE); } + /** Set up broad sensitivity based on the type of content that is selected */ void - FilmEditor::use_dci_name_toggled (wxCommandEvent &) + FilmEditor::setup_content_sensitivity () { - if (!_film) { - return; - } + _content_add_file->Enable (_generally_sensitive); + _content_add_folder->Enable (_generally_sensitive); - _film->set_use_dci_name (_use_dci_name->GetValue ()); + shared_ptr selection = selected_content (); + + _content_remove->Enable (selection && _generally_sensitive); + _content_timeline->Enable (_generally_sensitive); + + _video_panel->Enable (selection && dynamic_pointer_cast (selection) && _generally_sensitive); + _audio_panel->Enable (selection && dynamic_pointer_cast (selection) && _generally_sensitive); + _subtitle_panel->Enable (selection && dynamic_pointer_cast (selection) && _generally_sensitive); + _timing_panel->Enable (selection && _generally_sensitive); } - void - FilmEditor::edit_dci_button_clicked (wxCommandEvent &) + shared_ptr + FilmEditor::selected_content () { - if (!_film) { - return; + int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s == -1) { + return shared_ptr (); } - DCINameDialog* d = new DCINameDialog (this, _film); - d->ShowModal (); - d->Destroy (); + ContentList c = _film->content (); + if (s < 0 || size_t (s) >= c.size ()) { + return shared_ptr (); + } + + return c[s]; } - void - FilmEditor::setup_streams () + shared_ptr + FilmEditor::selected_video_content () { - _audio_stream->Clear (); - vector > a = _film->content_audio_streams (); - for (vector >::iterator i = a.begin(); i != a.end(); ++i) { - shared_ptr ffa = dynamic_pointer_cast (*i); - assert (ffa); - _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ()))); - } - - if (_film->use_content_audio() && _film->audio_stream()) { - checked_set (_audio_stream, _film->audio_stream()->to_string()); + shared_ptr c = selected_content (); + if (!c) { + return shared_ptr (); } - _subtitle_stream->Clear (); - vector > s = _film->subtitle_streams (); - for (vector >::iterator i = s.begin(); i != s.end(); ++i) { - _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ()))); - } - if (_film->subtitle_stream()) { - checked_set (_subtitle_stream, _film->subtitle_stream()->to_string()); - } else { - _subtitle_stream->SetValue (wxT ("")); + return dynamic_pointer_cast (c); + } + + shared_ptr + FilmEditor::selected_audio_content () + { + shared_ptr c = selected_content (); + if (!c) { + return shared_ptr (); } + + return dynamic_pointer_cast (c); } - void - FilmEditor::audio_stream_changed (wxCommandEvent &) + shared_ptr + FilmEditor::selected_subtitle_content () { - if (!_film) { - return; + shared_ptr c = selected_content (); + if (!c) { + return shared_ptr (); } - _film->set_content_audio_stream ( - audio_stream_factory ( - string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())), - Film::state_version - ) - ); + return dynamic_pointer_cast (c); } void - FilmEditor::subtitle_stream_changed (wxCommandEvent &) + FilmEditor::content_timeline_clicked () { - if (!_film) { - return; + if (_timeline_dialog) { + _timeline_dialog->Destroy (); + _timeline_dialog = 0; } - - _film->set_subtitle_stream ( - subtitle_stream_factory ( - string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())), - Film::state_version - ) - ); + + _timeline_dialog = new TimelineDialog (this, _film); + _timeline_dialog->Show (); } void - FilmEditor::setup_audio_details () + FilmEditor::set_selection (weak_ptr wc) { - if (!_film->audio_stream()) { - _audio->SetLabel (wxT ("")); - } else { - stringstream s; - if (_film->audio_stream()->channels() == 1) { - s << "1 channel"; + ContentList content = _film->content (); + for (size_t i = 0; i < content.size(); ++i) { + if (content[i] == wc.lock ()) { + _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } else { - s << _film->audio_stream()->channels () << " channels"; + _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); } - s << ", " << _film->audio_stream()->sample_rate() << "Hz"; - _audio->SetLabel (std_to_wx (s.str ())); } } void - FilmEditor::active_jobs_changed (bool a) + FilmEditor::sequence_video_changed () { - set_things_sensitive (!a); + if (!_film) { + return; + } + + _film->set_sequence_video (_sequence_video->GetValue ()); } void - FilmEditor::use_audio_changed (wxCommandEvent &) + FilmEditor::content_right_click (wxListEvent& ev) { - _film->set_use_content_audio (_use_content_audio->GetValue()); + ContentList cl; + cl.push_back (selected_content ()); + _menu.popup (cl, ev.GetPoint ()); } void - FilmEditor::external_audio_changed (wxCommandEvent &) + FilmEditor::three_d_changed () { - vector a; - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - a.push_back (wx_to_std (_external_audio[i]->GetPath())); + if (!_film) { + return; } - _film->set_external_audio (a); - } - - void - FilmEditor::setup_reel_control_sensitivity () - { - _reel_size->Enable (_multiple_reels->GetValue ()); + _film->set_three_d (_three_d->GetValue ()); } diff --combined src/wx/film_editor.h index b990ec40d,8ecec3ec7..bb217211c --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@@ -1,5 -1,5 +1,5 @@@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2013 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@@ -16,7 -16,7 +16,7 @@@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - + /** @file src/film_editor.h * @brief A wx widget to edit a film's metadata, and perform various functions. */ @@@ -27,10 -27,17 +27,17 @@@ #include #include #include "lib/film.h" + #include "content_menu.h" class wxNotebook; - + class wxListCtrl; + class wxListEvent; class Film; + class TimelineDialog; + class Ratio; + class Timecode; + class FilmEditorPanel; + class SubtitleContent; /** @class FilmEditor * @brief A wx widget to edit a film's metadata, and perform various functions. @@@ -41,146 -48,106 +48,108 @@@ public FilmEditor (boost::shared_ptr, wxWindow *); void set_film (boost::shared_ptr); - void setup_visibility (); + void set_selection (boost::weak_ptr); boost::signals2::signal FileChanged; + /* Stuff for panels */ + + wxNotebook* content_notebook () const { + return _content_notebook; + } + + boost::shared_ptr film () const { + return _film; + } + + boost::shared_ptr selected_content (); + boost::shared_ptr selected_video_content (); + boost::shared_ptr selected_audio_content (); + boost::shared_ptr selected_subtitle_content (); + private: - void make_film_panel (); - void make_video_panel (); - void make_audio_panel (); - void make_subtitle_panel (); + void make_dcp_panel (); + void make_content_panel (); void connect_to_widgets (); /* Handle changes to the view */ - void name_changed (wxCommandEvent &); - void use_dci_name_toggled (wxCommandEvent &); - void edit_dci_button_clicked (wxCommandEvent &); - void left_crop_changed (wxCommandEvent &); - void right_crop_changed (wxCommandEvent &); - void top_crop_changed (wxCommandEvent &); - void bottom_crop_changed (wxCommandEvent &); - void content_changed (wxCommandEvent &); - void trust_content_header_changed (wxCommandEvent &); - void format_changed (wxCommandEvent &); - void dcp_trim_start_changed (wxCommandEvent &); - void dcp_trim_end_changed (wxCommandEvent &); - void multiple_reels_toggled (wxCommandEvent &); - void reel_size_changed (wxCommandEvent &); - void dcp_content_type_changed (wxCommandEvent &); - void encrypted_toggled (wxCommandEvent &); - void dcp_ab_toggled (wxCommandEvent &); - void scaler_changed (wxCommandEvent &); - void audio_gain_changed (wxCommandEvent &); - void audio_gain_calculate_button_clicked (wxCommandEvent &); - void audio_delay_changed (wxCommandEvent &); - void with_subtitles_toggled (wxCommandEvent &); - void subtitle_offset_changed (wxCommandEvent &); - void subtitle_scale_changed (wxCommandEvent &); - void colour_lut_changed (wxCommandEvent &); - void j2k_bandwidth_changed (wxCommandEvent &); - void still_duration_changed (wxCommandEvent &); - void audio_stream_changed (wxCommandEvent &); - void subtitle_stream_changed (wxCommandEvent &); - void use_audio_changed (wxCommandEvent &); - void external_audio_changed (wxCommandEvent &); + void name_changed (); + void use_dci_name_toggled (); + void edit_dci_button_clicked (); + void content_selection_changed (); + void content_add_file_clicked (); + void content_add_folder_clicked (); + void content_remove_clicked (); + void container_changed (); + void dcp_content_type_changed (); + void scaler_changed (); + void j2k_bandwidth_changed (); + void frame_rate_changed (); + void best_frame_rate_clicked (); + void content_timeline_clicked (); + void audio_channels_changed (); + void resolution_changed (); + void sequence_video_changed (); + void content_right_click (wxListEvent &); + void three_d_changed (); + void standard_changed (); ++ void encrypted_toggled (); /* Handle changes to the model */ void film_changed (Film::Property); + void film_content_changed (boost::weak_ptr, int); - /* Button clicks */ - void edit_filters_clicked (wxCommandEvent &); - - void set_things_sensitive (bool); - void setup_formats (); - void setup_subtitle_control_sensitivity (); - void setup_audio_control_sensitivity (); - void setup_reel_control_sensitivity (); - void setup_streams (); - void setup_audio_details (); + void set_general_sensitivity (bool); + void setup_dcp_name (); + void setup_content (); + void setup_container (); + void setup_content_sensitivity (); - wxControl* video_control (wxControl *); - wxControl* still_control (wxControl *); - void active_jobs_changed (bool); - wxNotebook* _notebook; - wxPanel* _film_panel; - wxSizer* _film_sizer; - wxPanel* _video_panel; - wxSizer* _video_sizer; - wxPanel* _audio_panel; - wxSizer* _audio_sizer; - wxPanel* _subtitle_panel; - wxSizer* _subtitle_sizer; + FilmEditorPanel* _video_panel; + FilmEditorPanel* _audio_panel; + FilmEditorPanel* _subtitle_panel; + FilmEditorPanel* _timing_panel; + std::list _panels; + + wxNotebook* _main_notebook; + wxNotebook* _content_notebook; + wxPanel* _dcp_panel; + wxSizer* _dcp_sizer; + wxPanel* _content_panel; + wxSizer* _content_sizer; /** The film we are editing */ boost::shared_ptr _film; - /** The Film's name */ wxTextCtrl* _name; wxStaticText* _dcp_name; wxCheckBox* _use_dci_name; + wxChoice* _container; + wxListCtrl* _content; + wxButton* _content_add_file; + wxButton* _content_add_folder; + wxButton* _content_remove; + wxButton* _content_earlier; + wxButton* _content_later; + wxButton* _content_timeline; + wxCheckBox* _sequence_video; wxButton* _edit_dci_button; - /** The Film's format */ - wxComboBox* _format; - /** The Film's content file */ - wxFilePickerCtrl* _content; - wxCheckBox* _trust_content_header; - /** The Film's left crop */ - wxSpinCtrl* _left_crop; - /** The Film's right crop */ - wxSpinCtrl* _right_crop; - /** The Film's top crop */ - wxSpinCtrl* _top_crop; - /** The Film's bottom crop */ - wxSpinCtrl* _bottom_crop; - /** Currently-applied filters */ - wxStaticText* _filters; - /** Button to open the filters dialogue */ - wxButton* _filters_button; - /** The Film's scaler */ - wxComboBox* _scaler; - wxRadioButton* _use_content_audio; - wxComboBox* _audio_stream; - wxRadioButton* _use_external_audio; - wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS]; - /** The Film's audio gain */ - wxSpinCtrl* _audio_gain; - /** A button to open the gain calculation dialogue */ - wxButton* _audio_gain_calculate_button; - /** The Film's audio delay */ - wxSpinCtrl* _audio_delay; - wxCheckBox* _with_subtitles; - wxComboBox* _subtitle_stream; - wxSpinCtrl* _subtitle_offset; - wxSpinCtrl* _subtitle_scale; - wxComboBox* _colour_lut; - wxSpinCtrl* _j2k_bandwidth; - /** The Film's DCP content type */ - wxComboBox* _dcp_content_type; - /** The Film's frames per second */ - wxStaticText* _frames_per_second; - /** The Film's original size */ - wxStaticText* _original_size; - /** The Film's length */ - wxStaticText* _length; - /** The Film's audio details */ - wxStaticText* _audio; - /** The Film's duration for still sources */ - wxSpinCtrl* _still_duration; - - wxSpinCtrl* _dcp_trim_start; - wxSpinCtrl* _dcp_trim_end; + wxChoice* _scaler; + wxSpinCtrl* _j2k_bandwidth; + wxChoice* _dcp_content_type; + wxChoice* _frame_rate; + wxSpinCtrl* _audio_channels; + wxButton* _best_frame_rate; + wxCheckBox* _three_d; + wxChoice* _resolution; + wxChoice* _standard; + wxCheckBox* _encrypted; - wxCheckBox* _multiple_reels; - wxSpinCtrl* _reel_size; - /** Selector to generate an A/B comparison DCP */ - wxCheckBox* _dcp_ab; - std::list _video_controls; - std::list _still_controls; + ContentMenu _menu; - std::vector _formats; + std::vector _ratios; bool _generally_sensitive; + TimelineDialog* _timeline_dialog; }; diff --combined src/wx/kdm_dialog.cc index d94c13057,000000000..a9f63cffc mode 100644,000000..100644 --- a/src/wx/kdm_dialog.cc +++ b/src/wx/kdm_dialog.cc @@@ -1,376 -1,0 +1,373 @@@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include "lib/cinema.h" +#include "lib/config.h" +#include "kdm_dialog.h" +#include "cinema_dialog.h" +#include "screen_dialog.h" +#include "wx_util.h" +#ifdef __WXMSW__ +#include "dir_picker_ctrl.h" +#else +#include +#endif + +using std::string; +using std::map; +using std::list; +using std::pair; +using std::make_pair; +using boost::shared_ptr; + +KDMDialog::KDMDialog (wxWindow* parent) + : wxDialog (parent, wxID_ANY, _("Make KDMs")) +{ + wxBoxSizer* vertical = new wxBoxSizer (wxVERTICAL); - - add_label_to_sizer (vertical, this, "Make KDMs for"); - + wxBoxSizer* targets = new wxBoxSizer (wxHORIZONTAL); + + _targets = new wxTreeCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_HIDE_ROOT | wxTR_MULTIPLE | wxTR_HAS_BUTTONS); + targets->Add (_targets, 1, wxEXPAND | wxALL, 6); + + _root = _targets->AddRoot ("Foo"); + + list > c = Config::instance()->cinemas (); + for (list >::iterator i = c.begin(); i != c.end(); ++i) { + add_cinema (*i); + } + + _targets->ExpandAll (); + + wxBoxSizer* target_buttons = new wxBoxSizer (wxVERTICAL); + + _add_cinema = new wxButton (this, wxID_ANY, _("Add Cinema...")); - target_buttons->Add (_add_cinema, 1, 0, 6); ++ target_buttons->Add (_add_cinema, 1, wxEXPAND, 6); + _edit_cinema = new wxButton (this, wxID_ANY, _("Edit Cinema...")); - target_buttons->Add (_edit_cinema, 1, 0, 6); ++ target_buttons->Add (_edit_cinema, 1, wxEXPAND, 6); + _remove_cinema = new wxButton (this, wxID_ANY, _("Remove Cinema")); - target_buttons->Add (_remove_cinema, 1, 0, 6); ++ target_buttons->Add (_remove_cinema, 1, wxEXPAND, 6); + + _add_screen = new wxButton (this, wxID_ANY, _("Add Screen...")); - target_buttons->Add (_add_screen, 1, 0, 6); ++ target_buttons->Add (_add_screen, 1, wxEXPAND, 6); + _edit_screen = new wxButton (this, wxID_ANY, _("Edit Screen...")); - target_buttons->Add (_edit_screen, 1, 0, 6); ++ target_buttons->Add (_edit_screen, 1, wxEXPAND, 6); + _remove_screen = new wxButton (this, wxID_ANY, _("Remove Screen")); - target_buttons->Add (_remove_screen, 1, 0, 6); ++ target_buttons->Add (_remove_screen, 1, wxEXPAND, 6); + + targets->Add (target_buttons, 0, 0, 6); + + vertical->Add (targets, 1, wxEXPAND | wxALL, 6); + + wxFlexGridSizer* table = new wxFlexGridSizer (3, 2, 6); - add_label_to_sizer (table, this, "From"); ++ add_label_to_sizer (table, this, "From", true); + _from_date = new wxDatePickerCtrl (this, wxID_ANY); + table->Add (_from_date, 1, wxEXPAND); + _from_time = new wxTimePickerCtrl (this, wxID_ANY); + table->Add (_from_time, 1, wxEXPAND); + - add_label_to_sizer (table, this, "Until"); ++ add_label_to_sizer (table, this, "Until", true); + _until_date = new wxDatePickerCtrl (this, wxID_ANY); + table->Add (_until_date, 1, wxEXPAND); + _until_time = new wxTimePickerCtrl (this, wxID_ANY); + table->Add (_until_time, 1, wxEXPAND); + - add_label_to_sizer (table, this, "Write to"); ++ add_label_to_sizer (table, this, "Write to", true); + +#ifdef __WXMSW__ + _folder = new DirPickerCtrl (this); +#else + _folder = new wxDirPickerCtrl (this, wxDD_DIR_MUST_EXIST); +#endif + + table->Add (_folder, 1, wxEXPAND); + + vertical->Add (table, 0, wxEXPAND | wxALL, 6); + - wxSizer* buttons = CreateSeparatedButtonSizer (wxOK); ++ wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); + if (buttons) { + vertical->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + _targets->Connect (wxID_ANY, wxEVT_COMMAND_TREE_SEL_CHANGED, wxCommandEventHandler (KDMDialog::targets_selection_changed), 0, this); + + _add_cinema->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (KDMDialog::add_cinema_clicked), 0, this); + _edit_cinema->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (KDMDialog::edit_cinema_clicked), 0, this); + _remove_cinema->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (KDMDialog::remove_cinema_clicked), 0, this); + + _add_screen->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (KDMDialog::add_screen_clicked), 0, this); + _edit_screen->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (KDMDialog::edit_screen_clicked), 0, this); + _remove_screen->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (KDMDialog::remove_screen_clicked), 0, this); + + setup_sensitivity (); + + SetSizer (vertical); + vertical->Layout (); + vertical->SetSizeHints (this); +} + +list > > +KDMDialog::selected_cinemas () const +{ + wxArrayTreeItemIds s; + _targets->GetSelections (s); + + list > > c; + for (size_t i = 0; i < s.GetCount(); ++i) { + map >::const_iterator j = _cinemas.find (s[i]); + if (j != _cinemas.end ()) { + c.push_back (make_pair (j->first, j->second)); + } + } + + return c; +} + +list > > +KDMDialog::selected_screens () const +{ + wxArrayTreeItemIds s; + _targets->GetSelections (s); + + list > > c; + for (size_t i = 0; i < s.GetCount(); ++i) { + map >::const_iterator j = _screens.find (s[i]); + if (j != _screens.end ()) { + c.push_back (make_pair (j->first, j->second)); + } + } + + return c; +} + +void +KDMDialog::targets_selection_changed (wxCommandEvent &) +{ + setup_sensitivity (); +} + +void +KDMDialog::setup_sensitivity () +{ + bool const sc = selected_cinemas().size() == 1; + bool const ss = selected_screens().size() == 1; + + _edit_cinema->Enable (sc); + _remove_cinema->Enable (sc); + + _add_screen->Enable (sc); + _edit_screen->Enable (ss); + _remove_screen->Enable (ss); +} + +void +KDMDialog::add_cinema (shared_ptr c) +{ + _cinemas[_targets->AppendItem (_root, std_to_wx (c->name))] = c; + + for (list >::iterator i = c->screens.begin(); i != c->screens.end(); ++i) { + add_screen (c, *i); + } +} + +void +KDMDialog::add_screen (shared_ptr c, shared_ptr s) +{ + map >::const_iterator i = _cinemas.begin(); + while (i != _cinemas.end() && i->second != c) { + ++i; + } + + if (i == _cinemas.end()) { + return; + } + + _screens[_targets->AppendItem (i->first, std_to_wx (s->name))] = s; +} + +void +KDMDialog::add_cinema_clicked (wxCommandEvent &) +{ + CinemaDialog* d = new CinemaDialog (this, "Add Cinema"); + d->ShowModal (); + + shared_ptr c (new Cinema (d->name(), d->email())); + Config::instance()->add_cinema (c); + add_cinema (c); + + Config::instance()->write (); + + d->Destroy (); +} + +void +KDMDialog::edit_cinema_clicked (wxCommandEvent &) +{ + if (selected_cinemas().size() != 1) { + return; + } + + pair > c = selected_cinemas().front(); + + CinemaDialog* d = new CinemaDialog (this, "Edit cinema", c.second->name, c.second->email); + d->ShowModal (); + + c.second->name = d->name (); + c.second->email = d->email (); + _targets->SetItemText (c.first, std_to_wx (d->name())); + + Config::instance()->write (); + + d->Destroy (); +} + +void +KDMDialog::remove_cinema_clicked (wxCommandEvent &) +{ + if (selected_cinemas().size() != 1) { + return; + } + + pair > c = selected_cinemas().front(); + + Config::instance()->remove_cinema (c.second); + _targets->Delete (c.first); + + Config::instance()->write (); +} + +void +KDMDialog::add_screen_clicked (wxCommandEvent &) +{ + if (selected_cinemas().size() != 1) { + return; + } + + shared_ptr c = selected_cinemas().front().second; + + ScreenDialog* d = new ScreenDialog (this, "Add Screen"); + d->ShowModal (); + + shared_ptr s (new Screen (d->name(), d->certificate())); + c->screens.push_back (s); + add_screen (c, s); + + Config::instance()->write (); + + d->Destroy (); +} + +void +KDMDialog::edit_screen_clicked (wxCommandEvent &) +{ + if (selected_screens().size() != 1) { + return; + } + + pair > s = selected_screens().front(); + + ScreenDialog* d = new ScreenDialog (this, "Edit screen", s.second->name, s.second->certificate); + d->ShowModal (); + + s.second->name = d->name (); + s.second->certificate = d->certificate (); + _targets->SetItemText (s.first, std_to_wx (d->name())); + + Config::instance()->write (); + + d->Destroy (); +} + +void +KDMDialog::remove_screen_clicked (wxCommandEvent &) +{ + if (selected_screens().size() != 1) { + return; + } + + pair > s = selected_screens().front(); + + map >::iterator i = _cinemas.begin (); + while (i != _cinemas.end() && find (i->second->screens.begin(), i->second->screens.end(), s.second) == i->second->screens.end()) { + ++i; + } + + if (i == _cinemas.end()) { + return; + } + + i->second->screens.remove (s.second); + _targets->Delete (s.first); + + Config::instance()->write (); +} + +list > +KDMDialog::screens () const +{ + list > s; + + list > > cinemas = selected_cinemas (); + for (list > >::iterator i = cinemas.begin(); i != cinemas.end(); ++i) { + for (list >::iterator j = i->second->screens.begin(); j != i->second->screens.end(); ++j) { + s.push_back (*j); + } + } + + list > > screens = selected_screens (); + for (list > >::iterator i = screens.begin(); i != screens.end(); ++i) { + s.push_back (i->second); + } + + s.sort (); + s.unique (); + + return s; +} + +boost::posix_time::ptime +KDMDialog::from () const +{ + return posix_time (_from_date, _from_time); +} + +boost::posix_time::ptime +KDMDialog::posix_time (wxDatePickerCtrl* date_picker, wxTimePickerCtrl* time_picker) +{ + wxDateTime const date = date_picker->GetValue (); + wxDateTime const time = time_picker->GetValue (); + return boost::posix_time::ptime ( + boost::gregorian::date (date.GetYear(), date.GetMonth() + 1, date.GetDay()), + boost::posix_time::time_duration (time.GetHour(), time.GetMinute(), time.GetSecond()) + ); +} + +boost::posix_time::ptime +KDMDialog::until () const +{ + return posix_time (_until_date, _until_time); +} + +string +KDMDialog::directory () const +{ + return wx_to_std (_folder->GetPath ()); +} diff --combined src/wx/screen_dialog.cc index 910dece9d,000000000..7ff519713 mode 100644,000000..100644 --- a/src/wx/screen_dialog.cc +++ b/src/wx/screen_dialog.cc @@@ -1,97 -1,0 +1,97 @@@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include "lib/compose.hpp" +#include "screen_dialog.h" +#include "wx_util.h" + +using std::string; +using std::cout; +using boost::shared_ptr; + +ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr certificate) + : wxDialog (parent, wxID_ANY, std_to_wx (title)) + , _certificate (certificate) +{ + wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6); + table->AddGrowableCol (1, 1); + - add_label_to_sizer (table, this, "Name"); ++ add_label_to_sizer (table, this, "Name", true); + _name = new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (320, -1)); + table->Add (_name, 1, wxEXPAND); + - add_label_to_sizer (table, this, "Certificate"); ++ add_label_to_sizer (table, this, "Certificate", true); + _certificate_load = new wxButton (this, wxID_ANY, wxT ("Load from file...")); + table->Add (_certificate_load, 1, wxEXPAND); + + table->AddSpacer (0); + _certificate_text = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxSize (320, 256), wxTE_MULTILINE | wxTE_READONLY); + if (certificate) { + _certificate_text->SetValue (certificate->certificate ()); + } + wxFont font = wxSystemSettings::GetFont (wxSYS_ANSI_FIXED_FONT); + font.SetPointSize (font.GetPointSize() / 2); + _certificate_text->SetFont (font); + table->Add (_certificate_text, 1, wxEXPAND); + + wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); + overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6); + + wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); + if (buttons) { + overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + SetSizer (overall_sizer); + overall_sizer->Layout (); + overall_sizer->SetSizeHints (this); + + _certificate_load->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ScreenDialog::load_certificate), 0, this); +} + +string +ScreenDialog::name () const +{ + return wx_to_std (_name->GetValue()); +} + +shared_ptr +ScreenDialog::certificate () const +{ + return _certificate; +} + +void +ScreenDialog::load_certificate (wxCommandEvent &) +{ + wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File")); + d->ShowModal (); + + try { + _certificate.reset (new libdcp::Certificate (wx_to_std (d->GetPath ()))); + _certificate_text->SetValue (_certificate->certificate ()); + } catch (libdcp::MiscError& e) { + error_dialog (this, String::compose ("Could not read certificate file (%1)", e.what())); + } + + d->Destroy (); +} diff --combined src/wx/wscript index 82d9d3738,9c3ecdd71..8f35e2fac --- a/src/wx/wscript +++ b/src/wx/wscript @@@ -1,5 -1,63 +1,66 @@@ + import os + import glob + from waflib import Logs + import i18n + + sources = """ + about_dialog.cc + audio_dialog.cc + audio_mapping_view.cc + audio_panel.cc + audio_plot.cc ++ cinema_dialog.cc + colour_conversion_editor.cc + config_dialog.cc + content_colour_conversion_dialog.cc + content_menu.cc + dci_metadata_dialog.cc + dir_picker_ctrl.cc + film_editor.cc + film_editor_panel.cc + film_viewer.cc + filter_dialog.cc + filter_editor.cc + gain_calculator_dialog.cc + job_manager_view.cc + job_wrapper.cc ++ kdm_dialog.cc + new_film_dialog.cc + preset_colour_conversion_dialog.cc + properties_dialog.cc + repeat_dialog.cc ++ screen_dialog.cc + server_dialog.cc + subtitle_panel.cc + timecode.cc + timeline.cc + timeline_dialog.cc + timing_panel.cc + video_panel.cc + wx_util.cc + wx_ui_signaller.cc + """ + def configure(conf): - conf.check_cfg(package = '', path = 'wx-config', args = '--cppflags --cxxflags --libs', uselib_store = 'WXWIDGETS', mandatory = True) + args = '--cppflags --cxxflags' + if not conf.env.STATIC: + args += ' --libs' + + conf.check_cfg(msg='Checking for wxWidgets', package='', path=conf.options.wx_config, args=args, + uselib_store='WXWIDGETS', mandatory=True) + + if conf.env.STATIC: + # wx-config returns its static libraries as full paths, without -l prefixes, which confuses + # check_cfg(), so just hard-code it all. + conf.env.STLIB_WXWIDGETS = ['wx_gtk2u_xrc-2.9', 'wx_gtk2u_qa-2.9', 'wx_baseu_net-2.9', 'wx_gtk2u_html-2.9', + 'wx_gtk2u_adv-2.9', 'wx_gtk2u_core-2.9', 'wx_baseu_xml-2.9', 'wx_baseu-2.9'] + conf.env.LIB_WXWIDGETS = ['tiff', 'SM', 'dl', 'jpeg', 'png', 'X11'] + + conf.in_msg = 1 + wx_version = conf.check_cfg(package='', path=conf.options.wx_config, args='--version').strip() + conf.im_msg = 0 + if wx_version != '2.9.4' and wx_version != '2.9.5': + conf.fatal('wxwidgets version 2.9.4 or 2.9.5 is required; %s found' % wx_version) def build(bld): if bld.env.STATIC: @@@ -7,30 -65,20 +68,20 @@@ else: obj = bld(features = 'cxx cxxshlib') - obj.name = 'libdvdomatic-wx' - obj.includes = [ '..' ] - obj.export_includes = ['.'] + obj.name = 'libdcpomatic-wx' + # obj.includes = [ '..' ] + obj.export_includes = ['..'] obj.uselib = 'WXWIDGETS' - obj.use = 'libdvdomatic' - obj.source = """ - config_dialog.cc - dci_name_dialog.cc - dir_picker_ctrl.cc - film_editor.cc - film_viewer.cc - filter_dialog.cc - filter_view.cc - gain_calculator_dialog.cc - job_manager_view.cc - job_wrapper.cc - kdm_dialog.cc - cinema_dialog.cc - new_film_dialog.cc - screen_dialog.cc - properties_dialog.cc - server_dialog.cc - wx_util.cc - wx_ui_signaller.cc - """ - - obj.target = 'dvdomatic-wx' + if bld.env.TARGET_LINUX: + obj.uselib += ' GTK' + obj.use = 'libdcpomatic' + obj.source = sources + obj.target = 'dcpomatic-wx' + + i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld) + + def pot(bld): + i18n.pot(os.path.join('src', 'wx'), sources, 'libdcpomatic-wx') + + def pot_merge(bld): + i18n.pot_merge(os.path.join('src', 'wx'), 'libdcpomatic-wx')