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