Be explicit about the version of SMPTE 428-7 that is used for subtitles, 2389-vpos v1.8.60
authorCarl Hetherington <cth@carlh.net>
Sun, 11 Dec 2022 21:27:34 +0000 (22:27 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 17 Feb 2023 23:35:01 +0000 (00:35 +0100)
and default to writing the 2014 namespace.

src/interop_subtitle_asset.h
src/smpte_subtitle_asset.cc
src/smpte_subtitle_asset.h
src/subtitle_asset.h
src/subtitle_standard.cc [new file with mode: 0644]
src/subtitle_standard.h [new file with mode: 0644]
src/wscript
test/data/2007.mxf [new file with mode: 0644]
test/data/2010.mxf [new file with mode: 0644]
test/data/2014.mxf [new file with mode: 0644]
test/smpte_subtitle_test.cc

index 670dee27334c4b40870d5ebd68e2621e17e9db03..23cf0b492fc51aac6b06baf83ecab7016bbc3fb3 100644 (file)
@@ -42,6 +42,7 @@
 
 
 #include "subtitle_asset.h"
+#include "subtitle_standard.h"
 #include <boost/filesystem.hpp>
 
 
@@ -126,6 +127,10 @@ public:
                return 1000;
        }
 
+       SubtitleStandard subtitle_standard() const override {
+               return SubtitleStandard::INTEROP;
+       }
+
        static std::string static_pkl_type (Standard) {
                return "text/xml;asdcpKind=Subtitle";
        }
index 375bae0a52091707731e9ce0685562ade53a9f09..4b2ae8cd40e8fd03bdac0dc84447b49a76bf9ea9 100644 (file)
@@ -72,13 +72,16 @@ using boost::starts_with;
 using namespace dcp;
 
 
-static string const subtitle_smpte_ns = "http://www.smpte-ra.org/schemas/428-7/2010/DCST";
+static string const subtitle_smpte_ns_2007 = "http://www.smpte-ra.org/schemas/428-7/2007/DCST";
+static string const subtitle_smpte_ns_2010 = "http://www.smpte-ra.org/schemas/428-7/2010/DCST";
+static string const subtitle_smpte_ns_2014 = "http://www.smpte-ra.org/schemas/428-7/2014/DCST";
 
 
-SMPTESubtitleAsset::SMPTESubtitleAsset ()
-       : MXF (Standard::SMPTE)
+SMPTESubtitleAsset::SMPTESubtitleAsset(SubtitleStandard standard)
+       : MXF(Standard::SMPTE)
        , _edit_rate (24, 1)
        , _time_code_rate (24)
+       , _subtitle_standard(standard)
        , _xml_id (make_uuid())
 {
 
@@ -165,6 +168,15 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
 void
 SMPTESubtitleAsset::parse_xml (shared_ptr<cxml::Document> xml)
 {
+       if (xml->namespace_uri() == subtitle_smpte_ns_2007) {
+               _subtitle_standard = SubtitleStandard::SMPTE_2007;
+       } else if (xml->namespace_uri() == subtitle_smpte_ns_2010) {
+               _subtitle_standard = SubtitleStandard::SMPTE_2010;
+       } else if (xml->namespace_uri() == subtitle_smpte_ns_2014) {
+               _subtitle_standard = SubtitleStandard::SMPTE_2014;
+       } else {
+               throw XMLError("Unrecognised subtitle namespace " + xml->namespace_uri());
+       }
        _xml_id = remove_urn_uuid(xml->string_child("Id"));
        _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
 
@@ -372,7 +384,7 @@ SMPTESubtitleAsset::xml_as_string () const
 
        subtitles_as_xml (root->add_child("SubtitleList"), _time_code_rate, Standard::SMPTE);
 
-       return format_xml(doc, { {"", subtitle_smpte_ns}, {"xs", "http://www.w3.org/2001/XMLSchema"} });
+       return format_xml(doc, { {"", schema_namespace()}, {"xs", "http://www.w3.org/2001/XMLSchema"} });
 }
 
 
@@ -419,7 +431,7 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const
                }
        }
 
-       descriptor.NamespaceName = subtitle_smpte_ns;
+       descriptor.NamespaceName = schema_namespace();
        unsigned int c;
        DCP_ASSERT (_xml_id);
        Kumu::hex2bin (_xml_id->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
@@ -576,3 +588,21 @@ SMPTESubtitleAsset::add (shared_ptr<Subtitle> s)
        _intrinsic_duration = latest_subtitle_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator);
 }
 
