Trim unused stuff.
[dcpomatic.git] / src / lib / util.cc
1 /*
2     Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
3     Copyright (C) 2000-2007 Paul Davis
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 /** @file src/lib/util.cc
22  *  @brief Some utility functions and classes.
23  */
24
25 #include "util.h"
26 #include "exceptions.h"
27 #include "scaler.h"
28 #include "dcp_content_type.h"
29 #include "filter.h"
30 #include "cinema_sound_processor.h"
31 #include "config.h"
32 #include "ratio.h"
33 #include "job.h"
34 #include "cross.h"
35 #include "video_content.h"
36 #include "rect.h"
37 #include "md5_digester.h"
38 #include "audio_processor.h"
39 #include "safe_stringstream.h"
40 #ifdef DCPOMATIC_WINDOWS
41 #include "stack.hpp"
42 #endif
43 #include <dcp/version.h>
44 #include <dcp/util.h>
45 #include <dcp/signer.h>
46 #include <dcp/raw_convert.h>
47 extern "C" {
48 #include <libavcodec/avcodec.h>
49 #include <libavformat/avformat.h>
50 #include <libswscale/swscale.h>
51 #include <libavfilter/avfiltergraph.h>
52 #include <libavutil/pixfmt.h>
53 }
54 #include <glib.h>
55 #include <openjpeg.h>
56 #include <pangomm/init.h>
57 #ifdef DCPOMATIC_IMAGE_MAGICK
58 #include <magick/MagickCore.h>
59 #else
60 #include <magick/common.h>
61 #include <magick/magick_config.h>
62 #endif
63 #include <magick/version.h>
64 #include <libssh/libssh.h>
65 #include <boost/algorithm/string.hpp>
66 #include <boost/bind.hpp>
67 #include <boost/lambda/lambda.hpp>
68 #include <boost/thread.hpp>
69 #include <boost/filesystem.hpp>
70 #ifdef DCPOMATIC_WINDOWS
71 #include <boost/locale.hpp>
72 #endif
73 #include <signal.h>
74 #include <iomanip>
75 #include <iostream>
76 #include <fstream>
77 #include <climits>
78 #include <stdexcept>
79 #ifdef DCPOMATIC_POSIX
80 #include <execinfo.h>
81 #include <cxxabi.h>
82 #endif
83
84 #include "i18n.h"
85
86 using std::string;
87 using std::setfill;
88 using std::ostream;
89 using std::endl;
90 using std::vector;
91 using std::min;
92 using std::max;
93 using std::list;
94 using std::multimap;
95 using std::istream;
96 using std::pair;
97 using std::cout;
98 using std::bad_alloc;
99 using std::set_terminate;
100 using boost::shared_ptr;
101 using boost::thread;
102 using boost::optional;
103 using dcp::Size;
104 using dcp::raw_convert;
105
106 static boost::thread::id ui_thread;
107 static boost::filesystem::path backtrace_file;
108
109 /** Convert some number of seconds to a string representation
110  *  in hours, minutes and seconds.
111  *
112  *  @param s Seconds.
113  *  @return String of the form H:M:S (where H is hours, M
114  *  is minutes and S is seconds).
115  */
116 string
117 seconds_to_hms (int s)
118 {
119         int m = s / 60;
120         s -= (m * 60);
121         int h = m / 60;
122         m -= (h * 60);
123
124         SafeStringStream hms;
125         hms << h << N_(":");
126         hms.width (2);
127         hms << setfill ('0') << m << N_(":");
128         hms.width (2);
129         hms << setfill ('0') << s;
130
131         return hms.str ();
132 }
133
134 /** @param s Number of seconds.
135  *  @return String containing an approximate description of s (e.g. "about 2 hours")
136  */
137 string
138 seconds_to_approximate_hms (int s)
139 {
140         int m = s / 60;
141         s -= (m * 60);
142         int h = m / 60;
143         m -= (h * 60);
144
145         SafeStringStream ap;
146
147         bool const hours = h > 0;
148         bool const minutes = h < 10 && m > 0;
149         bool const seconds = m < 10 && s > 0;
150
151         if (hours) {
152                 if (m > 30 && !minutes) {
153                         /// TRANSLATORS: h here is an abbreviation for hours
154                         ap << (h + 1) << _("h");
155                 } else {
156                         /// TRANSLATORS: h here is an abbreviation for hours
157                         ap << h << _("h");
158                 }
159
160                 if (minutes | seconds) {
161                         ap << N_(" ");
162                 }
163         }
164
165         if (minutes) {
166                 /* Minutes */
167                 if (s > 30 && !seconds) {
168                         /// TRANSLATORS: m here is an abbreviation for minutes
169                         ap << (m + 1) << _("m");
170                 } else {
171                         /// TRANSLATORS: m here is an abbreviation for minutes
172                         ap << m << _("m");
173                 }
174
175                 if (seconds) {
176                         ap << N_(" ");
177                 }
178         }
179
180         if (seconds) {
181                 /* Seconds */
182                 /// TRANSLATORS: s here is an abbreviation for seconds
183                 ap << s << _("s");
184         }
185
186         return ap.str ();
187 }
188
189 #ifdef DCPOMATIC_POSIX
190 /** @param l Mangled C++ identifier.
191  *  @return Demangled version.
192  */
193 static string
194 demangle (string l)
195 {
196         string::size_type const b = l.find_first_of (N_("("));
197         if (b == string::npos) {
198                 return l;
199         }
200
201         string::size_type const p = l.find_last_of (N_("+"));
202         if (p == string::npos) {
203                 return l;
204         }
205
206         if ((p - b) <= 1) {
207                 return l;
208         }
209         
210         string const fn = l.substr (b + 1, p - b - 1);
211
212         int status;
213         try {
214                 
215                 char* realname = abi::__cxa_demangle (fn.c_str(), 0, 0, &status);
216                 string d (realname);
217                 free (realname);
218                 return d;
219                 
220         } catch (std::exception) {
221                 
222         }
223         
224         return l;
225 }
226
227 /** Write a stacktrace to an ostream.
228  *  @param out Stream to write to.
229  *  @param levels Number of levels to go up the call stack.
230  */
231 void
232 stacktrace (ostream& out, int levels)
233 {
234         void *array[200];
235         size_t size = backtrace (array, 200);
236         char** strings = backtrace_symbols (array, size);
237      
238         if (strings) {
239                 for (size_t i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
240                         out << N_("  ") << demangle (strings[i]) << "\n";
241                 }
242                 
243                 free (strings);
244         }
245 }
246 #endif
247
248 /** @param v Version as used by FFmpeg.
249  *  @return A string representation of v.
250  */
251 static string
252 ffmpeg_version_to_string (int v)
253 {
254         SafeStringStream s;
255         s << ((v & 0xff0000) >> 16) << N_(".") << ((v & 0xff00) >> 8) << N_(".") << (v & 0xff);
256         return s.str ();
257 }
258
259 double
260 seconds (struct timeval t)
261 {
262         return t.tv_sec + (double (t.tv_usec) / 1e6);
263 }
264
265 #ifdef DCPOMATIC_WINDOWS
266 LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
267 {
268         dbg::stack s;
269         FILE* f = fopen_boost (backtrace_file, "w");
270         fprintf (f, "Exception thrown:");
271         for (dbg::stack::const_iterator i = s.begin(); i != s.end(); ++i) {
272                 fprintf (f, "%p %s %d %s\n", i->instruction, i->function.c_str(), i->line, i->module.c_str());
273         }
274         fclose (f);
275         return EXCEPTION_CONTINUE_SEARCH;
276 }
277 #endif
278
279 void
280 set_backtrace_file (boost::filesystem::path p)
281 {
282         backtrace_file = p;
283 }
284
285 /* From http://stackoverflow.com/questions/2443135/how-do-i-find-where-an-exception-was-thrown-in-c */
286 void
287 terminate ()
288 {
289         static bool tried_throw = false;
290
291         try {
292                 // try once to re-throw currently active exception
293                 if (!tried_throw) {
294                         tried_throw = true;
295                         throw;
296                 }
297         }
298         catch (const std::exception &e) {
299                 std::cerr << __FUNCTION__ << " caught unhandled exception. what(): "
300                           << e.what() << std::endl;
301         }
302         catch (...) {
303                 std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." 
304                           << std::endl;
305         }
306
307 #ifdef DCPOMATIC_POSIX
308         stacktrace (cout, 50);
309 #endif
310         abort();
311 }
312
313 /** Call the required functions to set up DCP-o-matic's static arrays, etc.
314  *  Must be called from the UI thread, if there is one.
315  */
316 void
317 dcpomatic_setup ()
318 {
319 #ifdef DCPOMATIC_WINDOWS
320         boost::filesystem::path p = g_get_user_config_dir ();
321         p /= "backtrace.txt";
322         set_backtrace_file (p);
323         SetUnhandledExceptionFilter(exception_handler);
324
325         /* Dark voodoo which, I think, gets boost::filesystem::path to
326            correctly convert UTF-8 strings to paths, and also paths
327            back to UTF-8 strings (on path::string()).
328
329            After this, constructing boost::filesystem::paths from strings
330            converts from UTF-8 to UTF-16 inside the path.  Then
331            path::string().c_str() gives UTF-8 and
332            path::c_str()          gives UTF-16.
333
334            This is all Windows-only.  AFAICT Linux/OS X use UTF-8 everywhere,
335            so things are much simpler.
336         */
337         std::locale::global (boost::locale::generator().generate (""));
338         boost::filesystem::path::imbue (std::locale ());
339 #endif  
340         
341         avfilter_register_all ();
342
343 #ifdef DCPOMATIC_OSX
344         /* Add our lib directory to the libltdl search path so that
345            xmlsec can find xmlsec1-openssl.
346         */
347         boost::filesystem::path lib = app_contents ();
348         lib /= "lib";
349         setenv ("LTDL_LIBRARY_PATH", lib.c_str (), 1);
350 #endif
351
352         set_terminate (terminate);
353
354         Pango::init ();
355         dcp::init ();
356         
357         Ratio::setup_ratios ();
358         VideoContentScale::setup_scales ();
359         DCPContentType::setup_dcp_content_types ();
360         Scaler::setup_scalers ();
361         Filter::setup_filters ();
362         CinemaSoundProcessor::setup_cinema_sound_processors ();
363         AudioProcessor::setup_audio_processors ();
364
365         ui_thread = boost::this_thread::get_id ();
366 }
367
368 #ifdef DCPOMATIC_WINDOWS
369 boost::filesystem::path
370 mo_path ()
371 {
372         wchar_t buffer[512];
373         GetModuleFileName (0, buffer, 512 * sizeof(wchar_t));
374         boost::filesystem::path p (buffer);
375         p = p.parent_path ();
376         p = p.parent_path ();
377         p /= "locale";
378         return p;
379 }
380 #endif
381
382 #ifdef DCPOMATIC_OSX
383 boost::filesystem::path
384 mo_path ()
385 {
386         return "DCP-o-matic 2.app/Contents/Resources";
387 }
388 #endif
389
390 void
391 dcpomatic_setup_gettext_i18n (string lang)
392 {
393 #ifdef DCPOMATIC_LINUX
394         lang += ".UTF8";
395 #endif
396
397         if (!lang.empty ()) {
398                 /* Override our environment language.  Note that the caller must not
399                    free the string passed into putenv().
400                 */
401                 string s = String::compose ("LANGUAGE=%1", lang);
402                 putenv (strdup (s.c_str ()));
403                 s = String::compose ("LANG=%1", lang);
404                 putenv (strdup (s.c_str ()));
405                 s = String::compose ("LC_ALL=%1", lang);
406                 putenv (strdup (s.c_str ()));
407         }
408
409         setlocale (LC_ALL, "");
410         textdomain ("libdcpomatic");
411
412 #if defined(DCPOMATIC_WINDOWS) || defined(DCPOMATIC_OSX)
413         bindtextdomain ("libdcpomatic", mo_path().string().c_str());
414         bind_textdomain_codeset ("libdcpomatic", "UTF8");
415 #endif  
416
417 #ifdef DCPOMATIC_LINUX
418         bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
419 #endif
420 }
421
422 /** @param s A string.
423  *  @return Parts of the string split at spaces, except when a space is within quotation marks.
424  */
425 vector<string>
426 split_at_spaces_considering_quotes (string s)
427 {
428         vector<string> out;
429         bool in_quotes = false;
430         string c;
431         for (string::size_type i = 0; i < s.length(); ++i) {
432                 if (s[i] == ' ' && !in_quotes) {
433                         out.push_back (c);
434                         c = N_("");
435                 } else if (s[i] == '"') {
436                         in_quotes = !in_quotes;
437                 } else {
438                         c += s[i];
439                 }
440         }
441
442         out.push_back (c);
443         return out;
444 }
445
446 /** @param job Optional job for which to report progress */
447 string
448 md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job)
449 {
450         boost::uintmax_t const buffer_size = 64 * 1024;
451         char buffer[buffer_size];
452
453         MD5Digester digester;
454
455         vector<int64_t> sizes;
456         for (size_t i = 0; i < files.size(); ++i) {
457                 sizes.push_back (boost::filesystem::file_size (files[i]));
458         }
459
460         for (size_t i = 0; i < files.size(); ++i) {
461                 FILE* f = fopen_boost (files[i], "rb");
462                 if (!f) {
463                         throw OpenFileError (files[i].string());
464                 }
465
466                 boost::uintmax_t const bytes = boost::filesystem::file_size (files[i]);
467                 boost::uintmax_t remaining = bytes;
468
469                 while (remaining > 0) {
470                         int const t = min (remaining, buffer_size);
471                         int const r = fread (buffer, 1, t, f);
472                         if (r != t) {
473                                 throw ReadFileError (files[i], errno);
474                         }
475                         digester.add (buffer, t);
476                         remaining -= t;
477
478                         if (job) {
479                                 job->set_progress ((float (i) + 1 - float(remaining) / bytes) / files.size ());
480                         }
481                 }
482
483                 fclose (f);
484         }
485
486         return digester.get ();
487 }
488
489 /** @param An arbitrary audio frame rate.
490  *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
491  */
492 int
493 dcp_audio_frame_rate (int fs)
494 {
495         if (fs <= 48000) {
496                 return 48000;
497         }
498
499         return 96000;
500 }
501
502 Socket::Socket (int timeout)
503         : _deadline (_io_service)
504         , _socket (_io_service)
505         , _acceptor (0)
506         , _timeout (timeout)
507 {
508         _deadline.expires_at (boost::posix_time::pos_infin);
509         check ();
510 }
511
512 Socket::~Socket ()
513 {
514         delete _acceptor;
515 }
516
517 void
518 Socket::check ()
519 {
520         if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
521                 if (_acceptor) {
522                         _acceptor->cancel ();
523                 } else {
524                         _socket.close ();
525                 }
526                 _deadline.expires_at (boost::posix_time::pos_infin);
527         }
528
529         _deadline.async_wait (boost::bind (&Socket::check, this));
530 }
531
532 /** Blocking connect.
533  *  @param endpoint End-point to connect to.
534  */
535 void
536 Socket::connect (boost::asio::ip::tcp::endpoint endpoint)
537 {
538         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
539         boost::system::error_code ec = boost::asio::error::would_block;
540         _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
541         do {
542                 _io_service.run_one();
543         } while (ec == boost::asio::error::would_block);
544
545         if (ec) {
546                 throw NetworkError (String::compose (_("error during async_connect (%1)"), ec.value ()));
547         }
548
549         if (!_socket.is_open ()) {
550                 throw NetworkError (_("connect timed out"));
551         }
552 }
553
554 void
555 Socket::accept (int port)
556 {
557         _acceptor = new boost::asio::ip::tcp::acceptor (_io_service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port));
558         
559         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
560         boost::system::error_code ec = boost::asio::error::would_block;
561         _acceptor->async_accept (_socket, boost::lambda::var(ec) = boost::lambda::_1);
562         do {
563                 _io_service.run_one ();
564         } while (ec == boost::asio::error::would_block);
565
566         delete _acceptor;
567         _acceptor = 0;
568         
569         if (ec) {
570                 throw NetworkError (String::compose (_("error during async_accept (%1)"), ec.value ()));
571         }
572 }
573
574 /** Blocking write.
575  *  @param data Buffer to write.
576  *  @param size Number of bytes to write.
577  */
578 void
579 Socket::write (uint8_t const * data, int size)
580 {
581         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
582         boost::system::error_code ec = boost::asio::error::would_block;
583
584         boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
585         
586         do {
587                 _io_service.run_one ();
588         } while (ec == boost::asio::error::would_block);
589
590         if (ec) {
591                 throw NetworkError (String::compose (_("error during async_write (%1)"), ec.value ()));
592         }
593 }
594
595 void
596 Socket::write (uint32_t v)
597 {
598         v = htonl (v);
599         write (reinterpret_cast<uint8_t*> (&v), 4);
600 }
601
602 /** Blocking read.
603  *  @param data Buffer to read to.
604  *  @param size Number of bytes to read.
605  */
606 void
607 Socket::read (uint8_t* data, int size)
608 {
609         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
610         boost::system::error_code ec = boost::asio::error::would_block;
611
612         boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
613
614         do {
615                 _io_service.run_one ();
616         } while (ec == boost::asio::error::would_block);
617         
618         if (ec) {
619                 throw NetworkError (String::compose (_("error during async_read (%1)"), ec.value ()));
620         }
621 }
622
623 uint32_t
624 Socket::read_uint32 ()
625 {
626         uint32_t v;
627         read (reinterpret_cast<uint8_t *> (&v), 4);
628         return ntohl (v);
629 }
630
631 /** Round a number up to the nearest multiple of another number.
632  *  @param c Index.
633  *  @param s Array of numbers to round, indexed by c.
634  *  @param t Multiple to round to.
635  *  @return Rounded number.
636  */
637 int
638 stride_round_up (int c, int const * stride, int t)
639 {
640         int const a = stride[c] + (t - 1);
641         return a - (a % t);
642 }
643
644 /** @param n A number.
645  *  @param r Rounding `boundary' (must be a power of 2)
646  *  @return n rounded to the nearest r
647  */
648 int
649 round_to (float n, int r)
650 {
651         assert (r == 1 || r == 2 || r == 4);
652         return int (n + float(r) / 2) &~ (r - 1);
653 }
654
655 /** Read a sequence of key / value pairs from a text stream;
656  *  the keys are the first words on the line, and the values are
657  *  the remainder of the line following the key.  Lines beginning
658  *  with # are ignored.
659  *  @param s Stream to read.
660  *  @return key/value pairs.
661  */
662 multimap<string, string>
663 read_key_value (istream &s) 
664 {
665         multimap<string, string> kv;
666         
667         string line;
668         while (getline (s, line)) {
669                 if (line.empty ()) {
670                         continue;
671                 }
672
673                 if (line[0] == '#') {
674                         continue;
675                 }
676
677                 if (line[line.size() - 1] == '\r') {
678                         line = line.substr (0, line.size() - 1);
679                 }
680
681                 size_t const s = line.find (' ');
682                 if (s == string::npos) {
683                         continue;
684                 }
685
686                 kv.insert (make_pair (line.substr (0, s), line.substr (s + 1)));
687         }
688
689         return kv;
690 }
691
692 string
693 get_required_string (multimap<string, string> const & kv, string k)
694 {
695         if (kv.count (k) > 1) {
696                 throw StringError (N_("unexpected multiple keys in key-value set"));
697         }
698
699         multimap<string, string>::const_iterator i = kv.find (k);
700         
701         if (i == kv.end ()) {
702                 throw StringError (String::compose (_("missing key %1 in key-value set"), k));
703         }
704
705         return i->second;
706 }
707
708 int
709 get_required_int (multimap<string, string> const & kv, string k)
710 {
711         string const v = get_required_string (kv, k);
712         return raw_convert<int> (v);
713 }
714
715 float
716 get_required_float (multimap<string, string> const & kv, string k)
717 {
718         string const v = get_required_string (kv, k);
719         return raw_convert<float> (v);
720 }
721
722 string
723 get_optional_string (multimap<string, string> const & kv, string k)
724 {
725         if (kv.count (k) > 1) {
726                 throw StringError (N_("unexpected multiple keys in key-value set"));
727         }
728
729         multimap<string, string>::const_iterator i = kv.find (k);
730         if (i == kv.end ()) {
731                 return N_("");
732         }
733
734         return i->second;
735 }
736
737 int
738 get_optional_int (multimap<string, string> const & kv, string k)
739 {
740         if (kv.count (k) > 1) {
741                 throw StringError (N_("unexpected multiple keys in key-value set"));
742         }
743
744         multimap<string, string>::const_iterator i = kv.find (k);
745         if (i == kv.end ()) {
746                 return 0;
747         }
748
749         return raw_convert<int> (i->second);
750 }
751
752 /** Trip an assert if the caller is not in the UI thread */
753 void
754 ensure_ui_thread ()
755 {
756         assert (boost::this_thread::get_id() == ui_thread);
757 }
758
759 string
760 audio_channel_name (int c)
761 {
762         assert (MAX_DCP_AUDIO_CHANNELS == 12);
763
764         /// TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
765         /// enhancement channel (sub-woofer).  HI is the hearing-impaired audio track and
766         /// VI is the visually-impaired audio track (audio describe).
767         string const channels[] = {
768                 _("Left"),
769                 _("Right"),
770                 _("Centre"),
771                 _("Lfe (sub)"),
772                 _("Left surround"),
773                 _("Right surround"),
774                 _("Hearing impaired"),
775                 _("Visually impaired"),
776                 _("Left centre"),
777                 _("Right centre"),
778                 _("Left rear surround"),
779                 _("Right rear surround"),
780         };
781
782         return channels[c];
783 }
784
785 bool
786 valid_image_file (boost::filesystem::path f)
787 {
788         string ext = f.extension().string();
789         transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
790         return (
791                 ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" ||
792                 ext == ".png" || ext == ".bmp" || ext == ".tga" || ext == ".dpx" ||
793                 ext == ".j2c" || ext == ".j2k"
794                 );
795 }
796
797 bool
798 valid_j2k_file (boost::filesystem::path f)
799 {
800         string ext = f.extension().string();
801         transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
802         return (ext == ".j2k" || ext == ".j2c");
803 }
804
805 string
806 tidy_for_filename (string f)
807 {
808         string t;
809         for (size_t i = 0; i < f.length(); ++i) {
810                 if (isalnum (f[i]) || f[i] == '_' || f[i] == '-') {
811                         t += f[i];
812                 } else {
813                         t += '_';
814                 }
815         }
816
817         return t;
818 }
819
820 dcp::Size
821 fit_ratio_within (float ratio, dcp::Size full_frame, int round)
822 {
823         if (ratio < full_frame.ratio ()) {
824                 return dcp::Size (round_to (full_frame.height * ratio, round), full_frame.height);
825         }
826         
827         return dcp::Size (full_frame.width, round_to (full_frame.width / ratio, round));
828 }
829
830 void *
831 wrapped_av_malloc (size_t s)
832 {
833         void* p = av_malloc (s);
834         if (!p) {
835                 throw bad_alloc ();
836         }
837         return p;
838 }
839                 
840 string
841 entities_to_text (string e)
842 {
843         boost::algorithm::replace_all (e, "%3A", ":");
844         boost::algorithm::replace_all (e, "%2F", "/");
845         return e;
846 }
847
848 int64_t
849 divide_with_round (int64_t a, int64_t b)
850 {
851         if (a % b >= (b / 2)) {
852                 return (a + b - 1) / b;
853         } else {
854                 return a / b;
855         }
856 }
857
858 /** Return a user-readable string summarising the versions of our dependencies */
859 string
860 dependency_version_summary ()
861 {
862         SafeStringStream s;
863         s << N_("libopenjpeg ") << opj_version () << N_(", ")
864           << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
865           << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
866           << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
867           << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
868           << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
869           << MagickVersion << N_(", ")
870           << N_("libssh ") << ssh_version (0) << N_(", ")
871           << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
872
873         return s.str ();
874 }
875
876 /** Construct a ScopedTemporary.  A temporary filename is decided but the file is not opened
877  *  until ::open() is called.
878  */
879 ScopedTemporary::ScopedTemporary ()
880         : _open (0)
881 {
882         _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
883 }
884
885 /** Close and delete the temporary file */
886 ScopedTemporary::~ScopedTemporary ()
887 {
888         close ();       
889         boost::system::error_code ec;
890         boost::filesystem::remove (_file, ec);
891 }
892
893 /** @return temporary filename */
894 char const *
895 ScopedTemporary::c_str () const
896 {
897         return _file.string().c_str ();
898 }
899
900 /** Open the temporary file.
901  *  @return File's FILE pointer.
902  */
903 FILE*
904 ScopedTemporary::open (char const * params)
905 {
906         _open = fopen (c_str(), params);
907         return _open;
908 }
909
910 /** Close the file */
911 void
912 ScopedTemporary::close ()
913 {
914         if (_open) {
915                 fclose (_open);
916                 _open = 0;
917         }
918 }
919
920 ContentTimePeriod
921 subtitle_period (AVSubtitle const & sub)
922 {
923         ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE);
924
925         ContentTimePeriod period (
926                 packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3),
927                 packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3)
928                 );
929
930         return period;
931 }