X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Futil.cc;h=78e7b480c9c91f3c07485e886644fc864a66d7dd;hb=1b1bc528ee5ca1fee1bd33f9fb6f79cd551e3b33;hp=53c457898015f36908b6d3fc74094a20fd6043d1;hpb=b655bd3359e9a014da68cd9f61e2a5b1d233247d;p=dcpomatic.git diff --git a/src/lib/util.cc b/src/lib/util.cc index 53c457898..78e7b480c 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef DCPOMATIC_POSIX #include #include @@ -39,12 +40,19 @@ #include #include #include +#ifdef DCPOMATIC_WINDOWS +#include +#endif #include #include #include #include #include -#include +#include +#include +#include +#include +#include extern "C" { #include #include @@ -61,6 +69,8 @@ extern "C" { #include "sound_processor.h" #include "config.h" #include "ratio.h" +#include "job.h" +#include "cross.h" #ifdef DCPOMATIC_WINDOWS #include "stack.hpp" #endif @@ -75,21 +85,24 @@ 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::map; using std::istream; using std::numeric_limits; using std::pair; -using std::ofstream; +using std::cout; +using std::bad_alloc; +using std::streampos; +using std::set_terminate; using boost::shared_ptr; using boost::thread; using boost::lexical_cast; using boost::optional; -using libdcp::Size; +using dcp::Size; static boost::thread::id ui_thread; static boost::filesystem::path backtrace_file; @@ -119,12 +132,6 @@ seconds_to_hms (int s) return hms.str (); } -string -time_to_hms (Time t) -{ - return seconds_to_hms (t / TIME_HZ); -} - /** @param s Number of seconds. * @return String containing an approximate description of s (e.g. "about 2 hours") */ @@ -207,15 +214,11 @@ 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++) { + for (size_t i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) { out << N_(" ") << demangle (strings[i]) << "\n"; } @@ -249,7 +252,7 @@ dependency_version_summary () << 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; + << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit; return s.str (); } @@ -264,12 +267,42 @@ seconds (struct timeval t) 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")); + 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 +/* From http://stackoverflow.com/questions/2443135/how-do-i-find-where-an-exception-was-thrown-in-c */ +void +terminate () +{ + static bool tried_throw = false; + + try { + // try once to re-throw currently active exception + if (!tried_throw++) { + throw; + } + } + catch (const std::exception &e) { + std::cerr << __FUNCTION__ << " caught unhandled exception. what(): " + << e.what() << std::endl; + } + catch (...) { + std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." + << std::endl; + } + +#ifdef DCPOMATIC_POSIX + stacktrace (cout, 50); +#endif + abort(); +} + /** 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. */ @@ -280,9 +313,38 @@ dcpomatic_setup () 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 + + set_terminate (terminate); + + Pango::init (); + dcp::init (); Ratio::setup_ratios (); DCPContentType::setup_dcp_content_types (); @@ -323,6 +385,8 @@ dcpomatic_setup_gettext_i18n (string lang) putenv (cmd); snprintf (cmd, sizeof(cmd), "LANG=%s", lang.c_str ()); putenv (cmd); + snprintf (cmd, sizeof(cmd), "LC_ALL=%s", lang.c_str ()); + putenv (cmd); } setlocale (LC_ALL, ""); @@ -379,31 +443,42 @@ md5_digest (void const * data, int size) 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 (boost::filesystem::path file) +md5_digest (vector files, shared_ptr job) { - ifstream f (file.string().c_str(), std::ios::binary); - if (!f.good ()) { - throw OpenFileError (file.string()); - } - - f.seekg (0, std::ios::end); - int bytes = f.tellg (); - f.seekg (0, std::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]; @@ -417,33 +492,6 @@ md5_digest (boost::filesystem::path file) return s.str (); } -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 - film at (f + d) FPS it will last F(f + d) seconds. - - 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 - - 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 - */ - - return (fabs (a - b) < 1e-4); -} - /** @param An arbitrary audio frame rate. * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ @@ -457,37 +505,30 @@ dcp_audio_frame_rate (int fs) return 96000; } -/** @param index Colour LUT index. - * @return Human-readable name. - */ -string -colour_lut_index_to_name (int index) -{ - switch (index) { - case 0: - return _("sRGB"); - case 1: - return _("Rec 709"); - } - - assert (false); - return N_(""); -} - Socket::Socket (int timeout) : _deadline (_io_service) , _socket (_io_service) + , _acceptor (0) , _timeout (timeout) { _deadline.expires_at (boost::posix_time::pos_infin); check (); } +Socket::~Socket () +{ + delete _acceptor; +} + void Socket::check () { if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) { - _socket.close (); + if (_acceptor) { + _acceptor->cancel (); + } else { + _socket.close (); + } _deadline.expires_at (boost::posix_time::pos_infin); } @@ -498,7 +539,7 @@ Socket::check () * @param endpoint End-point to connect to. */ void -Socket::connect (boost::asio::ip::basic_resolver_entry const & endpoint) +Socket::connect (boost::asio::ip::tcp::endpoint endpoint) { _deadline.expires_from_now (boost::posix_time::seconds (_timeout)); boost::system::error_code ec = boost::asio::error::would_block; @@ -507,11 +548,35 @@ Socket::connect (boost::asio::ip::basic_resolver_entry con _io_service.run_one(); } while (ec == boost::asio::error::would_block); - if (ec || !_socket.is_open ()) { + if (ec) { + throw NetworkError (String::compose (_("error during async_connect (%1)"), ec.value ())); + } + + if (!_socket.is_open ()) { throw NetworkError (_("connect timed out")); } } +void +Socket::accept (int port) +{ + _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 == boost::asio::error::would_block ); + + delete _acceptor; + _acceptor = 0; + + if (ec) { + throw NetworkError (String::compose (_("error during async_accept (%1)"), ec.value ())); + } +} + /** Blocking write. * @param data Buffer to write. * @param size Number of bytes to write. @@ -529,7 +594,7 @@ Socket::write (uint8_t const * data, int size) } while (ec == boost::asio::error::would_block); if (ec) { - throw NetworkError (ec.message ()); + throw NetworkError (String::compose (_("error during async_write (%1)"), ec.value ())); } } @@ -557,7 +622,7 @@ Socket::read (uint8_t* data, int size) } while (ec == boost::asio::error::would_block); if (ec) { - throw NetworkError (ec.message ()); + throw NetworkError (String::compose (_("error during async_read (%1)"), ec.value ())); } } @@ -582,12 +647,6 @@ stride_round_up (int c, int const * stride, int t) 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 @@ -692,24 +751,14 @@ 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); + assert (MAX_AUDIO_CHANNELS == 8); /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency - enhancement channel (sub-woofer)./ + enhancement channel (sub-woofer). HI is the hearing-impaired audio track and + VI is the visually-impaired audio track (audio describe). */ string const channels[] = { _("Left"), @@ -718,55 +767,188 @@ audio_channel_name (int c) _("Lfe (sub)"), _("Left surround"), _("Right surround"), + _("HI"), + _("VI") }; return channels[c]; } -FrameRateConversion::FrameRateConversion (float source, int dcp) - : skip (false) - , repeat (false) - , change_speed (false) +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) { - if (fabs (source / 2.0 - dcp) < (fabs (source - dcp))) { - skip = true; - } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { - repeat = true; + 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 += '_'; + } } - change_speed = !about_equal (source * factor(), dcp); + return t; +} - 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"); +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); + dcp::make_signer_chain (sd, openssl_path ()); + break; } - if (change_speed) { - float const pc = dcp * 100 / (source * factor()); - description += String::compose (_("DCP will run at %1%% of the source speed.\n"), pc); + ++i; + } + + dcp::CertificateChain chain; + + { + boost::filesystem::path p (sd); + p /= "ca.self-signed.pem"; + chain.add (shared_ptr (new dcp::Certificate (p))); + } + + { + boost::filesystem::path p (sd); + p /= "intermediate.signed.pem"; + chain.add (shared_ptr (new dcp::Certificate (p))); + } + + { + boost::filesystem::path p (sd); + p /= "leaf.signed.pem"; + chain.add (shared_ptr (new dcp::Certificate (p))); + } + + boost::filesystem::path signer_key (sd); + signer_key /= "leaf.key"; + + return shared_ptr (new dcp::Signer (chain, signer_key)); +} + +map +split_get_request (string url) +{ + enum { + AWAITING_QUESTION_MARK, + KEY, + VALUE + } state = AWAITING_QUESTION_MARK; + + map r; + string k; + string v; + for (size_t i = 0; i < url.length(); ++i) { + switch (state) { + case AWAITING_QUESTION_MARK: + if (url[i] == '?') { + state = KEY; + } + break; + case KEY: + if (url[i] == '=') { + v.clear (); + state = VALUE; + } else { + k += url[i]; + } + break; + case VALUE: + if (url[i] == '&') { + r.insert (make_pair (k, v)); + k.clear (); + state = KEY; + } else { + v += url[i]; + } + break; } } + + if (state == VALUE) { + r.insert (make_pair (k, v)); + } + + return r; } -LocaleGuard::LocaleGuard () - : _old (0) +dcp::Size +fit_ratio_within (float ratio, dcp::Size full_frame) { - char const * old = setlocale (LC_NUMERIC, 0); + if (ratio < full_frame.ratio ()) { + return dcp::Size (rint (full_frame.height * ratio), full_frame.height); + } + + return dcp::Size (full_frame.width, rint (full_frame.width / ratio)); +} - if (old) { - _old = strdup (old); - if (strcmp (_old, "C")) { - setlocale (LC_NUMERIC, "C"); - } - } +void * +wrapped_av_malloc (size_t s) +{ + void* p = av_malloc (s); + if (!p) { + throw bad_alloc (); + } + return p; +} + +string +entities_to_text (string e) +{ + boost::algorithm::replace_all (e, "%3A", ":"); + boost::algorithm::replace_all (e, "%2F", "/"); + return e; } -LocaleGuard::~LocaleGuard () +int64_t +divide_with_round (int64_t a, int64_t b) { - setlocale (LC_NUMERIC, _old); - free (_old); + if (a % b >= (b / 2)) { + return (a + b - 1) / b; + } else { + return a / b; + } }