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