X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Futil.cc;h=ec1fd47bd7f03bdca133d791b6201d824f252cab;hb=f861018389acd9d277fe34d7621182b9b54f977f;hp=328be066a4f2266bc7163ca1d10e62df4c0079ec;hpb=55e5d61e68d9ee95594562f911c9db0397246fbe;p=dcpomatic.git diff --git a/src/lib/util.cc b/src/lib/util.cc index 328be066a..ec1fd47bd 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -27,7 +27,7 @@ #include #include #include -#ifdef DVDOMATIC_POSIX +#ifdef DCPOMATIC_POSIX #include #include #endif @@ -61,11 +61,32 @@ extern "C" { #include "sound_processor.h" #include "config.h" -using namespace std; -using namespace boost; +#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 boost::shared_ptr; +using boost::thread; +using boost::lexical_cast; +using boost::optional; using libdcp::Size; -thread::id ui_thread; +boost::thread::id ui_thread; /** Convert some number of seconds to a string representation * in hours, minutes and seconds. @@ -83,11 +104,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 (); } @@ -107,40 +128,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; } @@ -183,7 +204,7 @@ stacktrace (ostream& out, int levels) if (strings) { for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) { - out << " " << demangle (strings[i]) << endl; + out << N_(" ") << demangle (strings[i]) << "\n"; } free (strings); @@ -198,7 +219,7 @@ static string 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 (); } @@ -207,16 +228,16 @@ string 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 (); } @@ -227,11 +248,11 @@ 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. +/** 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 () { avfilter_register_all (); @@ -241,7 +262,52 @@ dvdomatic_setup () Filter::setup_filters (); SoundProcessor::setup_sound_processors (); - ui_thread = this_thread::get_id (); + ui_thread = boost::this_thread::get_id (); +} + +#ifdef DCPOMATIC_WINDOWS +boost::filesystem::path +mo_path () +{ + 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_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 start Start position for the crop within the image. @@ -252,7 +318,7 @@ string crop_string (Position start, libdcp::Size size) { stringstream s; - s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y; + s << N_("crop=") << size.width << N_(":") << size.height << N_(":") << start.x << N_(":") << start.y; return s.str (); } @@ -268,7 +334,7 @@ split_at_spaces_considering_quotes (string s) 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 { @@ -291,7 +357,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 (); @@ -301,16 +367,16 @@ md5_digest (void const * data, int size) * @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]; @@ -329,13 +395,14 @@ md5_digest (string file) 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 (); } -static bool about_equal (float a, float b) +static bool +about_equal (float a, float b) { /* A film of F seconds at f FPS will be Ff frames; Consider some delta FPS d, so if we run the same @@ -369,21 +436,12 @@ public: , dcp (dcp_) {} - bool skip () const { - return !about_equal (source, dcp) && source > dcp; - } - - bool repeat () const { - return !about_equal (source, dcp) && source < dcp; - } - float source; int dcp; }; -/** @param fps Arbitrary source frames-per-second value */ -/** XXX: this could be slow-ish */ -DCPFrameRate::DCPFrameRate (float source_fps) +int +best_dcp_frame_rate (float source_fps) { list const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); @@ -402,8 +460,8 @@ DCPFrameRate::DCPFrameRate (float source_fps) } /* Pick the best one, bailing early if we hit an exact match */ - float error = numeric_limits::max (); - boost::optional best; + float error = std::numeric_limits::max (); + optional best; list::iterator i = candidates.begin(); while (i != candidates.end()) { @@ -421,14 +479,8 @@ DCPFrameRate::DCPFrameRate (float source_fps) ++i; } - if (!best) { - throw EncodeError ("cannot find a suitable DCP frame rate for this source"); - } - - frames_per_second = best->dcp; - skip = best->skip (); - repeat = best->repeat (); - change_speed = !about_equal (source_fps * factor(), frames_per_second); + assert (best); + return best->dcp; } /** @param An arbitrary sampling rate. @@ -444,29 +496,6 @@ dcp_audio_sample_rate (int fs) return 96000; } -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; - } - - return f; -} - -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); -} - -bool operator!= (Crop const & a, Crop const & b) -{ - return !(a == b); -} - /** @param index Colour LUT index. * @return Human-readable name. */ @@ -475,177 +504,108 @@ colour_lut_index_to_name (int index) { switch (index) { case 0: - return "sRGB"; + return _("sRGB"); case 1: - return "Rec 709"; + return _("Rec 709"); } assert (false); - return ""; + return N_(""); } -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); + } while (ec == boost::asio::error::would_block); if (ec) { - throw NetworkError ("write timed out"); + throw NetworkError (ec.message ()); } } -/** Blocking read with timeout. +void +Socket::write (uint32_t v) +{ + v = htonl (v); + write (reinterpret_cast (&v), 4); +} + +/** Blocking read. * @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) +void +Socket::read (uint8_t* 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; - int amount_read = 0; - - _socket.async_read_some ( - asio::buffer (data, size), - (lambda::var(ec) = lambda::_1, lambda::var(amount_read) = lambda::_2) - ); + boost::asio::async_read (_socket, boost::asio::buffer (data, size), 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) { - amount_read = 0; - } - - 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) -{ - 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); - } -} - -/** Read a definite amount of data from our socket, and mark - * it as consumed. - * @param data Where to put the data. - * @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; + throw NetworkError (ec.message ()); } } -/** 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) +uint32_t +Socket::read_uint32 () { - assert (size < int (sizeof (_buffer))); - - /* 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"); - } - - to_read -= n; - _buffer_data += n; - } - - assert (_buffer_data >= size); - - /* copy data into the output buffer */ - assert (size >= _buffer_data); - memcpy (data, _buffer, size); + uint32_t v; + read (reinterpret_cast (&v), 4); + return ntohl (v); } /** @param other A Rect. @@ -724,13 +684,13 @@ string 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; @@ -754,12 +714,12 @@ string 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; @@ -769,7 +729,7 @@ int 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); @@ -810,6 +770,21 @@ AudioBuffers::AudioBuffers (AudioBuffers const & other) } } +/* XXX: it's a shame that this is a copy-and-paste of the above; + probably fixable with c++0x. +*/ +AudioBuffers::AudioBuffers (boost::shared_ptr 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 () { @@ -910,37 +885,39 @@ AudioBuffers::move (int from, int to, int frames) } } +/** Add data from from `from', `from_channel' to our channel `to_channel' */ +void +AudioBuffers::accumulate (shared_ptr from, int from_channel, int to_channel) +{ + int const N = frames (); + assert (from->frames() == N); + + float* s = from->data (from_channel); + float* d = _data[to_channel]; + + for (int i = 0; i < N; ++i) { + *d++ += *s++; + } +} + /** Trip an assert if the caller is not in the UI thread */ void ensure_ui_thread () { - assert (this_thread::get_id() == ui_thread); + assert (boost::this_thread::get_id() == ui_thread); } -/** @param v Source video frame. +/** @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'. */ int64_t -video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second) +video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second) { return ((int64_t) v * audio_sample_rate / frames_per_second); } -/** @param f Filename. - * @return true if this file is a still image, false if it is something else. - */ -bool -still_image_file (string f) -{ - string ext = boost::filesystem::path(f).extension().string(); - - transform (ext.begin(), ext.end(), ext.begin(), ::tolower); - - return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png"); -} - /** @return A pair containing CPU model name and the number of processors */ pair cpu_info () @@ -948,17 +925,17 @@ cpu_info () pair info; info.second = 0; -#ifdef DVDOMATIC_POSIX - ifstream f ("/proc/cpuinfo"); +#ifdef DCPOMATIC_POSIX + ifstream f (N_("/proc/cpuinfo")); while (f.good ()) { string l; getline (f, l); - if (boost::algorithm::starts_with (l, "model name")) { + if (boost::algorithm::starts_with (l, N_("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")) { + } else if (boost::algorithm::starts_with (l, N_("processor"))) { ++info.second; } } @@ -966,3 +943,71 @@ cpu_info () return info; } + +string +audio_channel_name (int c) +{ + assert (MAX_AUDIO_CHANNELS == 6); + + /* 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]; +} + +FrameRateConversion::FrameRateConversion (float source, int dcp) + : skip (false) + , repeat (false) + , change_speed (false) +{ + if (fabs (source / 2.0 - dcp) < (fabs (source - dcp))) { + skip = true; + } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { + repeat = true; + } + + change_speed = !about_equal (source * factor(), dcp); + + if (!skip && !repeat && !change_speed) { + description = _("DCP and source have the same rate.\n"); + } else { + if (skip) { + description = _("DCP will use every other frame of the source.\n"); + } else if (repeat) { + description = _("Each source frame will be doubled in the DCP.\n"); + } + + if (change_speed) { + float const pc = dcp * 100 / (source * factor()); + description += String::compose (_("DCP will run at %1%% of the source speed.\n"), pc); + } + } +} + +LocaleGuard::LocaleGuard () + : _old (0) +{ + char const * old = setlocale (LC_NUMERIC, 0); + + if (old) { + _old = strdup (old); + if (strcmp (_old, "POSIX")) { + setlocale (LC_NUMERIC, "POSIX"); + } + } +} + +LocaleGuard::~LocaleGuard () +{ + setlocale (LC_NUMERIC, _old); + free (_old); +}