+
+string
+SMPTESubtitleAsset::schema_namespace() const
+{
+       switch (_subtitle_standard) {
+       case SubtitleStandard::SMPTE_2007:
+               return subtitle_smpte_ns_2007;
+       case SubtitleStandard::SMPTE_2010:
+               return subtitle_smpte_ns_2010;
+       case SubtitleStandard::SMPTE_2014:
+               return subtitle_smpte_ns_2014;
+       default:
+               DCP_ASSERT(false);
+       }
+
+       DCP_ASSERT(false);
+}
+
index b707da124ba980f605345266cc3ec2939f9c744a..2632e0a526868bf9216a416dc55b002ce2e9c095 100644 (file)
  */
 
 
-#include "subtitle_asset.h"
+#include "crypto_context.h"
 #include "language_tag.h"
 #include "local_time.h"
 #include "mxf.h"
-#include "crypto_context.h"
+#include "subtitle_asset.h"
+#include "subtitle_standard.h"
 #include <boost/filesystem.hpp>
 
 
@@ -74,7 +75,7 @@ class SMPTELoadFontNode;
 class SMPTESubtitleAsset : public SubtitleAsset, public MXF
 {
 public:
-       SMPTESubtitleAsset ();
+       explicit SMPTESubtitleAsset(SubtitleStandard standard = SubtitleStandard::SMPTE_2014);
 
        /** Construct a SMPTESubtitleAsset by reading an MXF or XML file
         *  @param file Filename
@@ -190,6 +191,10 @@ public:
                return _resource_id;
        }
 
+       SubtitleStandard subtitle_standard() const override {
+               return _subtitle_standard;
+       }
+
        static bool valid_mxf (boost::filesystem::path);
        static std::string static_pkl_type (Standard) {
                return "application/mxf";
@@ -213,6 +218,7 @@ private:
        void parse_xml (std::shared_ptr<cxml::Document> xml);
        void read_mxf_descriptor (std::shared_ptr<ASDCP::TimedText::MXFReader> reader);
        void read_mxf_resources (std::shared_ptr<ASDCP::TimedText::MXFReader> reader, std::shared_ptr<DecryptionContext> dec);
+       std::string schema_namespace() const;
 
        /** The total length of this content in video frames.  The amount of
         *  content presented may be less than this.
@@ -230,6 +236,12 @@ private:
        Fraction _edit_rate;
        int _time_code_rate = 0;
        boost::optional<Time> _start_time;
+       /** There are two SMPTE standards describing subtitles, 428-7:2010 and 428-7:2014, and they
+        *  have different interpretations of what Vposition means.  Though libdcp does not need to
+        *  know the difference, this variable stores the standard from the namespace that this asset was
+        *  written with (or will be written with).
+        */
+       SubtitleStandard _subtitle_standard;
 
        std::vector<std::shared_ptr<SMPTELoadFontNode>> _load_font_nodes;
        /** UUID for the XML inside the MXF, which should be the same as the ResourceID in the MXF (our _resource_id)
index 7448ac9a0f25c72788b318a22cca58ad5585e210..012050b1854ceca70c1d6149e1fc644626f16abb 100644 (file)
@@ -44,6 +44,7 @@
 #include "array_data.h"
 #include "asset.h"
 #include "dcp_time.h"
+#include "subtitle_standard.h"
 #include "subtitle_string.h"
 #include <libcxml/cxml.h>
 #include <boost/shared_array.hpp>
@@ -132,6 +133,8 @@ public:
                return _raw_xml;
        }
 
+       virtual SubtitleStandard subtitle_standard() const = 0;
+
        static std::string format_xml (xmlpp::Document const& document, std::vector<std::pair<std::string, std::string>> const& namespaces);
 
 protected:
diff --git a/src/subtitle_standard.cc b/src/subtitle_standard.cc
new file mode 100644 (file)
index 0000000..101f84d
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "subtitle_standard.h"
+
+
+using namespace dcp;
+
+
+bool
+dcp::uses_baseline(SubtitleStandard standard)
+{
+       return standard == SubtitleStandard::INTEROP || standard == SubtitleStandard::SMPTE_2014;
+}
+
+
+bool
+dcp::uses_bounding_box(SubtitleStandard standard)
+{
+       /* I didn't check the 2007 version but I am assuming they didn't start out using Interop-style
+        * then change their mind to bounding-box and then change it back again.
+        */
+       return standard == SubtitleStandard::SMPTE_2007 || standard == SubtitleStandard::SMPTE_2010;
+}
+
+
diff --git a/src/subtitle_standard.h b/src/subtitle_standard.h
new file mode 100644 (file)
index 0000000..95972e2
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#ifndef LIBDCP_SUBTITLE_STANDARD_H
+#define LIBDCP_SUBTITLE_STANDARD_H
+
+
+namespace dcp {
+
+
+enum class SubtitleStandard {
+       INTEROP,
+       SMPTE_2007,
+       SMPTE_2010,
+       SMPTE_2014
+};
+
+
+bool uses_baseline(SubtitleStandard standard);
+bool uses_bounding_box(SubtitleStandard standard);
+
+
+}
+
+#endif
+
index 39e3351d8a969c860ec3bc4671eaa37241a07fb5..d16e15fc32a3064786e33900fb5645c2fdee58ae 100644 (file)
@@ -113,6 +113,7 @@ def build(bld):
              subtitle_asset.cc
              subtitle_asset_internal.cc
              subtitle_image.cc
