Rather hacky support for burnt subtitle Z-position in 3D (#1359).
[dcpomatic.git] / src / lib / text_content.cc
index c86150881418121b32c59e8d7187d72f6ca3aa18..0f5bc60bec6827656e0610eaa55d51451c4a30bb 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2022 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 */
 
 
-#include "text_content.h"
-#include "util.h"
+#include "content.h"
 #include "exceptions.h"
 #include "font.h"
-#include "content.h"
+#include "text_content.h"
+#include "util.h"
 #include <dcp/raw_convert.h>
 #include <libcxml/cxml.h>
 #include <libxml++/libxml++.h>
@@ -55,13 +55,14 @@ int const TextContentProperty::COLOUR = 507;
 int const TextContentProperty::EFFECT = 508;
 int const TextContentProperty::EFFECT_COLOUR = 509;
 int const TextContentProperty::LINE_SPACING = 510;
-int const TextContentProperty::FADE_IN = 511;
-int const TextContentProperty::FADE_OUT = 512;
-int const TextContentProperty::OUTLINE_WIDTH = 513;
-int const TextContentProperty::TYPE = 514;
-int const TextContentProperty::DCP_TRACK = 515;
-int const TextContentProperty::LANGUAGE = 516;
-int const TextContentProperty::LANGUAGE_IS_ADDITIONAL = 517;
+int const TextContentProperty::Z_POSITION = 511;
+int const TextContentProperty::FADE_IN = 512;
+int const TextContentProperty::FADE_OUT = 513;
+int const TextContentProperty::OUTLINE_WIDTH = 514;
+int const TextContentProperty::TYPE = 515;
+int const TextContentProperty::DCP_TRACK = 516;
+int const TextContentProperty::LANGUAGE = 517;
+int const TextContentProperty::LANGUAGE_IS_ADDITIONAL = 518;
 
 
 TextContent::TextContent (Content* parent, TextType type, TextType original_type)
@@ -84,7 +85,7 @@ TextContent::TextContent (Content* parent, TextType type, TextType original_type
  *  The list could be empty if no TextContents are found.
  */
 list<shared_ptr<TextContent>>
-TextContent::from_xml (Content* parent, cxml::ConstNodePtr node, int version)
+TextContent::from_xml (Content* parent, cxml::ConstNodePtr node, int version, list<string>& notes)
 {
        if (version < 34) {
                /* With old metadata FFmpeg content has the subtitle-related tags even with no
@@ -101,22 +102,18 @@ TextContent::from_xml (Content* parent, cxml::ConstNodePtr node, int version)
                if (!node->optional_number_child<double>("SubtitleXOffset") && !node->optional_number_child<double>("SubtitleOffset")) {
                        return {};
                }
-               return { make_shared<TextContent>(parent, node, version) };
-       }
-
-       if (!node->optional_node_child("Text")) {
-               return {};
+               return { make_shared<TextContent>(parent, node, version, notes) };
        }
 
        list<shared_ptr<TextContent>> c;
        for (auto i: node->node_children("Text")) {
-               c.push_back (make_shared<TextContent>(parent, i, version));
+               c.push_back (make_shared<TextContent>(parent, i, version, notes));
        }
 
        return c;
 }
 
-TextContent::TextContent (Content* parent, cxml::ConstNodePtr node, int version)
+TextContent::TextContent (Content* parent, cxml::ConstNodePtr node, int version, list<string>& notes)
        : ContentPart (parent)
        , _use (false)
        , _burn (false)
@@ -125,10 +122,10 @@ TextContent::TextContent (Content* parent, cxml::ConstNodePtr node, int version)
        , _x_scale (1)
        , _y_scale (1)
        , _line_spacing (node->optional_number_child<double>("LineSpacing").get_value_or(1))
+       , _z_position (node->optional_number_child<int>("ZPosition").get_value_or(0))
        , _outline_width (node->optional_number_child<int>("OutlineWidth").get_value_or(4))
        , _type (TextType::OPEN_SUBTITLE)
        , _original_type (TextType::OPEN_SUBTITLE)
-       , _language ("en-US")
 {
        if (version >= 37) {
                _use = node->bool_child ("Use");
@@ -239,9 +236,24 @@ TextContent::TextContent (Content* parent, cxml::ConstNodePtr node, int version)
 
        auto lang = node->optional_node_child("Language");
        if (lang) {
-               _language = dcp::LanguageTag(lang->content());
-               auto add = lang->optional_bool_attribute("Additional");
-               _language_is_additional = add && *add;
+               try {
+                       _language = dcp::LanguageTag(lang->content());
+                       auto add = lang->optional_bool_attribute("Additional");
+                       _language_is_additional = add && *add;
+               } catch (dcp::LanguageTagError&) {
+                       /* The language tag can be empty or invalid if it was loaded from a
+                        * 2.14.x metadata file; we'll just ignore it in that case.
+                        */
+                       if (version <= 37) {
+                               if (!lang->content().empty()) {
+                                       notes.push_back (String::compose(
+                                               _("A subtitle or closed caption file in this project is marked with the language '%1', "
+                                                 "which DCP-o-matic does not recognise.  The file's language has been cleared."), lang->content()));
+                               }
+                       } else {
+                               throw;
+                       }
+               }
        }
 }
 
@@ -285,6 +297,10 @@ TextContent::TextContent (Content* parent, vector<shared_ptr<Content>> c)
                        throw JoinError (_("Content to be joined must have the same subtitle line spacing."));
                }
 
