Fix subtitle font handling with in-memory fonts from SMPTE (#2509).
[dcpomatic.git] / src / lib / util.cc
index 2c38257b4bafabf1447cc1824956e62da36e953d..8c570059821d35ca3290dd250524d3f682b2c6f9 100644 (file)
 #include "cinema_sound_processor.h"
 #include "compose.hpp"
 #include "config.h"
+#include "constants.h"
 #include "cross.h"
 #include "crypto.h"
 #include "dcp_content_type.h"
+#include "dcpomatic_log.h"
 #include "digester.h"
 #include "exceptions.h"
 #include "ffmpeg_image_proxy.h"
@@ -45,6 +47,7 @@
 #include "ratio.h"
 #include "rect.h"
 #include "render_text.h"
+#include "scope_guard.h"
 #include "string_text.h"
 #include "text_decoder.h"
 #include "util.h"
@@ -71,6 +74,7 @@ LIBDCP_ENABLE_WARNINGS
 #include <unicode/utypes.h>
 #include <unicode/unistr.h>
 #include <unicode/translit.h>
+#include <unicode/brkiter.h>
 #include <boost/algorithm/string.hpp>
 #include <boost/range/algorithm/replace_if.hpp>
 #include <boost/thread.hpp>
@@ -79,7 +83,6 @@ LIBDCP_DISABLE_WARNINGS
 #include <boost/locale.hpp>
 LIBDCP_ENABLE_WARNINGS
 #ifdef DCPOMATIC_WINDOWS
-#include <boost/locale.hpp>
 #include <dbghelp.h>
 #endif
 #include <signal.h>
@@ -359,6 +362,59 @@ dcpomatic_setup_path_encoding ()
 #endif
 }
 
+
+class LogSink : public Kumu::ILogSink
+{
+public:
+       LogSink () {}
+       LogSink (LogSink const&) = delete;
+       LogSink& operator= (LogSink const&) = delete;
+
+       void WriteEntry(const Kumu::LogEntry& entry) override {
+               Kumu::AutoMutex L(m_lock);
+               WriteEntryToListeners(entry);
+               if (entry.TestFilter(m_filter)) {
+                       string buffer;
+                       entry.CreateStringWithOptions(buffer, m_options);
+                       LOG_GENERAL("asdcplib: %1", buffer);
+               }
+       }
+};
+
+
+void
+capture_asdcp_logs ()
+{
+       static LogSink log_sink;
+       Kumu::SetDefaultLogSink(&log_sink);
+}
+
+
+static
+void
+ffmpeg_log_callback(void* ptr, int level, const char* fmt, va_list vl)
+{
+       if (level > AV_LOG_WARNING) {
+               return;
+       }
+
+       char line[1024];
+       static int prefix = 0;
+       av_log_format_line(ptr, level, fmt, vl, line, sizeof (line), &prefix);
+       string str(line);
+       boost::algorithm::trim(str);
+       dcpomatic_log->log(String::compose("FFmpeg: %1", str), LogEntry::TYPE_GENERAL);
+}
+
+
+static
+void
+capture_ffmpeg_logs()
+{
+       av_log_set_callback(ffmpeg_log_callback);
+}
+
+
 /** 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.
  */
@@ -407,11 +463,11 @@ LIBDCP_ENABLE_WARNINGS
        /* Render something to fontconfig to create its cache */
        list<StringText> subs;
        dcp::SubtitleString ss(
-               optional<string>(), false, false, false, dcp::Colour(), 42, 1, dcp::Time(), dcp::Time(), 0, dcp::HAlign::CENTER, 0, dcp::VAlign::CENTER, dcp::Direction::LTR,
+               optional<string>(), false, false, false, dcp::Colour(), 42, 1, dcp::Time(), dcp::Time(), 0, dcp::HAlign::CENTER, 0, dcp::VAlign::CENTER, 0, dcp::Direction::LTR,
                "Hello dolly", dcp::Effect::NONE, dcp::Colour(), dcp::Time(), dcp::Time(), 0
                );
-       subs.push_back (StringText(ss, 0));
-       render_text (subs, list<shared_ptr<Font>>(), dcp::Size(640, 480), DCPTime(), 24);
+       subs.push_back(StringText(ss, 0, make_shared<dcpomatic::Font>("foo"), dcp::SubtitleStandard::SMPTE_2014));
+       render_text (subs, dcp::Size(640, 480), DCPTime(), 24);
 #endif
 
        Ratio::setup_ratios ();
