Listen for server replies on different ports on main and batch, and get servers to...
[dcpomatic.git] / src / lib / util.cc
index 59974c24c07bd33803923ee1cdef27e3c7ee207d..68cded2a348bee17bee9afcffe4cc749291d5105 100644 (file)
 #include "digester.h"
 #include "audio_processor.h"
 #include "compose.hpp"
-#include <locked_sstream.h>
+#include "audio_buffers.h"
+#include <dcp/locale_convert.h>
 #include <dcp/util.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 <libavformat/avformat.h>
 #include <libavcodec/avcodec.h>
 }
 #include <curl/curl.h>
@@ -52,6 +55,7 @@ extern "C" {
 #include <glib.h>
 #include <pangomm/init.h>
 #include <boost/algorithm/string.hpp>
+#include <boost/range/algorithm/replace_if.hpp>
 #include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
 #ifdef DCPOMATIC_WINDOWS
@@ -86,17 +90,21 @@ using std::pair;
 using std::cout;
 using std::bad_alloc;
 using std::set_terminate;
+using std::make_pair;
 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;
+using dcp::locale_convert;
 
 /** Path to our executable, required by the stacktrace stuff and filled
  *  in during App::onInit().
  */
 string program_name;
+bool is_batch_converter = false;
 static boost::thread::id ui_thread;
 static boost::filesystem::path backtrace_file;
 
@@ -115,14 +123,25 @@ seconds_to_hms (int s)
        int h = m / 60;
        m -= (h * 60);
 
-       locked_stringstream hms;
-       hms << h << N_(":");
-       hms.width (2);
-       hms << setfill ('0') << m << N_(":");
-       hms.width (2);
-       hms << setfill ('0') << s;
+       char buffer[64];
+       snprintf (buffer, sizeof(buffer), "%d:%02d:%02d", h, m, s);
+       return buffer;
+}
 
-       return hms.str ();
+string
+time_to_hmsf (DCPTime time, Frame rate)
+{
+       Frame f = time.frames_round (rate);
+       int s = f / rate;
+       f -= (s * rate);
+       int m = s / 60;
+       s -= m * 60;
+       int h = m / 60;
+       m -= h * 60;
+
+       char buffer[64];
+       snprintf (buffer, sizeof(buffer), "%d:%02d:%02d.%d", h, m, s, static_cast<int>(f));
+       return buffer;
 }
 
 /** @param s Number of seconds.
@@ -136,7 +155,7 @@ seconds_to_approximate_hms (int s)
        int h = m / 60;
        m -= (h * 60);
 
-       locked_stringstream ap;
+       string ap;
 
        bool const hours = h > 0;
        bool const minutes = h < 6 && m > 0;
@@ -145,14 +164,14 @@ seconds_to_approximate_hms (int s)
        if (hours) {
                if (m > 30 && !minutes) {
                        /// TRANSLATORS: h here is an abbreviation for hours
-                       ap << (h + 1) << _("h");
+                       ap += locale_convert<string>(h + 1) + _("h");
                } else {
                        /// TRANSLATORS: h here is an abbreviation for hours
-                       ap << h << _("h");
+                       ap += locale_convert<string>(h) + _("h");
                }
 
                if (minutes || seconds) {
-                       ap << N_(" ");
+                       ap += N_(" ");
                }
        }
 
@@ -160,24 +179,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 += locale_convert<string>(m + 1) + _("m");
                } else {
                        /// TRANSLATORS: m here is an abbreviation for minutes
-                       ap << m << _("m");
+                       ap += locale_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 += locale_convert<string>(s) + _("s");
        }
 
-       return ap.str ();
+       return ap;
 }
 
 double
@@ -323,6 +342,7 @@ dcpomatic_setup ()
        SetUnhandledExceptionFilter(exception_handler);
 #endif
 
+       av_register_all ();
        avfilter_register_all ();
 
 #ifdef DCPOMATIC_OSX
@@ -424,7 +444,7 @@ digest_head_tail (vector<boost::filesystem::path> files, boost::uintmax_t size)
        while (i < int64_t (files.size()) && to_do > 0) {
                FILE* f = fopen_boost (files[i], "rb");
                if (!f) {
-                       throw OpenFileError (files[i].string());
+                       throw OpenFileError (files[i].string(), errno, true);
                }
 
                boost::uintmax_t this_time = min (to_do, boost::filesystem::file_size (files[i]));
@@ -444,7 +464,7 @@ digest_head_tail (vector<boost::filesystem::path> files, boost::uintmax_t size)
        while (i >= 0 && to_do > 0) {
                FILE* f = fopen_boost (files[i], "rb");
                if (!f) {
-                       throw OpenFileError (files[i].string());
+                       throw OpenFileError (files[i].string(), errno, true);
                }
 
                boost::uintmax_t this_time = min (to_do, boost::filesystem::file_size (files[i]));
@@ -463,7 +483,7 @@ digest_head_tail (vector<boost::filesystem::path> files, boost::uintmax_t size)
 
 /** Round a number up to the nearest multiple of another number.
  *  @param c Index.
- *  @param s Array of numbers to round, indexed by c.
+ *  @param stride Array of numbers to round, indexed by c.
  *  @param t Multiple to round to.
  *  @return Rounded number.
  */
@@ -487,8 +507,7 @@ audio_channel_name (int c)
        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
-       /// VI is the visually-impaired audio track (audio describe).
+       /// enhancement channel (sub-woofer).
        string const channels[] = {
                _("Left"),
                _("Right"),
@@ -511,6 +530,38 @@ audio_channel_name (int c)
        return channels[c];
 }
 
