Remove all use of stringstream in an attempt to fix
[dcpomatic.git] / src / lib / util.cc
index 6ebdc85d41777971492697a254393883e159ca4e..5e870f1e2715f349ee3309f5f530e2133da32672 100644 (file)
@@ -1,19 +1,20 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
 
-    This program is free software; you can redistribute it and/or modify
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
+    DCP-o-matic is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
 #include "cross.h"
 #include "video_content.h"
 #include "rect.h"
-#include "md5_digester.h"
+#include "digester.h"
 #include "audio_processor.h"
-#include "safe_stringstream.h"
+#include "compose.hpp"
 #include <dcp/util.h>
-#include <dcp/signer.h>
-#include <dcp/raw_convert.h>
+#include <dcp/picture_asset.h>
+#include <dcp/sound_asset.h>
+#include <dcp/subtitle_asset.h>
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libavcodec/avcodec.h>
+}
+#include <curl/curl.h>
+#ifdef DCPOMATIC_GRAPHICS_MAGICK
+#include <Magick++.h>
+#endif
 #include <glib.h>
 #include <pangomm/init.h>
 #include <boost/algorithm/string.hpp>
-#include <boost/bind.hpp>
-#include <boost/lambda/lambda.hpp>
 #include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
 #ifdef DCPOMATIC_WINDOWS
@@ -80,8 +88,9 @@ using std::set_terminate;
 using boost::shared_ptr;
 using boost::thread;
 using boost::optional;
+using boost::lexical_cast;
+using boost::bad_lexical_cast;
 using dcp::Size;
-using dcp::raw_convert;
 
 /** Path to our executable, required by the stacktrace stuff and filled
  *  in during App::onInit().
@@ -105,14 +114,9 @@ seconds_to_hms (int s)
        int h = m / 60;
        m -= (h * 60);
 
-       SafeStringStream hms;
-       hms << h << N_(":");
-       hms.width (2);
-       hms << setfill ('0') << m << N_(":");
-       hms.width (2);
-       hms << setfill ('0') << s;
-
-       return hms.str ();
+       char buffer[64];
+       snprintf (buffer, sizeof(buffer), "%d:%02d:%02d", h, m, s);
+       return buffer;
 }
 
 /** @param s Number of seconds.
@@ -126,23 +130,23 @@ seconds_to_approximate_hms (int s)
        int h = m / 60;
        m -= (h * 60);
 
-       SafeStringStream ap;
+       string ap;
 
        bool const hours = h > 0;
-       bool const minutes = h < 10 && m > 0;
-       bool const seconds = m < 10 && s > 0;
+       bool const minutes = h < 6 && m > 0;
+       bool const seconds = h == 0 && m < 10 && s > 0;
 
        if (hours) {
                if (m > 30 && !minutes) {
                        /// TRANSLATORS: h here is an abbreviation for hours
-                       ap << (h + 1) << _("h");
+                       ap += raw_convert<string>(h + 1) + _("h");
                } else {
                        /// TRANSLATORS: h here is an abbreviation for hours
-                       ap << h << _("h");
+                       ap += raw_convert<string>(h) + _("h");
                }
 
-               if (minutes | seconds) {
-                       ap << N_(" ");
+               if (minutes || seconds) {
+                       ap += N_(" ");
                }
        }
 
@@ -150,24 +154,24 @@ seconds_to_approximate_hms (int s)
                /* Minutes */
                if (s > 30 && !seconds) {
                        /// TRANSLATORS: m here is an abbreviation for minutes
-                       ap << (m + 1) << _("m");
+                       ap += raw_convert<string>(m + 1) + _("m");
                } else {
                        /// TRANSLATORS: m here is an abbreviation for minutes
-                       ap << m << _("m");
+                       ap += raw_convert<string>(m) + _("m");
                }
 
                if (seconds) {
-                       ap << N_(" ");
+                       ap += N_(" ");
                }
        }
 
        if (seconds) {
                /* Seconds */
                /// TRANSLATORS: s here is an abbreviation for seconds
-               ap << s << _("s");
+               ap += raw_convert<string>(s) + _("s");
        }
 
