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