+string
+short_audio_channel_name (int c)
+{
+       DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
+
+       /// TRANSLATORS: these are short names of audio channels; Lfe is the low-frequency
+       /// enhancement channel (sub-woofer).  HI is the hearing-impaired audio track and
+       /// VI is the visually-impaired audio track (audio describe).  DBP is the D-BOX
+       /// primary channel and DBS is the D-BOX secondary channel.
+       string const channels[] = {
+               _("L"),
+               _("R"),
+               _("C"),
+               _("Lfe"),
+               _("Ls"),
+               _("Rs"),
+               _("HI"),
+               _("VI"),
+               _("Lc"),
+               _("Rc"),
+               _("BsL"),
+               _("BsR"),
+               _("DBP"),
+               _("DBS"),
+               "",
+               ""
+       };
+
+       return channels[c];
+}
+
+
 bool
 valid_image_file (boost::filesystem::path f)
 {
@@ -523,10 +574,22 @@ valid_image_file (boost::filesystem::path f)
        return (
                ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" ||
                ext == ".png" || ext == ".bmp" || ext == ".tga" || ext == ".dpx" ||
-               ext == ".j2c" || ext == ".j2k" || ext == ".jp2"
+               ext == ".j2c" || ext == ".j2k" || ext == ".jp2" || ext == ".exr"
                );
 }
 
+bool
+valid_sound_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 == ".wav" || ext == ".mp3" || ext == ".aif" || ext == ".aiff");
+}
+
 bool
 valid_j2k_file (boost::filesystem::path f)
 {
@@ -538,16 +601,8 @@ valid_j2k_file (boost::filesystem::path f)
 string
 tidy_for_filename (string f)
 {
-       string t;
-       for (size_t i = 0; i < f.length(); ++i) {
-               if (isalnum (f[i]) || f[i] == '_' || f[i] == '-') {
-                       t += f[i];
-               } else {
-                       t += '_';
-               }
-       }
-
-       return t;
+       boost::replace_if (f, boost::is_any_of ("\\/:"), '_');
+       return f;
 }
 
 dcp::Size
@@ -617,21 +672,29 @@ split_get_request (string url)
 }
 
 string
-video_asset_filename (shared_ptr<dcp::PictureAsset> asset)
+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();
-       return Config::instance()->dcp_filename_format().get(values) + ".mxf";
+       values['r'] = raw_convert<string> (reel_index + 1);
+       values['n'] = raw_convert<string> (reel_count);
+       if (summary) {
+               values['c'] = careful_string_filter (summary.get());
+       }
+       return Config::instance()->dcp_asset_filename_format().get(values, "_" + asset->id() + ".mxf");
 }
 
 string
-audio_asset_filename (shared_ptr<dcp::SoundAsset> asset)
+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();
-       return Config::instance()->dcp_filename_format().get(values) + ".mxf";
+       values['r'] = raw_convert<string> (reel_index + 1);
+       values['n'] = raw_convert<string> (reel_count);
+       if (summary) {
+               values['c'] = careful_string_filter (summary.get());
+       }
+       return Config::instance()->dcp_asset_filename_format().get(values, "_" + asset->id() + ".mxf");
 }
 
 float
@@ -646,8 +709,79 @@ relaxed_string_to_float (string s)
        }
 }
 
-bool
-string_not_empty (string s)
+string
+careful_string_filter (string s)
+{
+       /* Filter out `bad' characters which `may' cause problems with some systems (either for DCP name or filename).
+          There's no apparent list of what really is allowed, so this is a guess.
+          Safety first and all that.
+       */
+
+       string out;
+       string const allowed = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_%.";
+       for (size_t i = 0; i < s.size(); ++i) {
+               if (allowed.find (s[i]) != string::npos) {
+                       out += s[i];
+               }
+       }
+
+       return out;
+}
+
+/** @param mapped List of mapped audio channels from a Film.
+ *  @param channels Total number of channels in the Film.
+ *  @return First: number of non-LFE channels, second: number of LFE channels.
+ */
+pair<int, int>
+audio_channel_types (list<int> mapped, int channels)
+{
+       int non_lfe = 0;
+       int lfe = 0;
+
+       BOOST_FOREACH (int i, mapped) {
+               if (i >= channels) {
+                       /* This channel is mapped but is not included in the DCP */
+                       continue;
+               }
+
+               if (static_cast<dcp::Channel> (i) == dcp::LFE) {
+                       ++lfe;
+               } else {
+                       ++non_lfe;
+               }
+       }
+
+       return make_pair (non_lfe, lfe);
+}
+
+shared_ptr<AudioBuffers>
+remap (shared_ptr<const AudioBuffers> input, int output_channels, AudioMapping map)
+{
+       shared_ptr<AudioBuffers> mapped (new AudioBuffers (output_channels, input->frames()));
+       mapped->make_silent ();
+
+       for (int i = 0; i < map.input_channels(); ++i) {
+               for (int j = 0; j < mapped->channels(); ++j) {
+                       if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
+                               mapped->accumulate_channel (
+                                       input.get(),
+                                       i,
+                                       static_cast<dcp::Channel> (j),
+                                       map.get (i, static_cast<dcp::Channel> (j))
+                                       );
+                       }
+               }
+       }
+
+       return mapped;
+}
+
+Eyes
+increment_eyes (Eyes e)
 {
-       return !s.empty ();
+       if (e == EYES_LEFT) {
+               return EYES_RIGHT;
+       }
+
+       return EYES_LEFT;
 }