-       return ap.str ();
+       return ap;
 }
 
 double
@@ -183,7 +187,7 @@ int
 addr2line (void const * const addr)
 {
        char addr2line_cmd[512] = { 0 };
-       sprintf (addr2line_cmd, "addr2line -f -p -e %.256s %p > %s", program_name.c_str(), addr, backtrace_file.string().c_str()); 
+       sprintf (addr2line_cmd, "addr2line -f -p -e %.256s %p > %s", program_name.c_str(), addr, backtrace_file.string().c_str());
        return system(addr2line_cmd);
 }
 
@@ -197,19 +201,19 @@ exception_handler(struct _EXCEPTION_POINTERS * info)
        FILE* f = fopen_boost (backtrace_file, "w");
        fprintf (f, "C-style exception %d\n", info->ExceptionRecord->ExceptionCode);
        fclose(f);
-       
+
        if (info->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW) {
                CONTEXT* context = info->ContextRecord;
                SymInitialize (GetCurrentProcess (), 0, true);
-               
+
                STACKFRAME frame = { 0 };
-               
+
                /* setup initial stack frame */
 #if _WIN64
                frame.AddrPC.Offset    = context->Rip;
                frame.AddrStack.Offset = context->Rsp;
                frame.AddrFrame.Offset = context->Rbp;
-#else  
+#else
                frame.AddrPC.Offset    = context->Eip;
                frame.AddrStack.Offset = context->Esp;
                frame.AddrFrame.Offset = context->Ebp;
@@ -217,7 +221,7 @@ exception_handler(struct _EXCEPTION_POINTERS * info)
                frame.AddrPC.Mode      = AddrModeFlat;
                frame.AddrStack.Mode   = AddrModeFlat;
                frame.AddrFrame.Mode   = AddrModeFlat;
-               
+
                while (
                        StackWalk (
                                IMAGE_FILE_MACHINE_I386,
@@ -234,13 +238,13 @@ exception_handler(struct _EXCEPTION_POINTERS * info)
                        addr2line((void *) frame.AddrPC.Offset);
                }
        } else {
-#ifdef _WIN64          
+#ifdef _WIN64
                addr2line ((void *) info->ContextRecord->Rip);
-#else          
+#else
                addr2line ((void *) info->ContextRecord->Eip);
-#endif         
+#endif
        }
-       
+
        return EXCEPTION_CONTINUE_SEARCH;
 }
 #endif
@@ -272,25 +276,17 @@ terminate ()
                          << e.what() << std::endl;
        }
        catch (...) {
-               std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." 
+               std::cerr << __FUNCTION__ << " caught unknown/unhandled exception."
                          << std::endl;
        }
 
        abort();
 }
 
-/** Call the required functions to set up DCP-o-matic's static arrays, etc.
- *  Must be called from the UI thread, if there is one.
- */
 void
-dcpomatic_setup ()
+dcpomatic_setup_path_encoding ()
 {
 #ifdef DCPOMATIC_WINDOWS
-       boost::filesystem::path p = g_get_user_config_dir ();
-       p /= "backtrace.txt";
-       set_backtrace_file (p);
-       SetUnhandledExceptionFilter(exception_handler);
-
        /* Dark voodoo which, I think, gets boost::filesystem::path to
           correctly convert UTF-8 strings to paths, and also paths
           back to UTF-8 strings (on path::string()).
@@ -305,8 +301,22 @@ dcpomatic_setup ()
        */
        std::locale::global (boost::locale::generator().generate (""));
        boost::filesystem::path::imbue (std::locale ());
-#endif 
-       
+#endif
+}
+
+/** Call the required functions to set up DCP-o-matic's static arrays, etc.
+ *  Must be called from the UI thread, if there is one.
+ */
+void
+dcpomatic_setup ()
+{
+#ifdef DCPOMATIC_WINDOWS
+       boost::filesystem::path p = g_get_user_config_dir ();
+       p /= "backtrace.txt";
+       set_backtrace_file (p);
+       SetUnhandledExceptionFilter(exception_handler);
+#endif
+
        avfilter_register_all ();
 
 #ifdef DCPOMATIC_OSX
@@ -322,14 +332,21 @@ dcpomatic_setup ()
 
        Pango::init ();
        dcp::init ();
-       
+
        Ratio::setup_ratios ();
+       PresetColourConversion::setup_colour_conversion_presets ();
        VideoContentScale::setup_scales ();
        DCPContentType::setup_dcp_content_types ();
        Filter::setup_filters ();
        CinemaSoundProcessor::setup_cinema_sound_processors ();
        AudioProcessor::setup_audio_processors ();
 
+       curl_global_init (CURL_GLOBAL_ALL);
+
+#ifdef DCPOMATIC_GRAPHICS_MAGICK
+       Magick::InitializeMagick (0);
+#endif
+
        ui_thread = boost::this_thread::get_id ();
 }
 
