X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Futil.cc;h=32b1805be622051fe6c3946888c5b994978abdc5;hb=1d49c6b90bc8f6a0e5e7ff0a2e7aeae3e3043a6d;hp=47a86da9e09907a98fd563d9c395d11f6fc890f4;hpb=b996eb8276dc4645745540190c9a2f5e2c875c0c;p=dcpomatic.git diff --git a/src/lib/util.cc b/src/lib/util.cc index 47a86da9e..32b1805be 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -26,7 +26,8 @@ #include #include #include -#ifdef DVDOMATIC_POSIX +#include +#ifdef DCPOMATIC_POSIX #include #include #endif @@ -36,11 +37,20 @@ #include #include #include +#include +#include +#ifdef DCPOMATIC_WINDOWS +#include +#endif +#include #include #include #include #include #include +#include +#include +#include extern "C" { #include #include @@ -52,18 +62,45 @@ extern "C" { #include "util.h" #include "exceptions.h" #include "scaler.h" -#include "format.h" #include "dcp_content_type.h" #include "filter.h" -#include "screen.h" -#include "film_state.h" #include "sound_processor.h" -#ifndef DVDOMATIC_DISABLE_PLAYER -#include "player_manager.h" +#include "config.h" +#include "ratio.h" +#include "job.h" +#include "cross.h" +#ifdef DCPOMATIC_WINDOWS +#include "stack.hpp" #endif -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::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::cout; +using std::streampos; +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 +118,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 +142,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 +209,12 @@ void 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); @@ -189,63 +222,6 @@ stacktrace (ostream& out, int levels) } #endif -/** @param s Sample format. - * @return String representation. - */ -string -audio_sample_format_to_string (AVSampleFormat s) -{ - /* Our sample format handling is not exactly complete */ - - switch (s) { - case AV_SAMPLE_FMT_S16: - return "S16"; - default: - assert (false); - } -} - -/** @param s String representation of a sample format, as returned from audio_sample_format_to_string(). - * @return Sample format. - */ -AVSampleFormat -audio_sample_format_from_string (string s) -{ - if (s == "S16") { - return AV_SAMPLE_FMT_S16; - } - - assert (false); -} - -/** @return Version of vobcopy that is on the path (and hence that we will use) */ -static string -vobcopy_version () -{ - FILE* f = popen ("vobcopy -V 2>&1", "r"); - if (f == 0) { - throw EncodeError ("could not run vobcopy to check version"); - } - - string version = "unknown"; - - while (!feof (f)) { - char buf[256]; - if (fgets (buf, sizeof (buf), f)) { - string s (buf); - vector b; - split (b, s, is_any_of (" ")); - if (b.size() >= 2 && b[0] == "Vobcopy") { - version = b[1]; - } - } - } - - pclose (f); - - return version; -} - /** @param v Version as used by FFmpeg. * @return A string representation of v. */ @@ -253,7 +229,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 (); } @@ -262,17 +238,16 @@ string dependency_version_summary () { stringstream s; - s << "libopenjpeg " << opj_version () << ", " - << "vobcopy " << vobcopy_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 (); } @@ -283,44 +258,116 @@ seconds (struct timeval t) return t.tv_sec + (double (t.tv_usec) / 1e6); } - -#ifdef DVDOMATIC_POSIX -void -sigchld_handler (int, siginfo_t* info, void *) +#ifdef DCPOMATIC_WINDOWS +LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *) { -#ifndef DVDOMATIC_DISABLE_PLAYER - PlayerManager::instance()->child_exited (info->si_pid); -#endif + dbg::stack s; + FILE* f = fopen_boost (backtrace_file, "w"); + for (dbg::stack::const_iterator i = s.begin(); i != s.end(); ++i) { + fprintf (f, "%p %s %d %s", i->instruction, i->function.c_str(), i->line, i->module.c_str()); + } + fclose (f); + return EXCEPTION_CONTINUE_SEARCH; } #endif -/** 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 () -{ - Format::setup_formats (); +dcpomatic_setup () +{ +#ifdef DCPOMATIC_WINDOWS + backtrace_file /= g_get_user_config_dir (); + backtrace_file /= "backtrace.txt"; + SetUnhandledExceptionFilter(exception_handler); + + /* Dark voodoo which, I think, gets boost::filesystem::path to + correctly convert UTF-8 strings to paths, and also paths + back to UTF-8 strings (on path::string()). + + After this, constructing boost::filesystem::paths from strings + converts from UTF-8 to UTF-16 inside the path. Then + path::string().c_str() gives UTF-8 and + path::c_str() gives UTF-16. + + This is all Windows-only. AFAICT Linux/OS X use UTF-8 everywhere, + so things are much simpler. + */ + std::locale::global (boost::locale::generator().generate ("")); + boost::filesystem::path::imbue (std::locale ()); +#endif + + avfilter_register_all (); + +#ifdef DCPOMATIC_OSX + /* Add our lib directory to the libltdl search path so that + xmlsec can find xmlsec1-openssl. + */ + boost::filesystem::path lib = app_contents (); + lib /= "lib"; + setenv ("LTDL_LIBRARY_PATH", lib.c_str (), 1); +#endif + + libdcp::init (); + + Ratio::setup_ratios (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); Filter::setup_filters (); SoundProcessor::setup_sound_processors (); -#ifdef DVDOMATIC_POSIX - struct sigaction sa; - sa.sa_flags = SA_SIGINFO; - sigemptyset (&sa.sa_mask); - sa.sa_sigaction = sigchld_handler; - sigaction (SIGCHLD, &sa, 0); -#endif + ui_thread = boost::this_thread::get_id (); } -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. + * @return Parts of the string split at spaces, except when a space is within quotation marks. + */ vector split_at_spaces_considering_quotes (string s) { @@ -330,7 +377,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 { @@ -353,37 +400,48 @@ 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 (); } -/** @param file File name. - * @return MD5 digest of file's contents. - */ +/** @param job Optional job for which to report progress */ string -md5_digest (string file) +md5_digest (vector files, shared_ptr job) { - ifstream f (file.c_str(), ios::binary); - if (!f.good ()) { - throw OpenFileError (file); - } - - f.seekg (0, ios::end); - int bytes = f.tellg (); - f.seekg (0, ios::beg); - - int const buffer_size = 64 * 1024; + boost::uintmax_t const buffer_size = 64 * 1024; char buffer[buffer_size]; MD5_CTX md5_context; MD5_Init (&md5_context); - while (bytes > 0) { - int const t = min (bytes, buffer_size); - f.read (buffer, t); - MD5_Update (&md5_context, buffer, t); - bytes -= t; + + vector sizes; + for (size_t i = 0; i < files.size(); ++i) { + sizes.push_back (boost::filesystem::file_size (files[i])); + } + + for (size_t i = 0; i < files.size(); ++i) { + FILE* f = fopen_boost (files[i], "rb"); + if (!f) { + throw OpenFileError (files[i].string()); + } + + boost::uintmax_t const bytes = boost::filesystem::file_size (files[i]); + boost::uintmax_t remaining = bytes; + + while (remaining > 0) { + int const t = min (remaining, buffer_size); + fread (buffer, 1, t, f); + MD5_Update (&md5_context, buffer, t); + remaining -= t; + + if (job) { + job->set_progress ((float (i) + 1 - float(remaining) / bytes) / files.size ()); + } + } + + fclose (f); } unsigned char digest[MD5_DIGEST_LENGTH]; @@ -391,238 +449,191 @@ 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 (); } -/** @param An arbitrary sampling rate. - * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz). - */ -int -dcp_audio_sample_rate (int fs) +static bool +about_equal (float a, float b) { - if (fs <= 48000) { - return 48000; - } + /* 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. - return 96000; -} + 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) + , _acceptor (0) + , _timeout (timeout) { - _deadline.expires_at (posix_time::pos_infin); + _deadline.expires_at (boost::posix_time::pos_infin); check (); } +Socket::~Socket () +{ + delete _acceptor; +} + void Socket::check () { - if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) { - _socket.close (); - _deadline.expires_at (posix_time::pos_infin); + if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) { + if (_acceptor) { + _acceptor->cancel (); + } else { + _socket.close (); + } + _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::tcp::endpoint endpoint) { - 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"); + if (ec) { + throw NetworkError (String::compose (_("error during async_connect (%1)"), ec.value ())); + } + + if (!_socket.is_open ()) { + throw NetworkError (_("connect timed out")); } } -/** Blocking write with timeout. - * @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::accept (int port) { - _deadline.expires_from_now (posix_time::seconds (timeout)); - system::error_code ec = asio::error::would_block; - - asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1); + _acceptor = new boost::asio::ip::tcp::acceptor (_io_service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)); + + _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); + boost::system::error_code ec = boost::asio::error::would_block; + _acceptor->async_accept (_socket, 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 ); + delete _acceptor; + _acceptor = 0; + if (ec) { - throw NetworkError ("write timed out"); + throw NetworkError (String::compose (_("error during async_accept (%1)"), ec.value ())); } } -/** Blocking read with timeout. - * @param data Buffer to read to. - * @param size Number of bytes to read. - * @param timeout Time-out, in seconds. +/** Blocking write. + * @param data Buffer to write. + * @param size Number of bytes to write. */ -int -Socket::read (uint8_t* data, int size, int timeout) +void +Socket::write (uint8_t const * data, int size) { - _deadline.expires_from_now (posix_time::seconds (timeout)); - system::error_code ec = 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) - ); + _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); + boost::system::error_code ec = boost::asio::error::would_block; + 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) { - amount_read = 0; + throw NetworkError (String::compose (_("error during async_write (%1)"), ec.value ())); } - - 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 (String::compose (_("error during async_read (%1)"), ec.value ())); } - - assert (_buffer_data >= size); - - /* copy data into the output buffer */ - assert (size >= _buffer_data); - memcpy (data, _buffer, size); } -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. - * @param a Number to round. + * @param c Index. + * @param s Array of numbers to round, indexed by c. * @param t Multiple to round to. * @return Rounded number. */ - int -round_up (int a, int t) +stride_round_up (int c, int const * stride, int t) { - a += (t - 1); + int const a = stride[c] + (t - 1); return a - (a % t); } @@ -643,7 +654,7 @@ read_key_value (istream &s) if (line.empty ()) { continue; } - + if (line[0] == '#') { continue; } @@ -667,13 +678,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; @@ -697,12 +708,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; @@ -712,7 +723,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); @@ -722,3 +733,184 @@ get_optional_int (multimap const & kv, string k) return lexical_cast (i->second); } + +/** Trip an assert if the caller is not in the UI thread */ +void +ensure_ui_thread () +{ + assert (boost::this_thread::get_id() == ui_thread); +} + +/** @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 (VideoContent::Frame v, float audio_sample_rate, float frames_per_second) +{ + return ((int64_t) v * audio_sample_rate / frames_per_second); +} + +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 (1) + , change_speed (false) +{ + if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) { + /* The difference between source and DCP frame rate will be lower + (i.e. better) if we skip. + */ + skip = true; + } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { + /* The difference between source and DCP frame rate would be better + if we repeated each frame once; it may be better still if we + repeated more than once. Work out the required repeat. + */ + repeat = round (dcp / source); + } + + change_speed = !about_equal (source * factor(), dcp); + + if (!skip && repeat == 1 && !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 == 2) { + description = _("Each content frame will be doubled in the DCP.\n"); + } else if (repeat > 2) { + description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1); + } + + if (change_speed) { + float const pc = dcp * 100 / (source * factor()); + description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc); + } + } +} + +LocaleGuard::LocaleGuard () + : _old (0) +{ + char const * old = setlocale (LC_NUMERIC, 0); + + if (old) { + _old = strdup (old); + if (strcmp (_old, "C")) { + setlocale (LC_NUMERIC, "C"); + } + } +} + +LocaleGuard::~LocaleGuard () +{ + setlocale (LC_NUMERIC, _old); + free (_old); +} + +bool +valid_image_file (boost::filesystem::path f) +{ + string ext = f.extension().string(); + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); + return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".tga"); +} + +string +tidy_for_filename (string f) +{ + string t; + for (size_t i = 0; i < f.length(); ++i) { + if (isalnum (f[i]) || f[i] == '_' || f[i] == '-') { + t += f[i]; + } else { + t += '_'; + } + } + + return t; +} + +shared_ptr +make_signer () +{ + boost::filesystem::path const sd = Config::instance()->signer_chain_directory (); + + /* Remake the chain if any of it is missing */ + + list files; + files.push_back ("ca.self-signed.pem"); + files.push_back ("intermediate.signed.pem"); + files.push_back ("leaf.signed.pem"); + files.push_back ("leaf.key"); + + list::const_iterator i = files.begin(); + while (i != files.end()) { + boost::filesystem::path p (sd); + p /= *i; + if (!boost::filesystem::exists (p)) { + boost::filesystem::remove_all (sd); + boost::filesystem::create_directories (sd); + libdcp::make_signer_chain (sd, openssl_path ()); + break; + } + + ++i; + } + + libdcp::CertificateChain chain; + + { + boost::filesystem::path p (sd); + p /= "ca.self-signed.pem"; + chain.add (shared_ptr (new libdcp::Certificate (p))); + } + + { + boost::filesystem::path p (sd); + p /= "intermediate.signed.pem"; + chain.add (shared_ptr (new libdcp::Certificate (p))); + } + + { + boost::filesystem::path p (sd); + p /= "leaf.signed.pem"; + chain.add (shared_ptr (new libdcp::Certificate (p))); + } + + boost::filesystem::path signer_key (sd); + signer_key /= "leaf.key"; + + return shared_ptr (new libdcp::Signer (chain, signer_key)); +} + +libdcp::Size +fit_ratio_within (float ratio, libdcp::Size full_frame) +{ + if (ratio < full_frame.ratio ()) { + return libdcp::Size (rint (full_frame.height * ratio), full_frame.height); + } + + return libdcp::Size (full_frame.width, rint (full_frame.width / ratio)); +}