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