@@ -375,24 +392,24 @@ dcpomatic_setup_gettext_i18n (string lang)
        }
 
        setlocale (LC_ALL, "");
-       textdomain ("libdcpomatic");
+       textdomain ("libdcpomatic2");
 
 #if defined(DCPOMATIC_WINDOWS) || defined(DCPOMATIC_OSX)
-       bindtextdomain ("libdcpomatic", mo_path().string().c_str());
-       bind_textdomain_codeset ("libdcpomatic", "UTF8");
-#endif 
+       bindtextdomain ("libdcpomatic2", mo_path().string().c_str());
+       bind_textdomain_codeset ("libdcpomatic2", "UTF8");
+#endif
 
 #ifdef DCPOMATIC_LINUX
-       bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
+       bindtextdomain ("libdcpomatic2", LINUX_LOCALE_PREFIX);
 #endif
 }
 
 /** Compute a digest of the first and last `size' bytes of a set of files. */
 string
-md5_digest_head_tail (vector<boost::filesystem::path> files, boost::uintmax_t size)
+digest_head_tail (vector<boost::filesystem::path> files, boost::uintmax_t size)
 {
        boost::scoped_array<char> buffer (new char[size]);
-       MD5Digester digester;
+       Digester digester;
 
        /* Head */
        boost::uintmax_t to_do = size;
@@ -425,32 +442,19 @@ md5_digest_head_tail (vector<boost::filesystem::path> files, boost::uintmax_t si
                }
 
                boost::uintmax_t this_time = min (to_do, boost::filesystem::file_size (files[i]));
-               fseek (f, -this_time, SEEK_END);
+               dcpomatic_fseek (f, -this_time, SEEK_END);
                fread (p, 1, this_time, f);
                p += this_time;
                to_do -= this_time;
                fclose (f);
 
                --i;
-       }               
+       }
        digester.add (buffer.get(), size - to_do);
 
        return digester.get ();
 }
 
-/** @param An arbitrary audio frame rate.
- *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
- */
-int
-dcp_audio_frame_rate (int fs)
-{
-       if (fs <= 48000) {
-               return 48000;
-       }
-
-       return 96000;
-}
-
 /** Round a number up to the nearest multiple of another number.
  *  @param c Index.
  *  @param s Array of numbers to round, indexed by c.
@@ -464,17 +468,6 @@ stride_round_up (int c, int const * stride, int t)
        return a - (a % t);
 }
 
-/** @param n A number.
- *  @param r Rounding `boundary' (must be a power of 2)
- *  @return n rounded to the nearest r
- */
-int
-round_to (float n, int r)
-{
-       DCPOMATIC_ASSERT (r == 1 || r == 2 || r == 4);
-       return int (n + float(r) / 2) &~ (r - 1);
-}
-
 /** Trip an assert if the caller is not in the UI thread */
 void
 ensure_ui_thread ()
@@ -485,7 +478,7 @@ ensure_ui_thread ()
 string
 audio_channel_name (int c)
 {
-       DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 12);
+       DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
 
        /// TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
        /// enhancement channel (sub-woofer).  HI is the hearing-impaired audio track and
