290dd20ef1f7d186f7844caa9222c175058a14e4
[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 #ifdef DCPOMATIC_OSX
401 boost::filesystem::path
402 mo_path ()
403 {
404         return "DCP-o-matic.app/Contents/Resources";
405 }
406 #endif
407
408 void
409 dcpomatic_setup_gettext_i18n (string lang)
410 {
411 #ifdef DCPOMATIC_LINUX
412         lang += ".UTF8";
413 #endif
414
415         if (!lang.empty ()) {
416                 /* Override our environment language.  Note that the caller must not
417                    free the string passed into putenv().
418                 */
419                 string s = String::compose ("LANGUAGE=%1", lang);
420                 putenv (strdup (s.c_str ()));
421                 s = String::compose ("LANG=%1", lang);
422                 putenv (strdup (s.c_str ()));
423                 s = String::compose ("LC_ALL=%1", lang);
424                 putenv (strdup (s.c_str ()));
425         }
426
427         setlocale (LC_ALL, "");
428         textdomain ("libdcpomatic");
429
430 #if defined(DCPOMATIC_WINDOWS) || defined(DCPOMATIC_OSX)
431         bindtextdomain ("libdcpomatic", mo_path().string().c_str());
432         bind_textdomain_codeset ("libdcpomatic", "UTF8");
433 #endif  
434
435 #ifdef DCPOMATIC_LINUX
436         bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
437 #endif
438 }
439
440 /** @param s A string.
441  *  @return Parts of the string split at spaces, except when a space is within quotation marks.
442  */
443 vector<string>
444 split_at_spaces_considering_quotes (string s)
445 {
446         vector<string> out;
447         bool in_quotes = false;
448         string c;
449         for (string::size_type i = 0; i < s.length(); ++i) {
450                 if (s[i] == ' ' && !in_quotes) {
451                         out.push_back (c);
452                         c = N_("");
453                 } else if (s[i] == '"') {
454                         in_quotes = !in_quotes;
455                 } else {
456                         c += s[i];
457                 }
458         }
459
460         out.push_back (c);
461         return out;
462 }
463
464 /** @param job Optional job for which to report progress */
465 string
466 md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job)
467 {
468         boost::uintmax_t const buffer_size = 64 * 1024;
469         char buffer[buffer_size];
470
471         MD5Digester digester;
472
473         vector<int64_t> sizes;
474         for (size_t i = 0; i < files.size(); ++i) {
475                 sizes.push_back (boost::filesystem::file_size (files[i]));
476         }
477
478         for (size_t i = 0; i < files.size(); ++i) {
479                 FILE* f = fopen_boost (files[i], "rb");
480                 if (!f) {
481                         throw OpenFileError (files[i].string());
482                 }
483
484                 boost::uintmax_t const bytes = boost::filesystem::file_size (files[i]);
485                 boost::uintmax_t remaining = bytes;
486
487                 while (remaining > 0) {
488                         int const t = min (remaining, buffer_size);
489                         fread (buffer, 1, t, f);
490                         digester.add (buffer, t);
491                         remaining -= t;
492
493                         if (job) {
494                                 job->set_progress ((float (i) + 1 - float(remaining) / bytes) / files.size ());
495                         }
496                 }
497
498                 fclose (f);
499         }
500
501         return digester.get ();
502 }
503
504 /** @param An arbitrary audio frame rate.
505  *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
506  */
507 int
508 dcp_audio_frame_rate (int fs)
509 {
510         if (fs <= 48000) {
511                 return 48000;
512         }
513
514         return 96000;
515 }
516
517 Socket::Socket (int timeout)
518         : _deadline (_io_service)
519         , _socket (_io_service)
520         , _acceptor (0)
521         , _timeout (timeout)
522 {
523         _deadline.expires_at (boost::posix_time::pos_infin);
524         check ();
525 }
526
527 Socket::~Socket ()
528 {
529         delete _acceptor;
530 }
531
532 void
533 Socket::check ()
534 {
535         if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
536                 if (_acceptor) {
537                         _acceptor->cancel ();
538                 } else {
539                         _socket.close ();
540                 }
541                 _deadline.expires_at (boost::posix_time::pos_infin);
542         }
543
544         _deadline.async_wait (boost::bind (&Socket::check, this));
545 }
546
547 /** Blocking connect.
548  *  @param endpoint End-point to connect to.
549  */
550 void
551 Socket::connect (boost::asio::ip::tcp::endpoint endpoint)
552 {
553         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
554         boost::system::error_code ec = boost::asio::error::would_block;
555         _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
556         do {
557                 _io_service.run_one();
558         } while (ec == boost::asio::error::would_block);
559
560         if (ec) {
561                 throw NetworkError (String::compose (_("error during async_connect (%1)"), ec.value ()));
562         }
563
564         if (!_socket.is_open ()) {
565                 throw NetworkError (_("connect timed out"));
566         }
567 }
568
569 void
570 Socket::accept (int port)
571 {
572         _acceptor = new boost::asio::ip::tcp::acceptor (_io_service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port));
573         
574         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
575         boost::system::error_code ec = boost::asio::error::would_block;
576         _acceptor->async_accept (_socket, boost::lambda::var(ec) = boost::lambda::_1);
577         do {
578                 _io_service.run_one ();
579         } while (ec == boost::asio::error::would_block);
580
581         delete _acceptor;
582         _acceptor = 0;
583         
584         if (ec) {
585                 throw NetworkError (String::compose (_("error during async_accept (%1)"), ec.value ()));
586         }
587 }
588
589 /** Blocking write.
590  *  @param data Buffer to write.
591  *  @param size Number of bytes to write.
592  */
593 void
594 Socket::write (uint8_t const * data, int size)
595 {
596         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
597         boost::system::error_code ec = boost::asio::error::would_block;
598
599         boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
600         
601         do {
602                 _io_service.run_one ();
603         } while (ec == boost::asio::error::would_block);
604
605         if (ec) {
606                 throw NetworkError (String::compose (_("error during async_write (%1)"), ec.value ()));
607         }
608 }
609
610 void
611 Socket::write (uint32_t v)
612 {
613         v = htonl (v);
614         write (reinterpret_cast<uint8_t*> (&v), 4);
615 }
616
617 /** Blocking read.
618  *  @param data Buffer to read to.
619  *  @param size Number of bytes to read.
620  */
621 void
622 Socket::read (uint8_t* data, int size)
623 {
624         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
625         boost::system::error_code ec = boost::asio::error::would_block;
626
627         boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
628
629         do {
630                 _io_service.run_one ();
631         } while (ec == boost::asio::error::would_block);
632         
633         if (ec) {
634                 throw NetworkError (String::compose (_("error during async_read (%1)"), ec.value ()));
635         }
636 }
637
638 uint32_t
639 Socket::read_uint32 ()
640 {
641         uint32_t v;
642         read (reinterpret_cast<uint8_t *> (&v), 4);
643         return ntohl (v);
644 }
645
646 /** Round a number up to the nearest multiple of another number.
647  *  @param c Index.
648  *  @param s Array of numbers to round, indexed by c.
649  *  @param t Multiple to round to.
650  *  @return Rounded number.
651  */
652 int
653 stride_round_up (int c, int const * stride, int t)
654 {
655         int const a = stride[c] + (t - 1);
656         return a - (a % t);
657 }
658
659 /** Read a sequence of key / value pairs from a text stream;
660  *  the keys are the first words on the line, and the values are
661  *  the remainder of the line following the key.  Lines beginning
662  *  with # are ignored.
663  *  @param s Stream to read.
664  *  @return key/value pairs.
665  */
666 multimap<string, string>
667 read_key_value (istream &s) 
668 {
669         multimap<string, string> kv;
670         
671         string line;
672         while (getline (s, line)) {
673                 if (line.empty ()) {
674                         continue;
675                 }
676
677                 if (line[0] == '#') {
678                         continue;
679                 }
680
681                 if (line[line.size() - 1] == '\r') {
682                         line = line.substr (0, line.size() - 1);
683                 }
684
685                 size_t const s = line.find (' ');
686                 if (s == string::npos) {
687                         continue;
688                 }
689
690                 kv.insert (make_pair (line.substr (0, s), line.substr (s + 1)));
691         }
692
693         return kv;
694 }
695
696 string
697 get_required_string (multimap<string, string> const & kv, string k)
698 {
699         if (kv.count (k) > 1) {
700                 throw StringError (N_("unexpected multiple keys in key-value set"));
701         }
702
703         multimap<string, string>::const_iterator i = kv.find (k);
704         
705         if (i == kv.end ()) {
706                 throw StringError (String::compose (_("missing key %1 in key-value set"), k));
707         }
708
709         return i->second;
710 }
711
712 int
713 get_required_int (multimap<string, string> const & kv, string k)
714 {
715         string const v = get_required_string (kv, k);
716         return raw_convert<int> (v);
717 }
718
719 float
720 get_required_float (multimap<string, string> const & kv, string k)
721 {
722         string const v = get_required_string (kv, k);
723         return raw_convert<float> (v);
724 }
725
726 string
727 get_optional_string (multimap<string, string> const & kv, string k)
728 {
729         if (kv.count (k) > 1) {
730                 throw StringError (N_("unexpected multiple keys in key-value set"));
731         }
732
733         multimap<string, string>::const_iterator i = kv.find (k);
734         if (i == kv.end ()) {
735                 return N_("");
736         }
737
738         return i->second;
739 }
740
741 int
742 get_optional_int (multimap<string, string> const & kv, string k)
743 {
744         if (kv.count (k) > 1) {
745                 throw StringError (N_("unexpected multiple keys in key-value set"));
746         }
747
748         multimap<string, string>::const_iterator i = kv.find (k);
749         if (i == kv.end ()) {
750                 return 0;
751         }
752
753         return raw_convert<int> (i->second);
754 }
755
756 /** Trip an assert if the caller is not in the UI thread */
757 void
758 ensure_ui_thread ()
759 {
760         assert (boost::this_thread::get_id() == ui_thread);
761 }
762
763 /** @param v Content video frame.
764  *  @param audio_sample_rate Source audio sample rate.
765  *  @param frames_per_second Number of video frames per second.
766  *  @return Equivalent number of audio frames for `v'.
767  */
768 int64_t
769 video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
770 {
771         return ((int64_t) v * audio_sample_rate / frames_per_second);
772 }
773
774 string
775 audio_channel_name (int c)
776 {
777         assert (MAX_DCP_AUDIO_CHANNELS == 12);
778
779         /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
780            enhancement channel (sub-woofer).  HI is the hearing-impaired audio track and
781            VI is the visually-impaired audio track (audio describe).
782         */
783         string const channels[] = {
784                 _("Left"),
785                 _("Right"),
786                 _("Centre"),
787                 _("Lfe (sub)"),
788                 _("Left surround"),
789                 _("Right surround"),
790                 _("Hearing impaired"),
791                 _("Visually impaired"),
792                 _("Left centre"),
793                 _("Right centre"),
794                 _("Left rear surround"),
795                 _("Right rear surround"),
796         };
797
798         return channels[c];
799 }
800
801 bool
802 valid_image_file (boost::filesystem::path f)
803 {
804         string ext = f.extension().string();
805         transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
806         return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".tga" || ext == ".dpx");
807 }
808
809 string
810 tidy_for_filename (string f)
811 {
812         string t;
813         for (size_t i = 0; i < f.length(); ++i) {
814                 if (isalnum (f[i]) || f[i] == '_' || f[i] == '-') {
815                         t += f[i];
816                 } else {
817                         t += '_';
818                 }
819         }
820
821         return t;
822 }
823
824 shared_ptr<const libdcp::Signer>
825 make_signer ()
826 {
827         boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
828
829         /* Remake the chain if any of it is missing */
830         
831         list<boost::filesystem::path> files;
832         files.push_back ("ca.self-signed.pem");
833         files.push_back ("intermediate.signed.pem");
834         files.push_back ("leaf.signed.pem");
835         files.push_back ("leaf.key");
836
837         list<boost::filesystem::path>::const_iterator i = files.begin();
838         while (i != files.end()) {
839                 boost::filesystem::path p (sd);
840                 p /= *i;
841                 if (!boost::filesystem::exists (p)) {
842                         boost::filesystem::remove_all (sd);
843                         boost::filesystem::create_directories (sd);
844                         libdcp::make_signer_chain (sd, openssl_path ());
845                         break;
846                 }
847
848                 ++i;
849         }
850         
851         libdcp::CertificateChain chain;
852
853         {
854                 boost::filesystem::path p (sd);
855                 p /= "ca.self-signed.pem";
856                 chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
857         }
858
859         {
860                 boost::filesystem::path p (sd);
861                 p /= "intermediate.signed.pem";
862                 chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
863         }
864
865         {
866                 boost::filesystem::path p (sd);
867                 p /= "leaf.signed.pem";
868                 chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
869         }
870
871         boost::filesystem::path signer_key (sd);
872         signer_key /= "leaf.key";
873
874         return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
875 }
876
877 map<string, string>
878 split_get_request (string url)
879 {
880         enum {
881                 AWAITING_QUESTION_MARK,
882                 KEY,
883                 VALUE
884         } state = AWAITING_QUESTION_MARK;
885         
886         map<string, string> r;
887         string k;
888         string v;
889         for (size_t i = 0; i < url.length(); ++i) {
890                 switch (state) {
891                 case AWAITING_QUESTION_MARK:
892                         if (url[i] == '?') {
893                                 state = KEY;
894                         }
895                         break;
896                 case KEY:
897                         if (url[i] == '=') {
898                                 v.clear ();
899                                 state = VALUE;
900                         } else {
901                                 k += url[i];
902                         }
903                         break;
904                 case VALUE:
905                         if (url[i] == '&') {
906                                 r.insert (make_pair (k, v));
907                                 k.clear ();
908                                 state = KEY;
909                         } else {
910                                 v += url[i];
911                         }
912                         break;
913                 }
914         }
915
916         if (state == VALUE) {
917                 r.insert (make_pair (k, v));
918         }
919
920         return r;
921 }
922
923 libdcp::Size
924 fit_ratio_within (float ratio, libdcp::Size full_frame)
925 {
926         if (ratio < full_frame.ratio ()) {
927                 return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
928         }
929         
930         return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
931 }
932
933 void *
934 wrapped_av_malloc (size_t s)
935 {
936         void* p = av_malloc (s);
937         if (!p) {
938                 throw bad_alloc ();
939         }
940         return p;
941 }
942                 
943 string
944 entities_to_text (string e)
945 {
946         boost::algorithm::replace_all (e, "%3A", ":");
947         boost::algorithm::replace_all (e, "%2F", "/");
948         return e;
949 }
950
951 int64_t
952 divide_with_round (int64_t a, int64_t b)
953 {
954         if (a % b >= (b / 2)) {
955                 return (a + b - 1) / b;
956         } else {
957                 return a / b;
958         }
959 }
960
961 ScopedTemporary::ScopedTemporary ()
962         : _open (0)
963 {
964         _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
965 }
966
967 ScopedTemporary::~ScopedTemporary ()
968 {
969         close ();       
970         boost::system::error_code ec;
971         boost::filesystem::remove (_file, ec);
972 }
973
974 char const *
975 ScopedTemporary::c_str () const
976 {
977         return _file.string().c_str ();
978 }
979
980 FILE*
981 ScopedTemporary::open (char const * params)
982 {
983         _open = fopen (c_str(), params);
984         return _open;
985 }
986
987 void
988 ScopedTemporary::close ()
989 {
990         if (_open) {
991                 fclose (_open);
992                 _open = 0;
993         }
994 }