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