@@ -503,6 +496,10 @@ audio_channel_name (int c)
                _("Right centre"),
                _("Left rear surround"),
                _("Right rear surround"),
+               _("D-BOX primary"),
+               _("D-BOX secondary"),
+               _("Unused"),
+               _("Unused")
        };
 
        return channels[c];
@@ -511,12 +508,16 @@ audio_channel_name (int c)
 bool
 valid_image_file (boost::filesystem::path f)
 {
+       if (boost::starts_with (f.leaf().string(), "._")) {
+               return false;
+       }
+
        string ext = f.extension().string();
        transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
        return (
                ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" ||
                ext == ".png" || ext == ".bmp" || ext == ".tga" || ext == ".dpx" ||
-               ext == ".j2c" || ext == ".j2k"
+               ext == ".j2c" || ext == ".j2k" || ext == ".jp2"
                );
 }
 
@@ -525,7 +526,7 @@ valid_j2k_file (boost::filesystem::path f)
 {
        string ext = f.extension().string();
        transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-       return (ext == ".j2k" || ext == ".j2c");
+       return (ext == ".j2k" || ext == ".j2c" || ext == ".jp2");
 }
 
 string
@@ -544,13 +545,13 @@ tidy_for_filename (string f)
 }
 
 dcp::Size
-fit_ratio_within (float ratio, dcp::Size full_frame, int round)
+fit_ratio_within (float ratio, dcp::Size full_frame)
 {
        if (ratio < full_frame.ratio ()) {
-               return dcp::Size (round_to (full_frame.height * ratio, round), full_frame.height);
+               return dcp::Size (lrintf (full_frame.height * ratio), full_frame.height);
        }
-       
-       return dcp::Size (full_frame.width, round_to (full_frame.width / ratio, round));
+
+       return dcp::Size (full_frame.width, lrintf (full_frame.width / ratio));
 }
 
 void *
@@ -562,19 +563,6 @@ wrapped_av_malloc (size_t s)
        }
        return p;
 }
-               
-ContentTimePeriod
-subtitle_period (AVSubtitle const & sub)
-{
-       ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE);
-
-       ContentTimePeriod period (
-               packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3),
-               packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3)
-               );
-
-       return period;
-}
 
 map<string, string>
 split_get_request (string url)
@@ -584,7 +572,7 @@ split_get_request (string url)
                KEY,
                VALUE
        } state = AWAITING_QUESTION_MARK;
-       
+
        map<string, string> r;
        string k;
        string v;
@@ -621,3 +609,49 @@ split_get_request (string url)
 
        return r;
 }
+
+string
+video_asset_filename (shared_ptr<dcp::PictureAsset> asset, int reel_index, int reel_count, optional<string> summary)
+{
+       dcp::NameFormat::Map values;
+       values['t'] = "j2c";
+       values['i'] = asset->id();
+       values['r'] = raw_convert<string> (reel_index + 1);
+       values['n'] = raw_convert<string> (reel_count);
+       if (summary) {
+               values['c'] = summary.get();
+       }
+       return Config::instance()->dcp_asset_filename_format().get(values) + ".mxf";
+}
+
+string
+audio_asset_filename (shared_ptr<dcp::SoundAsset> asset, int reel_index, int reel_count, optional<string> summary)
+{
+       dcp::NameFormat::Map values;
+       values['t'] = "pcm";
+       values['i'] = asset->id();
+       values['r'] = raw_convert<string> (reel_index + 1);
+       values['n'] = raw_convert<string> (reel_count);
+       if (summary) {
+               values['c'] = summary.get();
+       }
+       return Config::instance()->dcp_asset_filename_format().get(values) + ".mxf";
+}
+
+float
+relaxed_string_to_float (string s)
+{
+       try {
+               boost::algorithm::replace_all (s, ",", ".");
+               return lexical_cast<float> (s);
+       } catch (bad_lexical_cast) {
+               boost::algorithm::replace_all (s, ".", ",");
+               return lexical_cast<float> (s);
+       }
+}
+
+bool
+string_not_empty (string s)
+{
+       return !s.empty ();
+}