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