+               if (c[i]->only_text()->z_position() != ref->z_position()) {
+                       throw JoinError (_("Content to be joined must have the same subtitle Z position."));
+               }
+
                if ((c[i]->only_text()->fade_in() != ref->fade_in()) || (c[i]->only_text()->fade_out() != ref->fade_out())) {
                        throw JoinError (_("Content to be joined must have the same subtitle fades."));
                }
@@ -330,6 +346,7 @@ TextContent::TextContent (Content* parent, vector<shared_ptr<Content>> c)
        _y_scale = ref->y_scale ();
        _fonts = ref_fonts;
        _line_spacing = ref->line_spacing ();
+       _z_position = ref->z_position ();
        _fade_in = ref->fade_in ();
        _fade_out = ref->fade_out ();
        _outline_width = ref->outline_width ();
@@ -380,6 +397,7 @@ TextContent::as_xml (xmlpp::Node* root) const
                text->add_child("EffectBlue")->add_child_text (raw_convert<string> (_effect_colour->b));
        }
        text->add_child("LineSpacing")->add_child_text (raw_convert<string> (_line_spacing));
+       text->add_child("ZPosition")->add_child_text(raw_convert<string>(_z_position));
        if (_fade_in) {
                text->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in->get()));
        }
@@ -412,6 +430,7 @@ TextContent::identifier () const
                + "_" + raw_convert<string> (x_offset())
                + "_" + raw_convert<string> (y_offset())
                + "_" + raw_convert<string> (line_spacing())
+               + "_" + raw_convert<string> (z_position())
                + "_" + raw_convert<string> (fade_in().get_value_or(ContentTime()).get())
                + "_" + raw_convert<string> (fade_out().get_value_or(ContentTime()).get())
                + "_" + raw_convert<string> (outline_width())
@@ -435,6 +454,9 @@ TextContent::identifier () const
 void
 TextContent::add_font (shared_ptr<Font> font)
 {
+       boost::mutex::scoped_lock lm(_mutex);
+
+       DCPOMATIC_ASSERT(!get_font_unlocked(font->id()));
        _fonts.push_back (font);
        connect_to_fonts ();
 }
@@ -538,6 +560,13 @@ TextContent::set_line_spacing (double s)
        maybe_set (_line_spacing, s, TextContentProperty::LINE_SPACING);
 }
 
+void
+TextContent::set_z_position (int z)
+{
+       maybe_set (_z_position, z, TextContentProperty::Z_POSITION);
+}
+
+
 void
 TextContent::set_fade_in (ContentTime t)
 {
@@ -622,6 +651,7 @@ TextContent::take_settings_from (shared_ptr<const TextContent> c)
                unset_effect_colour ();
        }
        set_line_spacing (c->_line_spacing);
+       set_z_position (c->_z_position);
        if (c->_fade_in) {
                set_fade_in (*c->_fade_in);
        }
@@ -637,3 +667,36 @@ TextContent::take_settings_from (shared_ptr<const TextContent> c)
        set_language (c->_language);
        set_language_is_additional (c->_language_is_additional);
 }
+
+
+shared_ptr<dcpomatic::Font>
+TextContent::get_font(string id) const
+{
+       boost::mutex::scoped_lock lm(_mutex);
+       return get_font_unlocked(id);
+}
+
+
+shared_ptr<dcpomatic::Font>
+TextContent::get_font_unlocked(string id) const
+{
+       auto iter = std::find_if(_fonts.begin(), _fonts.end(), [&id](shared_ptr<dcpomatic::Font> font) {
+               return font->id() == id;
+       });
+
+       if (iter == _fonts.end()) {
+               return {};
+       }
+
+       return *iter;
+}
+
+
+void
+TextContent::clear_fonts()
+{
+       boost::mutex::scoped_lock lm(_mutex);
+
+       _fonts.clear();
+}
+