X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Futil.cc;h=381c47a9ae606253a7207a492351965987638c58;hb=cc27c2716f755305d67f1e1ba828ecf37f8405dd;hp=ec1fd47bd7f03bdca133d791b6201d824f252cab;hpb=9525e7726e4d488f193957d4fcf1cc1725581ae8;p=dcpomatic.git diff --git a/src/lib/util.cc b/src/lib/util.cc index ec1fd47bd..60953b544 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington Copyright (C) 2000-2007 Paul Davis This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef DCPOMATIC_POSIX #include #include @@ -36,30 +37,44 @@ #include #include #include -#include #include #include +#ifdef DCPOMATIC_WINDOWS +#include +#endif +#include #include -#include +#include #include #include -#include +#include +#include +#include +#include extern "C" { #include #include #include #include -#include #include } #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 "cinema_sound_processor.h" #include "config.h" +#include "ratio.h" +#include "job.h" +#include "cross.h" +#include "video_content.h" +#include "rect.h" +#include "md5_digester.h" +#include "audio_processor.h" +#ifdef DCPOMATIC_WINDOWS +#include "stack.hpp" +#endif #include "i18n.h" @@ -71,22 +86,27 @@ 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::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; +using dcp::raw_convert; -boost::thread::id ui_thread; +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. @@ -125,25 +145,54 @@ seconds_to_approximate_hms (int s) m -= (h * 60); stringstream ap; - - if (h > 0) { - if (m > 30) { + + bool const hours = h > 0; + bool const minutes = h < 10 && m > 0; + bool const seconds = m < 10 && s > 0; + + if (hours) { + if (m > 30 && !minutes) { ap << (h + 1) << N_(" ") << _("hours"); } else { + ap << h << N_(" "); if (h == 1) { - ap << N_("1 ") << _("hour"); + ap << _("hour"); } else { - ap << h << N_(" ") << _("hours"); + ap << _("hours"); } } - } else if (m > 0) { - if (m == 1) { - ap << N_("1 ") << _("minute"); + + if (minutes | seconds) { + ap << N_(" "); + } + } + + if (minutes) { + /* Minutes */ + if (s > 30 && !seconds) { + ap << (m + 1) << N_(" ") << _("minutes"); } else { - ap << m << N_(" ") << _("minutes"); + ap << m << N_(" "); + if (m == 1) { + ap << _("minute"); + } else { + ap << _("minutes"); + } + } + + if (seconds) { + ap << N_(" "); + } + } + + if (seconds) { + /* Seconds */ + ap << s << N_(" "); + if (s == 1) { + ap << _("second"); + } else { + ap << _("seconds"); } - } else { - ap << s << N_(" ") << _("seconds"); } return ap.str (); @@ -195,15 +244,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"; } @@ -223,29 +268,52 @@ ffmpeg_version_to_string (int v) return s.str (); } -/** Return a user-readable string summarising the versions of our dependencies */ -string -dependency_version_summary () +double +seconds (struct timeval t) { - stringstream s; - 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 t.tv_sec + (double (t.tv_usec) / 1e6); +} - return s.str (); +#ifdef DCPOMATIC_WINDOWS +LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *) +{ + dbg::stack s; + FILE* f = fopen_boost (backtrace_file, "w"); + fprintf (f, "Exception thrown:"); + for (dbg::stack::const_iterator i = s.begin(); i != s.end(); ++i) { + fprintf (f, "%p %s %d %s\n", i->instruction, i->function.c_str(), i->line, i->module.c_str()); + } + fclose (f); + return EXCEPTION_CONTINUE_SEARCH; } +#endif -double -seconds (struct timeval t) +/* From http://stackoverflow.com/questions/2443135/how-do-i-find-where-an-exception-was-thrown-in-c */ +void +terminate () { - return t.tv_sec + (double (t.tv_usec) / 1e6); + static bool tried_throw = false; + + try { + // try once to re-throw currently active exception + if (!tried_throw) { + tried_throw = true; + 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. @@ -254,13 +322,50 @@ seconds (struct timeval t) void 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 + + set_terminate (terminate); + + Pango::init (); + dcp::init (); - Format::setup_formats (); + Ratio::setup_ratios (); + VideoContentScale::setup_scales (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); Filter::setup_filters (); - SoundProcessor::setup_sound_processors (); + CinemaSoundProcessor::setup_cinema_sound_processors (); + AudioProcessor::setup_audio_processors (); ui_thread = boost::this_thread::get_id (); } @@ -280,7 +385,7 @@ mo_path () #endif void -dcpomatic_setup_i18n (string lang) +dcpomatic_setup_gettext_i18n (string lang) { #ifdef DCPOMATIC_POSIX lang += ".UTF8"; @@ -295,6 +400,8 @@ dcpomatic_setup_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, ""); @@ -310,18 +417,6 @@ dcpomatic_setup_i18n (string lang) #endif } -/** @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, libdcp::Size size) -{ - stringstream s; - s << N_("crop=") << size.width << N_(":") << size.height << N_(":") << start.x << N_(":") << start.y; - return s.str (); -} - /** @param s A string. * @return Parts of the string split at spaces, except when a space is within quotation marks. */ @@ -346,148 +441,51 @@ split_at_spaces_considering_quotes (string s) return out; } +/** @param job Optional job for which to report progress */ string -md5_digest (void const * data, int size) -{ - MD5_CTX md5_context; - MD5_Init (&md5_context); - MD5_Update (&md5_context, data, size); - unsigned char digest[MD5_DIGEST_LENGTH]; - MD5_Final (digest, &md5_context); - - stringstream s; - for (int i = 0; i < MD5_DIGEST_LENGTH; ++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. - */ -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; - } - - unsigned char digest[MD5_DIGEST_LENGTH]; - MD5_Final (digest, &md5_context); + MD5Digester digester; - stringstream s; - for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { - s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); + vector sizes; + for (size_t i = 0; i < files.size(); ++i) { + sizes.push_back (boost::filesystem::file_size (files[i])); } - 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); -} - -class FrameRateCandidate -{ -public: - FrameRateCandidate (float source_, int dcp_) - : source (source_) - , dcp (dcp_) - {} - - float source; - int dcp; -}; - -int -best_dcp_frame_rate (float source_fps) -{ - list const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); - - /* Work out what rates we could manage, including those achieved by using skip / repeat. */ - list candidates; + for (size_t i = 0; i < files.size(); ++i) { + FILE* f = fopen_boost (files[i], "rb"); + if (!f) { + throw OpenFileError (files[i].string()); + } - /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ - for (list::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { - candidates.push_back (FrameRateCandidate (*i, *i)); - } + boost::uintmax_t const bytes = boost::filesystem::file_size (files[i]); + boost::uintmax_t remaining = bytes; - /* Then the skip/repeat ones */ - for (list::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { - candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); - candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); - } - - /* Pick the best one, bailing early if we hit an exact match */ - float error = std::numeric_limits::max (); - optional best; - list::iterator i = candidates.begin(); - while (i != candidates.end()) { - - if (about_equal (i->source, source_fps)) { - best = *i; - break; - } + while (remaining > 0) { + int const t = min (remaining, buffer_size); + fread (buffer, 1, t, f); + digester.add (buffer, t); + remaining -= t; - float const e = fabs (i->source - source_fps); - if (e < error) { - error = e; - best = *i; + if (job) { + job->set_progress ((float (i) + 1 - float(remaining) / bytes) / files.size ()); + } } - ++i; + fclose (f); } - assert (best); - return best->dcp; + return digester.get (); } -/** @param An arbitrary sampling rate. - * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz). +/** @param An arbitrary audio frame rate. + * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ int -dcp_audio_sample_rate (int fs) +dcp_audio_frame_rate (int fs) { if (fs <= 48000) { return 48000; @@ -496,37 +494,30 @@ dcp_audio_sample_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); } @@ -537,7 +528,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; @@ -546,11 +537,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. @@ -568,7 +583,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 ())); } } @@ -596,7 +611,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 ())); } } @@ -608,22 +623,6 @@ Socket::read_uint32 () return ntohl (v); } -/** @param other A Rect. - * @return The intersection of this with `other'. - */ -Rect -Rect::intersection (Rect const & other) const -{ - 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 - ); -} - /** Round a number up to the nearest multiple of another number. * @param c Index. * @param s Array of numbers to round, indexed by c. @@ -637,10 +636,15 @@ stride_round_up (int c, int const * stride, int t) return a - (a % t); } +/** @param n A number. + * @param r Rounding `boundary' (must be a power of 2) + * @return n rounded to the nearest r + */ int -stride_lookup (int c, int const * stride) +round_to (float n, int r) { - return stride[c]; + assert (r == 1 || r == 2 || r == 4); + return int (n + float(r) / 2) &~ (r - 1); } /** Read a sequence of key / value pairs from a text stream; @@ -700,14 +704,14 @@ int get_required_int (multimap const & kv, string k) { string const v = get_required_string (kv, k); - return lexical_cast (v); + return raw_convert (v); } float get_required_float (multimap const & kv, string k) { string const v = get_required_string (kv, k); - return lexical_cast (v); + return raw_convert (v); } string @@ -737,277 +741,221 @@ get_optional_int (multimap const & kv, string k) return 0; } - return lexical_cast (i->second); + return raw_convert (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) +/** Trip an assert if the caller is not in the UI thread */ +void +ensure_ui_thread () { - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[frames]; - } + assert (boost::this_thread::get_id() == ui_thread); } -/** Copy constructor. - * @param other Other AudioBuffers; data is copied. - */ -AudioBuffers::AudioBuffers (AudioBuffers const & other) - : _channels (other._channels) - , _frames (other._frames) - , _allocated_frames (other._frames) +string +audio_channel_name (int c) { - _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)); - } + assert (MAX_DCP_AUDIO_CHANNELS == 12); + + /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency + 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"), + _("Right"), + _("Centre"), + _("Lfe (sub)"), + _("Left surround"), + _("Right surround"), + _("Hearing impaired"), + _("Visually impaired"), + _("Left centre"), + _("Right centre"), + _("Left rear surround"), + _("Right rear surround"), + }; + + return channels[c]; } -/* 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) +bool +valid_image_file (boost::filesystem::path f) { - _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)); - } + 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" || ext == ".dpx"); } -/** AudioBuffers destructor */ -AudioBuffers::~AudioBuffers () +string +tidy_for_filename (string f) { - for (int i = 0; i < _channels; ++i) { - delete[] _data[i]; + string t; + for (size_t i = 0; i < f.length(); ++i) { + if (isalnum (f[i]) || f[i] == '_' || f[i] == '-') { + t += f[i]; + } else { + t += '_'; + } } - delete[] _data; + return t; } -/** @param c Channel index. - * @return Buffer for this channel. - */ -float* -AudioBuffers::data (int c) const +map +split_get_request (string url) { - assert (c >= 0 && c < _channels); - return _data[c]; -} + 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; + } + } -/** 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) -{ - assert (f <= _allocated_frames); - _frames = f; + if (state == VALUE) { + r.insert (make_pair (k, v)); + } + + return r; } -/** Make all samples on all channels silent */ -void -AudioBuffers::make_silent () +dcp::Size +fit_ratio_within (float ratio, dcp::Size full_frame, int round) { - for (int i = 0; i < _channels; ++i) { - make_silent (i); + if (ratio < full_frame.ratio ()) { + return dcp::Size (round_to (full_frame.height * ratio, round), full_frame.height); } + + return dcp::Size (full_frame.width, round_to (full_frame.width / ratio, round)); } -/** Make all samples on a given channel silent. - * @param c Channel. - */ -void -AudioBuffers::make_silent (int c) +void * +wrapped_av_malloc (size_t s) { - assert (c >= 0 && c < _channels); - - for (int i = 0; i < _frames; ++i) { - _data[c][i] = 0; + void* p = av_malloc (s); + if (!p) { + throw bad_alloc (); } + return p; } - -/** 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) + +string +entities_to_text (string e) { - assert (from->channels() == channels()); - - 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); - - for (int i = 0; i < _channels; ++i) { - memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float)); - } + boost::algorithm::replace_all (e, "%3A", ":"); + boost::algorithm::replace_all (e, "%2F", "/"); + return e; } -/** 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) +int64_t +divide_with_round (int64_t a, int64_t b) { - 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)); + if (a % b >= (b / 2)) { + return (a + b - 1) / b; + } else { + return a / b; } } -/** Add data from from `from', `from_channel' to our channel `to_channel' */ -void -AudioBuffers::accumulate (shared_ptr from, int from_channel, int to_channel) +/** Return a user-readable string summarising the versions of our dependencies */ +string +dependency_version_summary () { - int const N = frames (); - assert (from->frames() == N); - - float* s = from->data (from_channel); - float* d = _data[to_channel]; + stringstream s; + 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_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ") + << MagickVersion << N_(", ") + << N_("libssh ") << ssh_version (0) << N_(", ") + << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit; - for (int i = 0; i < N; ++i) { - *d++ += *s++; - } + return s.str (); } -/** Trip an assert if the caller is not in the UI thread */ -void -ensure_ui_thread () +/** Construct a ScopedTemporary. A temporary filename is decided but the file is not opened + * until ::open() is called. + */ +ScopedTemporary::ScopedTemporary () + : _open (0) { - assert (boost::this_thread::get_id() == ui_thread); + _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path (); } -/** @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 (ContentVideoFrame v, float audio_sample_rate, float frames_per_second) +/** Close and delete the temporary file */ +ScopedTemporary::~ScopedTemporary () { - return ((int64_t) v * audio_sample_rate / frames_per_second); + close (); + boost::system::error_code ec; + boost::filesystem::remove (_file, ec); } -/** @return A pair containing CPU model name and the number of processors */ -pair -cpu_info () +/** @return temporary filename */ +char const * +ScopedTemporary::c_str () const { - pair info; - info.second = 0; - -#ifdef DCPOMATIC_POSIX - ifstream f (N_("/proc/cpuinfo")); - while (f.good ()) { - string l; - getline (f, l); - 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, N_("processor"))) { - ++info.second; - } - } -#endif - - return info; + return _file.string().c_str (); } -string -audio_channel_name (int c) +/** Open the temporary file. + * @return File's FILE pointer. + */ +FILE* +ScopedTemporary::open (char const * params) { - 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]; + _open = fopen (c_str(), params); + return _open; } -FrameRateConversion::FrameRateConversion (float source, int dcp) - : skip (false) - , repeat (false) - , change_speed (false) +/** Close the file */ +void +ScopedTemporary::close () { - 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); - } + if (_open) { + fclose (_open); + _open = 0; } } -LocaleGuard::LocaleGuard () - : _old (0) +ContentTimePeriod +subtitle_period (AVSubtitle const & sub) { - char const * old = setlocale (LC_NUMERIC, 0); + ContentTime const packet_time = ContentTime::from_seconds (static_cast (sub.pts) / AV_TIME_BASE); - if (old) { - _old = strdup (old); - if (strcmp (_old, "POSIX")) { - setlocale (LC_NUMERIC, "POSIX"); - } - } -} + ContentTimePeriod period ( + packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3), + packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3) + ); -LocaleGuard::~LocaleGuard () -{ - setlocale (LC_NUMERIC, _old); - free (_old); + return period; }