+             subtitle_standard.cc
              subtitle_string.cc
              transfer_function.cc
              types.cc
@@ -213,6 +214,7 @@ def build(bld):
               subtitle.h
               subtitle_asset.h
               subtitle_image.h
+              subtitle_standard.h
               subtitle_string.h
               transfer_function.h
               types.h
diff --git a/test/data/2007.mxf b/test/data/2007.mxf
new file mode 100644 (file)
index 0000000..3cdf486
Binary files /dev/null and b/test/data/2007.mxf differ
diff --git a/test/data/2010.mxf b/test/data/2010.mxf
new file mode 100644 (file)
index 0000000..1d1b855
Binary files /dev/null and b/test/data/2010.mxf differ
diff --git a/test/data/2014.mxf b/test/data/2014.mxf
new file mode 100644 (file)
index 0000000..6362264
Binary files /dev/null and b/test/data/2014.mxf differ
index f8412daa475054fa823b6cf223bbf8a1ff2ea1f0..a23a7c458b1513564cb901c1064071ac6f831093 100644 (file)
@@ -708,3 +708,43 @@ BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_bottom_alignment)
                );
 }
 
+
+BOOST_AUTO_TEST_CASE(smpte_subtitle_standard_written_correctly)
+{
+       RNGFixer fixer;
+
+       boost::filesystem::path const ref = "test/data";
+       boost::filesystem::path const out = "build/test/smpte_subtitle_standard_written_correctly";
+
+       boost::filesystem::remove_all(out);
+       boost::filesystem::create_directories(out);
+
+       dcp::SMPTESubtitleAsset test_2014;
+       test_2014.set_issue_date(dcp::LocalTime("2020-01-01T14:00:00"));
+       test_2014.write(out / "2014.mxf");
+       check_file(ref / "2014.mxf", out / "2014.mxf");
+
+       dcp::SMPTESubtitleAsset test_2010(dcp::SubtitleStandard::SMPTE_2010);
+       test_2010.set_issue_date(dcp::LocalTime("2020-01-01T14:00:00"));
+       test_2010.write(out / "2010.mxf");
+       check_file(ref / "2010.mxf", out / "2010.mxf");
+
+       dcp::SMPTESubtitleAsset test_2007(dcp::SubtitleStandard::SMPTE_2007);
+       test_2007.set_issue_date(dcp::LocalTime("2020-01-01T14:00:00"));
+       test_2007.write(out / "2007.mxf");
+       check_file(ref / "2007.mxf", out / "2007.mxf");
+}
+
+
+BOOST_AUTO_TEST_CASE(smpte_subtitle_standard_read_correctly)
+{
+       dcp::SMPTESubtitleAsset test_2007("test/data/2007.mxf");
+       BOOST_CHECK(test_2007.subtitle_standard() == dcp::SubtitleStandard::SMPTE_2007);
+
+       dcp::SMPTESubtitleAsset test_2010("test/data/2010.mxf");
+       BOOST_CHECK(test_2010.subtitle_standard() == dcp::SubtitleStandard::SMPTE_2010);
+
+       dcp::SMPTESubtitleAsset test_2014("test/data/2014.mxf");
+       BOOST_CHECK(test_2014.subtitle_standard() == dcp::SubtitleStandard::SMPTE_2014);
+}
+