@@ -424,6 +480,9 @@ LIBDCP_ENABLE_WARNINGS
        curl_global_init (CURL_GLOBAL_ALL);
 
        ui_thread = boost::this_thread::get_id ();
+
+       capture_asdcp_logs ();
+       capture_ffmpeg_logs();
 }
 
 #ifdef DCPOMATIC_WINDOWS
@@ -591,14 +650,14 @@ short_audio_channel_name (int c)
                _("Rs"),
                _("HI"),
                _("VI"),
-               _("Lc"),
-               _("Rc"),
+               _("9"),
+               _("10"),
                _("BsL"),
                _("BsR"),
                _("DBP"),
                _("DBS"),
                _("Sign"),
-               ""
+               _("16")
        };
 
        return channels[c];
@@ -659,56 +718,9 @@ fit_ratio_within (float ratio, dcp::Size full_frame)
        return dcp::Size (full_frame.width, lrintf (full_frame.width / ratio));
 }
 
-map<string, string>
-split_get_request (string url)
-{
-       enum {
-               AWAITING_QUESTION_MARK,
-               KEY,
-               VALUE
-       } state = AWAITING_QUESTION_MARK;
-
-       map<string, string> r;
-       string k;
-       string v;
-       for (size_t i = 0; i < url.length(); ++i) {
-               switch (state) {
-               case AWAITING_QUESTION_MARK:
-                       if (url[i] == '?') {
-                               state = KEY;
-                       }
-                       break;
-               case KEY:
-                       if (url[i] == '=') {
-                               v.clear ();
-                               state = VALUE;
-                       } else {
-                               k += url[i];
-                       }
-                       break;
-               case VALUE:
-                       if (url[i] == '&') {
-                               r.insert (make_pair (k, v));
-                               k.clear ();
-                               state = KEY;
-                       } else {
-                               v += url[i];
-                       }
-                       break;
-               }
-       }
-
-       if (state == VALUE) {
-               r.insert (make_pair (k, v));
-       }
-
-       return r;
-}
-
-
 static
 string
-asset_filename (shared_ptr<dcp::Asset> asset, string type, int reel_index, int reel_count, optional<string> summary)
+asset_filename (shared_ptr<dcp::Asset> asset, string type, int reel_index, int reel_count, optional<string> summary, string extension)
 {
        dcp::NameFormat::Map values;
        values['t'] = type;
@@ -717,50 +729,38 @@ asset_filename (shared_ptr<dcp::Asset> asset, string type, int reel_index, int r
        if (summary) {
                values['c'] = careful_string_filter(summary.get());
        }
-       return Config::instance()->dcp_asset_filename_format().get(values, "_" + asset->id() + ".mxf");
+       return Config::instance()->dcp_asset_filename_format().get(values, "_" + asset->id() + extension);
 }
 
 
 string
 video_asset_filename (shared_ptr<dcp::PictureAsset> asset, int reel_index, int reel_count, optional<string> summary)
 {
-       return asset_filename(asset, "j2c", reel_index, reel_count, summary);
+       return asset_filename(asset, "j2c", reel_index, reel_count, summary, ".mxf");
 }
 
 
 string
 audio_asset_filename (shared_ptr<dcp::SoundAsset> asset, int reel_index, int reel_count, optional<string> summary)
 {
-       return asset_filename(asset, "pcm", reel_index, reel_count, summary);
+       return asset_filename(asset, "pcm", reel_index, reel_count, summary, ".mxf");
 }
 
 
 string
-subtitle_asset_filename (shared_ptr<dcp::SubtitleAsset> asset, int reel_index, int reel_count, optional<string> summary)
+subtitle_asset_filename (shared_ptr<dcp::SubtitleAsset> asset, int reel_index, int reel_count, optional<string> summary, string extension)
 {
-       return asset_filename(asset, "sub", reel_index, reel_count, summary);
+       return asset_filename(asset, "sub", reel_index, reel_count, summary, extension);
 }
 
 
 string
 atmos_asset_filename (shared_ptr<dcp::AtmosAsset> asset, int reel_index, int reel_count, optional<string> summary)
 {
-       return asset_filename(asset, "atmos", reel_index, reel_count, summary);
+       return asset_filename(asset, "atmos", reel_index, reel_count, summary, ".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);
-       }
-}
-
 string
 careful_string_filter (string s)
 {
@@ -900,28 +900,6 @@ utf8_strlen (string s)
        return N;
 }
 
-string
-day_of_week_to_string (boost::gregorian::greg_weekday d)
-{
-       switch (d.as_enum()) {
-       case boost::date_time::Sunday:
-               return _("Sunday");
-       case boost::date_time::Monday:
-               return _("Monday");
-       case boost::date_time::Tuesday:
-               return _("Tuesday");
-       case boost::date_time::Wednesday:
-               return _("Wednesday");
-       case boost::date_time::Thursday:
-               return _("Thursday");
-       case boost::date_time::Friday:
-               return _("Friday");
-       case boost::date_time::Saturday:
-               return _("Saturday");
-       }
-
-       return d.as_long_string ();
-}
 
 /** @param size Size of picture that the subtitle will be overlaid onto */
 void
@@ -962,60 +940,6 @@ emit_subtitle_image (ContentTimePeriod period, dcp::SubtitleImage sub, dcp::Size
        decoder->emit_bitmap (period, image, rect);
 }
 
-bool
-show_jobs_on_console (bool progress)
-{
-       bool first = true;
-       bool error = false;
-       while (true) {
-
-               dcpomatic_sleep_seconds (5);
-
-               auto jobs = JobManager::instance()->get();
-
-               if (!first && progress) {
-                       for (size_t i = 0; i < jobs.size(); ++i) {
-                               cout << "\033[1A\033[2K";
-                       }
-                       cout.flush ();
-               }
-
-               first = false;
-
-               for (auto i: jobs) {
-                       if (progress) {
-                               cout << i->name();
-                               if (!i->sub_name().empty()) {
-                                       cout << "; " << i->sub_name();
-                               }
-                               cout << ": ";
-
-                               if (i->progress ()) {
-                                       cout << i->status() << "                            \n";
-                               } else {
-                                       cout << ": Running           \n";
-                               }
-                       }
-
-                       if (!progress && i->finished_in_error()) {
-                               /* We won't see this error if we haven't been showing progress,
-                                  so show it now.
-                               */
-                               cout << i->status() << "\n";
-                       }
-
-                       if (i->finished_in_error()) {
-                               error = true;
-                       }
-               }
-
-               if (!JobManager::instance()->work_to_do()) {
-                       break;
-               }
-       }
-
-       return error;
-}
 
 /** XXX: could use mmap? */
 void
@@ -1118,19 +1042,14 @@ default_font_file ()
        if (!boost::filesystem::exists(liberation_normal)) {
                liberation_normal = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf";
        }
+       if (!boost::filesystem::exists(liberation_normal)) {
+               liberation_normal = "/usr/share/fonts/liberation-sans/LiberationSans-Regular.ttf";
+       }
 
        return liberation_normal;
 }
 
 
-string
-to_upper (string s)
-{
-       transform (s.begin(), s.end(), s.begin(), ::toupper);
-       return s;
-}
-
-
 /* Set to 1 to print the IDs of some of our threads to stdout on creation */
 #define DCPOMATIC_DEBUG_THREADS 0
 
@@ -1148,3 +1067,52 @@ start_of_thread (string)
 }
 #endif
 
+
+string
+error_details(boost::system::error_code ec)
+{
+       return String::compose("%1:%2:%3", ec.category().name(), ec.value(), ec.message());
+}
+
+
+bool
+contains_assetmap(boost::filesystem::path dir)
+{
+       return boost::filesystem::is_regular_file(dir / "ASSETMAP") || boost::filesystem::is_regular_file(dir / "ASSETMAP.xml");
+}
+
+
+string
+word_wrap(string input, int columns)
+{
+       icu::Locale locale;
+       UErrorCode status = U_ZERO_ERROR;
+       auto iter = icu::BreakIterator::createLineInstance(locale, status);
+       ScopeGuard sg = [iter]() { delete iter; };
+       if (U_FAILURE(status)) {
+               return input;
+       }
+
+       auto input_icu = icu::UnicodeString::fromUTF8(icu::StringPiece(input));
+       iter->setText(input_icu);
+
+       int position = 0;
+       string output;
+       while (position < input_icu.length()) {
+               int end_of_line = iter->preceding(position + columns + 1);
+               icu::UnicodeString line;
+               if (end_of_line <= position) {
+                       /* There's no good line-break position; just break in the middle of a word */
+                       line = input_icu.tempSubString(position, columns);
+                       position += columns;
+               } else {
+                       line = input_icu.tempSubString(position, end_of_line - position);
+                       position = end_of_line;
+               }
+               line.toUTF8String(output);
+               output += "\n";
+       }
+
+       return output;
+}
+