Rename everything.
authorCarl Hetherington <cth@carlh.net>
Mon, 4 Dec 2023 22:14:40 +0000 (23:14 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 4 Dec 2023 23:49:33 +0000 (00:49 +0100)
64 files changed:
examples/read_dcp.cc
src/asset_factory.cc
src/combine.cc
src/dcp.cc
src/interop_subtitle_asset.cc [deleted file]
src/interop_subtitle_asset.h [deleted file]
src/interop_text_asset.cc [new file with mode: 0644]
src/interop_text_asset.h [new file with mode: 0644]
src/reel.cc
src/reel_closed_caption_asset.cc
src/reel_closed_caption_asset.h
src/reel_interop_closed_caption_asset.cc
src/reel_interop_closed_caption_asset.h
src/reel_interop_subtitle_asset.cc
src/reel_interop_subtitle_asset.h
src/reel_smpte_closed_caption_asset.cc
src/reel_smpte_closed_caption_asset.h
src/reel_smpte_subtitle_asset.cc
src/reel_smpte_subtitle_asset.h
src/reel_subtitle_asset.cc
src/reel_subtitle_asset.h
src/smpte_subtitle_asset.cc [deleted file]
src/smpte_subtitle_asset.h [deleted file]
src/smpte_text_asset.cc [new file with mode: 0644]
src/smpte_text_asset.h [new file with mode: 0644]
src/subtitle.cc [deleted file]
src/subtitle.h [deleted file]
src/subtitle_asset.cc [deleted file]
src/subtitle_asset.h [deleted file]
src/subtitle_asset_internal.cc [deleted file]
src/subtitle_asset_internal.h [deleted file]
src/subtitle_image.cc
src/subtitle_image.h
src/subtitle_standard.cc [deleted file]
src/subtitle_standard.h [deleted file]
src/subtitle_string.cc [deleted file]
src/subtitle_string.h [deleted file]
src/text.cc [new file with mode: 0644]
src/text.h [new file with mode: 0644]
src/text_asset.cc [new file with mode: 0644]
src/text_asset.h [new file with mode: 0644]
src/text_asset_internal.cc [new file with mode: 0644]
src/text_asset_internal.h [new file with mode: 0644]
src/text_standard.cc [new file with mode: 0644]
src/text_standard.h [new file with mode: 0644]
src/text_string.cc [new file with mode: 0644]
src/text_string.h [new file with mode: 0644]
src/verify.cc
src/wscript
test/combine_test.cc
test/dcp_font_test.cc
test/decryption_test.cc
test/encryption_test.cc
test/interop_subtitle_test.cc
test/kdm_test.cc
test/rewrite_subs.cc
test/shared_subtitle_test.cc
test/smpte_subtitle_test.cc
test/subs_in_out.cc
test/test.cc
test/test.h
test/verify_test.cc
tools/dcpdumpsub.cc
tools/dcpinfo.cc

index f099409f81996acc5349349aac7478243b2ebe0c..d87cb60e212c7396c3c064ee2cc9062b4fc3eeb9 100644 (file)
@@ -32,7 +32,7 @@
 #include "mono_picture_asset_reader.h"
 #include "stereo_picture_asset.h"
 #include "sound_asset.h"
-#include "subtitle_asset.h"
+#include "text_asset.h"
 #include "openjpeg_image.h"
 #include "colour_conversion.h"
 #include "rgb_xyz.h"
@@ -77,8 +77,8 @@ main ()
                        std::cout << "3D picture\n";
                } else if (std::dynamic_pointer_cast<dcp::SoundAsset>(i)) {
                        std::cout << "Sound\n";
-               } else if (std::dynamic_pointer_cast<dcp::SubtitleAsset>(i)) {
-                       std::cout << "Subtitle\n";
+               } else if (std::dynamic_pointer_cast<dcp::TextAsset>(i)) {
+                       std::cout << "Subtitle / closed caption\n";
                } else if (std::dynamic_pointer_cast<dcp::CPL>(i)) {
                        std::cout << "CPL\n";
                }
index d326ba680d30f85d38e491e076742dc652fd7354..4884f09077ee895859dc2688b3cf4875817f78c7 100644 (file)
@@ -41,7 +41,7 @@
 #include "atmos_asset.h"
 #include "compose.hpp"
 #include "mono_picture_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "sound_asset.h"
 #include "stereo_picture_asset.h"
 #include "stereo_picture_asset.h"
@@ -90,7 +90,7 @@ dcp::asset_factory (boost::filesystem::path path, bool ignore_incorrect_picture_
        case ASDCP::ESS_JPEG_2000_S:
                return make_shared<StereoPictureAsset>(path);
        case ASDCP::ESS_TIMED_TEXT:
-               return make_shared<SMPTESubtitleAsset>(path);
+               return make_shared<SMPTETextAsset>(path);
        case ASDCP::ESS_DCDATA_DOLBY_ATMOS:
                return make_shared<AtmosAsset>(path);
        default:
index b7a625f089df8e236722da76e7cad5f8f37d3a3d..74456bfb776b4f27cbb39e9f3903722412035300 100644 (file)
@@ -45,7 +45,7 @@
 #include "exceptions.h"
 #include "filesystem.h"
 #include "font_asset.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "raw_convert.h"
 #include <boost/filesystem.hpp>
 #include <set>
@@ -139,7 +139,7 @@ dcp::combine (
                                continue;
                        }
 
-                       auto sub = dynamic_pointer_cast<dcp::InteropSubtitleAsset>(j);
+                       auto sub = dynamic_pointer_cast<dcp::InteropTextAsset>(j);
                        if (sub) {
                                /* Interop fonts are really fiddly.  The font files are assets (in the ASSETMAP)
                                 * and also linked from the font XML by filename.  We have to fix both these things,
index d603cfae48694ae71d89ae9f0b57a673efd0381a..a809529ed3d7120c92a14296bb7a528baccb63d0 100644 (file)
@@ -49,7 +49,7 @@
 #include "exceptions.h"
 #include "filesystem.h"
 #include "font_asset.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "metadata.h"
 #include "mono_picture_asset.h"
 #include "picture_asset.h"
@@ -57,7 +57,7 @@
 #include "raw_convert.h"
 #include "reel_asset.h"
 #include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "sound_asset.h"
 #include "stereo_picture_asset.h"
 #include "util.h"
@@ -221,7 +221,7 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
 
                if (
                        pkl_type == remove_parameters(CPL::static_pkl_type(standard)) ||
-                       pkl_type == remove_parameters(InteropSubtitleAsset::static_pkl_type(standard))) {
+                       pkl_type == remove_parameters(InteropTextAsset::static_pkl_type(standard))) {
                        auto p = new xmlpp::DomParser;
                        try {
                                p->parse_file(dcp::filesystem::fix_long_path(path).string());
@@ -243,13 +243,13 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m
                                if (standard == Standard::SMPTE && notes) {
                                        notes->push_back (VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_STANDARD));
                                }
-                               other_assets.push_back (make_shared<InteropSubtitleAsset>(path));
+                               other_assets.push_back(make_shared<InteropTextAsset>(path));
                        }
                } else if (
                        *pkl_type == remove_parameters(PictureAsset::static_pkl_type(standard)) ||
                        *pkl_type == remove_parameters(SoundAsset::static_pkl_type(standard)) ||
                        *pkl_type == remove_parameters(AtmosAsset::static_pkl_type(standard)) ||
-                       *pkl_type == remove_parameters(SMPTESubtitleAsset::static_pkl_type(standard))
+                       *pkl_type == remove_parameters(SMPTETextAsset::static_pkl_type(standard))
                        ) {
 
                        bool found_threed_marked_as_twod = false;
@@ -542,7 +542,7 @@ DCP::assets (bool ignore_unresolved) const
                                auto o = j->asset_ref().asset();
                                assets.push_back (o);
                                /* More Interop special-casing */
-                               auto sub = dynamic_pointer_cast<InteropSubtitleAsset>(o);
+                               auto sub = dynamic_pointer_cast<InteropTextAsset>(o);
                                if (sub) {
                                        add_to_container(assets, sub->font_assets());
                                }
diff --git a/src/interop_subtitle_asset.cc b/src/interop_subtitle_asset.cc
deleted file mode 100644 (file)
index 32c3f66..0000000
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/interop_subtitle_asset.cc
- *  @brief InteropSubtitleAsset class
- */
-
-
-#include "compose.hpp"
-#include "dcp_assert.h"
-#include "equality_options.h"
-#include "filesystem.h"
-#include "font_asset.h"
-#include "file.h"
-#include "interop_load_font_node.h"
-#include "interop_subtitle_asset.h"
-#include "raw_convert.h"
-#include "subtitle_asset_internal.h"
-#include "subtitle_image.h"
-#include "util.h"
-#include "warnings.h"
-#include "xml.h"
-LIBDCP_DISABLE_WARNINGS
-#include <libxml++/libxml++.h>
-LIBDCP_ENABLE_WARNINGS
-#include <boost/weak_ptr.hpp>
-#include <cmath>
-#include <cstdio>
-
-
-using std::cerr;
-using std::cout;
-using std::dynamic_pointer_cast;
-using std::make_shared;
-using std::shared_ptr;
-using std::string;
-using std::vector;
-using boost::optional;
-using namespace dcp;
-
-
-InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file)
-       : SubtitleAsset (file)
-{
-       _raw_xml = dcp::file_to_string (file);
-
-       auto xml = make_shared<cxml::Document>("DCSubtitle");
-       xml->read_file(dcp::filesystem::fix_long_path(file));
-       _id = xml->string_child ("SubtitleID");
-       _reel_number = xml->string_child ("ReelNumber");
-       _language = xml->string_child ("Language");
-       _movie_title = xml->string_child ("MovieTitle");
-       _load_font_nodes = type_children<InteropLoadFontNode> (xml, "LoadFont");
-
-       /* Now we need to drop down to xmlpp */
-
-       vector<ParseState> ps;
-       for (auto i: xml->node()->get_children()) {
-               auto e = dynamic_cast<xmlpp::Element const *>(i);
-               if (e && (e->get_name() == "Font" || e->get_name() == "Subtitle")) {
-                       parse_subtitles (e, ps, optional<int>(), Standard::INTEROP);
-               }
-       }
-
-       for (auto i: _subtitles) {
-               auto si = dynamic_pointer_cast<SubtitleImage>(i);
-               if (si) {
-                       si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
-               }
-       }
-}
-
-
-InteropSubtitleAsset::InteropSubtitleAsset ()
-{
-
-}
-
-
-string
-InteropSubtitleAsset::xml_as_string () const
-{
-       xmlpp::Document doc;
-       auto root = doc.create_root_node ("DCSubtitle");
-       root->set_attribute ("Version", "1.0");
-
-       root->add_child("SubtitleID")->add_child_text (_id);
-       root->add_child("MovieTitle")->add_child_text (_movie_title);
-       root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
-       root->add_child("Language")->add_child_text (_language);
-
-       for (auto i: _load_font_nodes) {
-               auto load_font = root->add_child("LoadFont");
-               load_font->set_attribute ("Id", i->id);
-               load_font->set_attribute ("URI", i->uri);
-       }
-
-       subtitles_as_xml (root, 250, Standard::INTEROP);
-
-       return format_xml(doc, {});
-}
-
-
-void
-InteropSubtitleAsset::add_font (string load_id, dcp::ArrayData data)
-{
-       _fonts.push_back (Font(load_id, make_uuid(), data));
-       auto const uri = String::compose("font_%1.ttf", _load_font_nodes.size());
-       _load_font_nodes.push_back (make_shared<InteropLoadFontNode>(load_id, uri));
-}
-
-
-bool
-InteropSubtitleAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
-{
-       if (!SubtitleAsset::equals (other_asset, options, note)) {
-               return false;
-       }
-
-       auto other = dynamic_pointer_cast<const InteropSubtitleAsset> (other_asset);
-       if (!other) {
-               return false;
-       }
-
-       if (!options.load_font_nodes_can_differ) {
-               auto i = _load_font_nodes.begin();
-               auto j = other->_load_font_nodes.begin();
-
-               while (i != _load_font_nodes.end ()) {
-                       if (j == other->_load_font_nodes.end ()) {
-                               note (NoteType::ERROR, "<LoadFont> nodes differ");
-                               return false;
-                       }
-
-                       if (**i != **j) {
-                               note (NoteType::ERROR, "<LoadFont> nodes differ");
-                               return false;
-                       }
-
-                       ++i;
-                       ++j;
-               }
-       }
-
-       if (_movie_title != other->_movie_title) {
-               note (NoteType::ERROR, "Subtitle movie titles differ");
-               return false;
-       }
-
-       return true;
-}
-
-
-vector<shared_ptr<LoadFontNode>>
-InteropSubtitleAsset::load_font_nodes () const
-{
-       vector<shared_ptr<LoadFontNode>> lf;
-       copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
-       return lf;
-}
-
-
-void
-InteropSubtitleAsset::write (boost::filesystem::path p) const
-{
-       File f(p, "wb");
-       if (!f) {
-               throw FileError ("Could not open file for writing", p, -1);
-       }
-
-       _raw_xml = xml_as_string ();
-       /* length() here gives bytes not characters */
-       f.write(_raw_xml->c_str(), 1, _raw_xml->length());
-
-       _file = p;
-
-       /* Image subtitles */
-       for (auto i: _subtitles) {
-               auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
-               if (im) {
-                       im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
-               }
-       }
-
-       /* Fonts */
-       for (auto i: _load_font_nodes) {
-               auto file = p.parent_path() / i->uri;
-               auto font_with_id = std::find_if(_fonts.begin(), _fonts.end(), [i](Font const& font) { return font.load_id == i->id; });
-               if (font_with_id != _fonts.end()) {
-                       font_with_id->data.write(file);
-                       font_with_id->file = file;
-               }
-       }
-}
-
-
-/** Look at a supplied list of assets and find the fonts.  Then match these
- *  fonts up with anything requested by a <LoadFont> so that _fonts contains
- *  a list of font ID, load ID and data.
- */
-void
-InteropSubtitleAsset::resolve_fonts (vector<shared_ptr<Asset>> assets)
-{
-       for (auto asset: assets) {
-               auto font = dynamic_pointer_cast<FontAsset>(asset);
-               if (!font) {
-                       continue;
-               }
-
-               DCP_ASSERT(_file);
-
-               for (auto load_font_node: _load_font_nodes) {
-                       auto const path_in_load_font_node = _file->parent_path() / load_font_node->uri;
-                       if (font->file() && path_in_load_font_node == *font->file()) {
-                               auto existing = std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; });
-                               if (existing != _fonts.end()) {
-                                       *existing = Font(load_font_node->id, asset->id(), font->file().get());
-                               } else {
-                                       _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
-                               }
-                       }
-               }
-       }
-}
-
-
-vector<shared_ptr<Asset>>
-InteropSubtitleAsset::font_assets()
-{
-       vector<shared_ptr<Asset>> assets;
-       for (auto const& i: _fonts) {
-               DCP_ASSERT (i.file);
-               assets.push_back(make_shared<FontAsset>(i.uuid, i.file.get()));
-       }
-       return assets;
-}
-
-
-vector<shared_ptr<const Asset>>
-InteropSubtitleAsset::font_assets() const
-{
-       vector<shared_ptr<const Asset>> assets;
-       for (auto const& i: _fonts) {
-               DCP_ASSERT (i.file);
-               assets.push_back(make_shared<const FontAsset>(i.uuid, i.file.get()));
-       }
-       return assets;
-}
-
-
-void
-InteropSubtitleAsset::add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const
-{
-       Asset::add_to_assetmap(asset_map, root);
-
-       for (auto i: _subtitles) {
-               auto im = dynamic_pointer_cast<dcp::SubtitleImage>(i);
-               if (im) {
-                       DCP_ASSERT(im->file());
-                       add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
-               }
-       }
-}
-
-
-void
-InteropSubtitleAsset::add_to_pkl (shared_ptr<PKL> pkl, boost::filesystem::path root) const
-{
-       Asset::add_to_pkl (pkl, root);
-
-       for (auto i: _subtitles) {
-               auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
-               if (im) {
-                       auto png_image = im->png_image ();
-                       pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
-               }
-       }
-}
-
-
-void
-InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path file)
-{
-       for (auto& i: _fonts) {
-               if (i.load_id == load_id) {
-                       i.file = file;
-               }
-       }
-
-       for (auto i: _load_font_nodes) {
-               if (i->id == load_id) {
-                       i->uri = file.filename().string();
-               }
-       }
-}
-
-
-vector<string>
-InteropSubtitleAsset::unresolved_fonts() const
-{
-       vector<string> unresolved;
-       for (auto load_font_node: _load_font_nodes) {
-               if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
-                       unresolved.push_back(load_font_node->id);
-               }
-       }
-       return unresolved;
-}
-
diff --git a/src/interop_subtitle_asset.h b/src/interop_subtitle_asset.h
deleted file mode 100644 (file)
index f63740d..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/interop_subtitle_asset.h
- *  @brief InteropSubtitleAsset class
- */
-
-
-#ifndef DCP_INTEROP_SUBTITLE_ASSET_H
-#define DCP_INTEROP_SUBTITLE_ASSET_H
-
-
-#include "subtitle_asset.h"
-#include "subtitle_standard.h"
-#include <boost/filesystem.hpp>
-
-
-namespace dcp {
-
-
-class InteropLoadFontNode;
-
-
-/** @class InteropSubtitleAsset
- *  @brief A set of subtitles to be read and/or written in the Inter-Op format
- *
- *  Inter-Op subtitles are sometimes known as CineCanvas.
- */
-class InteropSubtitleAsset : public SubtitleAsset
-{
-public:
-       InteropSubtitleAsset ();
-       explicit InteropSubtitleAsset (boost::filesystem::path file);
-
-       bool equals (
-               std::shared_ptr<const Asset>,
-               EqualityOptions const&,
-               NoteHandler note
-               ) const override;
-
-       void add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const override;
-       void add_to_pkl (std::shared_ptr<PKL> pkl, boost::filesystem::path root) const override;
-
-       std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const override;
-
-       void add_font (std::string load_id, dcp::ArrayData data) override;
-
-       std::string xml_as_string () const override;
-
-       /** Write this content to an XML file with its fonts alongside */
-       void write (boost::filesystem::path path) const override;
-
-       void resolve_fonts (std::vector<std::shared_ptr<Asset>> assets);
-       void set_font_file (std::string load_id, boost::filesystem::path file);
-       std::vector<std::shared_ptr<Asset>> font_assets();
-       std::vector<std::shared_ptr<const Asset>> font_assets() const;
-
-       /** @return the <LoadFont> IDs of fonts for which we have not (yet) found a font asset.
-        *  This could be because resolve_fonts() has not yet been called, or because there is
-        *  a missing font file.
-        */
-       std::vector<std::string> unresolved_fonts() const;
-
-       /** Set the reel number or sub-element identifier
-        *  of these subtitles.
-        *  @param n New reel number.
-        */
-       void set_reel_number (std::string n) {
-               _reel_number = n;
-       }
-
-       /** Set the language tag of these subtitles.
-        *  @param l New language.
-        */
-       void set_language (std::string l) {
-               _language = l;
-       }
-
-       /** @return title of the movie that the subtitles are for */
-       void set_movie_title (std::string m) {
-               _movie_title = m;
-       }
-
-       /** @return reel number or sub-element of a programme that
-        *  these subtitles refer to.
-        */
-       std::string reel_number () const {
-               return _reel_number;
-       }
-
-       /** @return language used in the subtitles */
-       std::string language () const {
-               return _language;
-       }
-
-       /** @return movie title that these subtitles are for */
-       std::string movie_title () const {
-               return _movie_title;
-       }
-
-       int time_code_rate () const override {
-               /* Interop can use either; just pick one */
-               return 1000;
-       }
-
-       SubtitleStandard subtitle_standard() const override {
-               return SubtitleStandard::INTEROP;
-       }
-
-       static std::string static_pkl_type (Standard) {
-               return "text/xml;asdcpKind=Subtitle";
-       }
-
-protected:
-
-       std::string pkl_type (Standard s) const override {
-               return static_pkl_type (s);
-       }
-
-private:
-       std::string _reel_number;
-       std::string _language;
-       std::string _movie_title;
-       std::vector<std::shared_ptr<InteropLoadFontNode>> _load_font_nodes;
-};
-
-
-}
-
-
-#endif
-
diff --git a/src/interop_text_asset.cc b/src/interop_text_asset.cc
new file mode 100644 (file)
index 0000000..d951557
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+    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.
+*/
+
+
+/** @file  src/interop_subtitle_asset.cc
+ *  @brief InteropTextAsset class
+ */
+
+
+#include "compose.hpp"
+#include "dcp_assert.h"
+#include "equality_options.h"
+#include "filesystem.h"
+#include "font_asset.h"
+#include "file.h"
+#include "interop_load_font_node.h"
+#include "interop_text_asset.h"
+#include "raw_convert.h"
+#include "subtitle_image.h"
+#include "text_asset_internal.h"
+#include "util.h"
+#include "warnings.h"
+#include "xml.h"
+LIBDCP_DISABLE_WARNINGS
+#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
+#include <boost/weak_ptr.hpp>
+#include <cmath>
+#include <cstdio>
+
+
+using std::cerr;
+using std::cout;
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using boost::optional;
+using namespace dcp;
+
+
+InteropTextAsset::InteropTextAsset(boost::filesystem::path file)
+       : TextAsset(file)
+{
+       _raw_xml = dcp::file_to_string (file);
+
+       auto xml = make_shared<cxml::Document>("DCSubtitle");
+       xml->read_file(dcp::filesystem::fix_long_path(file));
+       _id = xml->string_child ("SubtitleID");
+       _reel_number = xml->string_child ("ReelNumber");
+       _language = xml->string_child ("Language");
+       _movie_title = xml->string_child ("MovieTitle");
+       _load_font_nodes = type_children<InteropLoadFontNode> (xml, "LoadFont");
+
+       /* Now we need to drop down to xmlpp */
+
+       vector<ParseState> ps;
+       for (auto i: xml->node()->get_children()) {
+               auto e = dynamic_cast<xmlpp::Element const *>(i);
+               if (e && (e->get_name() == "Font" || e->get_name() == "Subtitle")) {
+                       parse_texts(e, ps, optional<int>(), Standard::INTEROP);
+               }
+       }
+
+       for (auto i: _texts) {
+               auto si = dynamic_pointer_cast<SubtitleImage>(i);
+               if (si) {
+                       si->read_png_file (file.parent_path() / String::compose("%1.png", si->id()));
+               }
+       }
+}
+
+
+string
+InteropTextAsset::xml_as_string() const
+{
+       xmlpp::Document doc;
+       auto root = doc.create_root_node ("DCSubtitle");
+       root->set_attribute ("Version", "1.0");
+
+       root->add_child("SubtitleID")->add_child_text (_id);
+       root->add_child("MovieTitle")->add_child_text (_movie_title);
+       root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
+       root->add_child("Language")->add_child_text (_language);
+
+       for (auto i: _load_font_nodes) {
+               auto load_font = root->add_child("LoadFont");
+               load_font->set_attribute ("Id", i->id);
+               load_font->set_attribute ("URI", i->uri);
+       }
+
+       texts_as_xml(root, 250, Standard::INTEROP);
+
+       return format_xml(doc, {});
+}
+
+
+void
+InteropTextAsset::add_font(string load_id, dcp::ArrayData data)
+{
+       _fonts.push_back (Font(load_id, make_uuid(), data));
+       auto const uri = String::compose("font_%1.ttf", _load_font_nodes.size());
+       _load_font_nodes.push_back (make_shared<InteropLoadFontNode>(load_id, uri));
+}
+
+
+bool
+InteropTextAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
+{
+       if (!TextAsset::equals(other_asset, options, note)) {
+               return false;
+       }
+
+       auto other = dynamic_pointer_cast<const InteropTextAsset>(other_asset);
+       if (!other) {
+               return false;
+       }
+
+       if (!options.load_font_nodes_can_differ) {
+               auto i = _load_font_nodes.begin();
+               auto j = other->_load_font_nodes.begin();
+
+               while (i != _load_font_nodes.end ()) {
+                       if (j == other->_load_font_nodes.end ()) {
+                               note (NoteType::ERROR, "<LoadFont> nodes differ");
+                               return false;
+                       }
+
+                       if (**i != **j) {
+                               note (NoteType::ERROR, "<LoadFont> nodes differ");
+                               return false;
+                       }
+
+                       ++i;
+                       ++j;
+               }
+       }
+
+       if (_movie_title != other->_movie_title) {
+               note (NoteType::ERROR, "Subtitle or closed caption movie titles differ");
+               return false;
+       }
+
+       return true;
+}
+
+
+vector<shared_ptr<LoadFontNode>>
+InteropTextAsset::load_font_nodes() const
+{
+       vector<shared_ptr<LoadFontNode>> lf;
+       copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
+       return lf;
+}
+
+
+void
+InteropTextAsset::write(boost::filesystem::path p) const
+{
+       File f(p, "wb");
+       if (!f) {
+               throw FileError ("Could not open file for writing", p, -1);
+       }
+
+       _raw_xml = xml_as_string ();
+       /* length() here gives bytes not characters */
+       f.write(_raw_xml->c_str(), 1, _raw_xml->length());
+
+       _file = p;
+
+       /* Image subtitles */
+       for (auto i: _texts) {
+               auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
+               if (im) {
+                       im->write_png_file(p.parent_path() / String::compose("%1.png", im->id()));
+               }
+       }
+
+       /* Fonts */
+       for (auto i: _load_font_nodes) {
+               auto file = p.parent_path() / i->uri;
+               auto font_with_id = std::find_if(_fonts.begin(), _fonts.end(), [i](Font const& font) { return font.load_id == i->id; });
+               if (font_with_id != _fonts.end()) {
+                       font_with_id->data.write(file);
+                       font_with_id->file = file;
+               }
+       }
+}
+
+
+/** Look at a supplied list of assets and find the fonts.  Then match these
+ *  fonts up with anything requested by a <LoadFont> so that _fonts contains
+ *  a list of font ID, load ID and data.
+ */
+void
+InteropTextAsset::resolve_fonts(vector<shared_ptr<Asset>> assets)
+{
+       for (auto asset: assets) {
+               auto font = dynamic_pointer_cast<FontAsset>(asset);
+               if (!font) {
+                       continue;
+               }
+
+               DCP_ASSERT(_file);
+
+               for (auto load_font_node: _load_font_nodes) {
+                       auto const path_in_load_font_node = _file->parent_path() / load_font_node->uri;
+                       if (font->file() && path_in_load_font_node == *font->file()) {
+                               auto existing = std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; });
+                               if (existing != _fonts.end()) {
+                                       *existing = Font(load_font_node->id, asset->id(), font->file().get());
+                               } else {
+                                       _fonts.push_back(Font(load_font_node->id, asset->id(), font->file().get()));
+                               }
+                       }
+               }
+       }
+}
+
+
+vector<shared_ptr<Asset>>
+InteropTextAsset::font_assets()
+{
+       vector<shared_ptr<Asset>> assets;
+       for (auto const& i: _fonts) {
+               DCP_ASSERT (i.file);
+               assets.push_back(make_shared<FontAsset>(i.uuid, i.file.get()));
+       }
+       return assets;
+}
+
+
+vector<shared_ptr<const Asset>>
+InteropTextAsset::font_assets() const
+{
+       vector<shared_ptr<const Asset>> assets;
+       for (auto const& i: _fonts) {
+               DCP_ASSERT (i.file);
+               assets.push_back(make_shared<const FontAsset>(i.uuid, i.file.get()));
+       }
+       return assets;
+}
+
+
+void
+InteropTextAsset::add_to_assetmap(AssetMap& asset_map, boost::filesystem::path root) const
+{
+       Asset::add_to_assetmap(asset_map, root);
+
+       for (auto i: _texts) {
+               auto im = dynamic_pointer_cast<dcp::SubtitleImage>(i);
+               if (im) {
+                       DCP_ASSERT(im->file());
+                       add_file_to_assetmap(asset_map, root, im->file().get(), im->id());
+               }
+       }
+}
+
+
+void
+InteropTextAsset::add_to_pkl(shared_ptr<PKL> pkl, boost::filesystem::path root) const
+{
+       Asset::add_to_pkl (pkl, root);
+
+       for (auto i: _texts) {
+               auto im = dynamic_pointer_cast<dcp::SubtitleImage> (i);
+               if (im) {
+                       auto png_image = im->png_image ();
+                       pkl->add_asset(im->id(), optional<string>(), make_digest(png_image), png_image.size(), "image/png", root.filename().string());
+               }
+       }
+}
+
+
+void
+InteropTextAsset::set_font_file(string load_id, boost::filesystem::path file)
+{
+       for (auto& i: _fonts) {
+               if (i.load_id == load_id) {
+                       i.file = file;
+               }
+       }
+
+       for (auto i: _load_font_nodes) {
+               if (i->id == load_id) {
+                       i->uri = file.filename().string();
+               }
+       }
+}
+
+
+vector<string>
+InteropTextAsset::unresolved_fonts() const
+{
+       vector<string> unresolved;
+       for (auto load_font_node: _load_font_nodes) {
+               if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
+                       unresolved.push_back(load_font_node->id);
+               }
+       }
+       return unresolved;
+}
+
diff --git a/src/interop_text_asset.h b/src/interop_text_asset.h
new file mode 100644 (file)
index 0000000..b260222
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+    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.
+*/
+
+
+/** @file  src/interop_text_asset.h
+ *  @brief InteropTextAsset class
+ */
+
+
+#ifndef DCP_INTEROP_TEXT_ASSET_H
+#define DCP_INTEROP_TEXT_ASSET_H
+
+
+#include "text_asset.h"
+#include "text_standard.h"
+#include <boost/filesystem.hpp>
+
+
+namespace dcp {
+
+
+class InteropLoadFontNode;
+
+
+/** @class InteropTextAsset
+ *  @brief A set of subtitles or closed captions to be read and/or written in the Inter-Op format
+ *
+ *  Inter-Op subtitles are sometimes known as CineCanvas.
+ */
+class InteropTextAsset : public TextAsset
+{
+public:
+       InteropTextAsset() = default;
+       explicit InteropTextAsset(boost::filesystem::path file);
+
+       bool equals (
+               std::shared_ptr<const Asset>,
+               EqualityOptions const&,
+               NoteHandler note
+               ) const override;
+
+       void add_to_assetmap (AssetMap& asset_map, boost::filesystem::path root) const override;
+       void add_to_pkl (std::shared_ptr<PKL> pkl, boost::filesystem::path root) const override;
+
+       std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const override;
+
+       void add_font (std::string load_id, dcp::ArrayData data) override;
+
+       std::string xml_as_string () const override;
+
+       /** Write this content to an XML file with its fonts alongside */
+       void write (boost::filesystem::path path) const override;
+
+       void resolve_fonts (std::vector<std::shared_ptr<Asset>> assets);
+       void set_font_file (std::string load_id, boost::filesystem::path file);
+       std::vector<std::shared_ptr<Asset>> font_assets();
+       std::vector<std::shared_ptr<const Asset>> font_assets() const;
+
+       /** @return the <LoadFont> IDs of fonts for which we have not (yet) found a font asset.
+        *  This could be because resolve_fonts() has not yet been called, or because there is
+        *  a missing font file.
+        */
+       std::vector<std::string> unresolved_fonts() const;
+
+       /** Set the reel number or sub-element identifier
+        *  of these subtitles / closed captions.
+        *  @param n New reel number.
+        */
+       void set_reel_number (std::string n) {
+               _reel_number = n;
+       }
+
+       /** Set the language tag of these subtitles / closed captions.
+        *  @param l New language.
+        */
+       void set_language (std::string l) {
+               _language = l;
+       }
+
+       /** @return title of the movie that the subtitles / closed captions are for */
+       void set_movie_title (std::string m) {
+               _movie_title = m;
+       }
+
+       /** @return reel number or sub-element of a programme that
+        *  these subtitles / closed captions refer to.
+        */
+       std::string reel_number () const {
+               return _reel_number;
+       }
+
+       /** @return language used in the subtitles / closed captions */
+       std::string language () const {
+               return _language;
+       }
+
+       /** @return movie title that these subtitles / closed captions are for */
+       std::string movie_title () const {
+               return _movie_title;
+       }
+
+       int time_code_rate () const override {
+               /* Interop can use either; just pick one */
+               return 1000;
+       }
+
+       TextStandard text_standard() const override {
+               return TextStandard::INTEROP;
+       }
+
+       static std::string static_pkl_type (Standard) {
+               return "text/xml;asdcpKind=Subtitle";
+       }
+
+protected:
+
+       std::string pkl_type (Standard s) const override {
+               return static_pkl_type (s);
+       }
+
+private:
+       std::string _reel_number;
+       std::string _language;
+       std::string _movie_title;
+       std::vector<std::shared_ptr<InteropLoadFontNode>> _load_font_nodes;
+};
+
+
+}
+
+
+#endif
+
index a8481d593f03db5a8f60517353da65a7d8fd3508..95769140bff4734349e9e7385fb2b40ef6993183 100644 (file)
@@ -40,7 +40,7 @@
 #include "decrypted_kdm.h"
 #include "decrypted_kdm_key.h"
 #include "equality_options.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "mono_picture_asset.h"
 #include "picture_asset.h"
 #include "reel.h"
 #include "reel_sound_asset.h"
 #include "reel_stereo_picture_asset.h"
 #include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "sound_asset.h"
 #include "stereo_picture_asset.h"
-#include "subtitle_asset.h"
+#include "text_asset.h"
 #include "util.h"
 #include <libxml++/nodes/element.h>
 #include <stdint.h>
@@ -409,7 +409,7 @@ Reel::resolve_refs (vector<shared_ptr<Asset>> assets)
 
                /* Interop subtitle handling is all special cases */
                if (_main_subtitle->asset_ref().resolved()) {
-                       auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (_main_subtitle->asset_ref().asset());
+                       auto iop = dynamic_pointer_cast<InteropTextAsset>(_main_subtitle->asset_ref().asset());
                        if (iop) {
                                iop->resolve_fonts (assets);
                        }
@@ -421,7 +421,7 @@ Reel::resolve_refs (vector<shared_ptr<Asset>> assets)
 
                /* Interop subtitle handling is all special cases */
                if (i->asset_ref().resolved()) {
-                       auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (i->asset_ref().asset());
+                       auto iop = dynamic_pointer_cast<InteropTextAsset>(i->asset_ref().asset());
                        if (iop) {
                                iop->resolve_fonts (assets);
                        }
index e5649d6a407b3b331909c0ea33bf403d84852d6a..a9ba32f5a24ca2b9e0b619086907c08f65c4f0d1 100644 (file)
@@ -39,8 +39,8 @@
 
 #include "dcp_assert.h"
 #include "reel_closed_caption_asset.h"
-#include "smpte_subtitle_asset.h"
-#include "subtitle_asset.h"
+#include "smpte_text_asset.h"
+#include "text_asset.h"
 #include "warnings.h"
 LIBDCP_DISABLE_WARNINGS
 #include <libxml++/libxml++.h>
@@ -53,10 +53,10 @@ using std::dynamic_pointer_cast;
 using namespace dcp;
 
 
-ReelClosedCaptionAsset::ReelClosedCaptionAsset (std::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
+ReelClosedCaptionAsset::ReelClosedCaptionAsset(std::shared_ptr<TextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelFileAsset (
                asset,
-               dynamic_pointer_cast<SMPTESubtitleAsset>(asset) ? dynamic_pointer_cast<SMPTESubtitleAsset>(asset)->key_id() : boost::none,
+               dynamic_pointer_cast<SMPTETextAsset>(asset) ? dynamic_pointer_cast<SMPTETextAsset>(asset)->key_id() : boost::none,
                asset->id(),
                edit_rate,
                intrinsic_duration,
index 405de34b2e2cff77090555a5b452a7ca3e993d0d..adc1e1eb4a54a54b14705a9d58c3c008d7a2431a 100644 (file)
@@ -44,7 +44,7 @@
 #include "language_tag.h"
 #include "reel_asset.h"
 #include "reel_file_asset.h"
-#include "subtitle_asset.h"
+#include "text_asset.h"
 
 
 struct verify_invalid_language2;
@@ -59,15 +59,15 @@ namespace dcp {
 class ReelClosedCaptionAsset : public ReelFileAsset
 {
 public:
-       ReelClosedCaptionAsset (std::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
+       ReelClosedCaptionAsset(std::shared_ptr<TextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
        explicit ReelClosedCaptionAsset (std::shared_ptr<const cxml::Node>);
 
-       std::shared_ptr<const SubtitleAsset> asset () const {
-               return asset_of_type<const SubtitleAsset>();
+       std::shared_ptr<const TextAsset> asset () const {
+               return asset_of_type<const TextAsset>();
        }
 
-       std::shared_ptr<SubtitleAsset> asset () {
-               return asset_of_type<SubtitleAsset>();
+       std::shared_ptr<TextAsset> asset () {
+               return asset_of_type<TextAsset>();
        }
 
        bool equals(std::shared_ptr<const ReelClosedCaptionAsset>, EqualityOptions const&, NoteHandler) const;
index be968068eed92f1dfa122502dab0710ab6f5c45e..ea1b85c96a677a54a196688f8670d55f278e66e3 100644 (file)
@@ -46,14 +46,13 @@ using std::string;
 using namespace dcp;
 
 
-ReelInteropClosedCaptionAsset::ReelInteropClosedCaptionAsset (shared_ptr<InteropSubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
+ReelInteropClosedCaptionAsset::ReelInteropClosedCaptionAsset(shared_ptr<InteropTextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelClosedCaptionAsset (asset, edit_rate, intrinsic_duration, entry_point)
 {
 
 }
 
 
-
 ReelInteropClosedCaptionAsset::ReelInteropClosedCaptionAsset (shared_ptr<const cxml::Node> node)
        : ReelClosedCaptionAsset (node)
 {
index 5e8f7c1ef306045dce6d1d8b39d84319fec18896..7e1ee6c5a0b48ee37e1c6ad3e12aade77bb0464e 100644 (file)
@@ -41,7 +41,7 @@
 #define LIBDCP_REEL_INTEROP_CLOSED_CAPTION_ASSET_H
 
 
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "reel_closed_caption_asset.h"
 
 
@@ -51,15 +51,15 @@ namespace dcp {
 class ReelInteropClosedCaptionAsset : public ReelClosedCaptionAsset
 {
 public:
-       ReelInteropClosedCaptionAsset (std::shared_ptr<InteropSubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
+       ReelInteropClosedCaptionAsset(std::shared_ptr<InteropTextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
        explicit ReelInteropClosedCaptionAsset (std::shared_ptr<const cxml::Node>);
 
-       std::shared_ptr<const InteropSubtitleAsset> interop_asset () const {
-               return asset_of_type<const InteropSubtitleAsset>();
+       std::shared_ptr<const InteropTextAsset> interop_asset() const {
+               return asset_of_type<const InteropTextAsset>();
        }
 
-       std::shared_ptr<InteropSubtitleAsset> interop_asset () {
-               return asset_of_type<InteropSubtitleAsset>();
+       std::shared_ptr<InteropTextAsset> interop_asset() {
+               return asset_of_type<InteropTextAsset>();
        }
 
        xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override;
index 5f295d53a8b12602c64732ffaa4eea859bfce6cf..6b9ef79c5eb611d8c3ed48922556b3cac6e2cee4 100644 (file)
@@ -50,7 +50,7 @@ using boost::optional;
 using namespace dcp;
 
 
-ReelInteropSubtitleAsset::ReelInteropSubtitleAsset (std::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
+ReelInteropSubtitleAsset::ReelInteropSubtitleAsset(std::shared_ptr<TextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelSubtitleAsset (asset, edit_rate, intrinsic_duration, entry_point)
 {
 
index 7e90e9e0c3f504cb41f58f66629aba81e1527e16..1a7da9d47dd3dd71e553c788648fd766261ef5d7 100644 (file)
 */
 
 
-/** @file  src/reel_interop_subtitle_asset.h
- *  @brief ReelInteropSubtitleAsset class
+/** @file  src/reel_interop_text_asset.h
+ *  @brief ReelInteropTextAsset class
  */
 
 
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "reel_file_asset.h"
 #include "reel_subtitle_asset.h"
 
@@ -51,15 +51,15 @@ namespace dcp {
 class ReelInteropSubtitleAsset : public ReelSubtitleAsset
 {
 public:
-       ReelInteropSubtitleAsset (std::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
+       ReelInteropSubtitleAsset(std::shared_ptr<TextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
        explicit ReelInteropSubtitleAsset (std::shared_ptr<const cxml::Node>);
 
-       std::shared_ptr<const InteropSubtitleAsset> interop_asset () const {
-               return asset_of_type<const InteropSubtitleAsset>();
+       std::shared_ptr<const InteropTextAsset> interop_asset () const {
+               return asset_of_type<const InteropTextAsset>();
        }
 
-       std::shared_ptr<InteropSubtitleAsset> interop_asset () {
-               return asset_of_type<InteropSubtitleAsset>();
+       std::shared_ptr<InteropTextAsset> interop_asset () {
+               return asset_of_type<InteropTextAsset>();
        }
 };
 
index a2a68202a997a5e5dff7f94cfeab085d7156e8f5..22f23b8ee072ff5134176fa3a5b977077e157edb 100644 (file)
@@ -51,7 +51,7 @@ using std::string;
 using namespace dcp;
 
 
-ReelSMPTEClosedCaptionAsset::ReelSMPTEClosedCaptionAsset (shared_ptr<SMPTESubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
+ReelSMPTEClosedCaptionAsset::ReelSMPTEClosedCaptionAsset(shared_ptr<SMPTETextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelClosedCaptionAsset (asset, edit_rate, intrinsic_duration, entry_point)
 {
 
index 32a79efd2588ccf8229edd6a84ad2e25e67b200f..e4eb4f64c670bddf2969b251d42c6903319c9483 100644 (file)
@@ -43,7 +43,7 @@
 
 #include "reel_file_asset.h"
 #include "reel_closed_caption_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 
 
 namespace dcp {
@@ -52,15 +52,15 @@ namespace dcp {
 class ReelSMPTEClosedCaptionAsset : public ReelClosedCaptionAsset
 {
 public:
-       ReelSMPTEClosedCaptionAsset (std::shared_ptr<SMPTESubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
+       ReelSMPTEClosedCaptionAsset(std::shared_ptr<SMPTETextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
        explicit ReelSMPTEClosedCaptionAsset (std::shared_ptr<const cxml::Node>);
 
-       std::shared_ptr<SMPTESubtitleAsset> smpte_asset () {
-               return asset_of_type<SMPTESubtitleAsset>();
+       std::shared_ptr<SMPTETextAsset> smpte_asset() {
+               return asset_of_type<SMPTETextAsset>();
        }
 
-       std::shared_ptr<const SMPTESubtitleAsset> smpte_asset () const {
-               return asset_of_type<const SMPTESubtitleAsset>();
+       std::shared_ptr<const SMPTETextAsset> smpte_asset() const {
+               return asset_of_type<const SMPTETextAsset>();
        }
 
        xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override;
index 64440547ce036fff99cf7f11eb77a233fae15679..807622697acbcf28f64a38cd6c7dcc42ace2273c 100644 (file)
@@ -38,7 +38,7 @@
 
 
 #include "reel_smpte_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "warnings.h"
 LIBDCP_DISABLE_WARNINGS
 #include <libxml++/libxml++.h>
@@ -51,7 +51,7 @@ using boost::optional;
 using namespace dcp;
 
 
-ReelSMPTESubtitleAsset::ReelSMPTESubtitleAsset (shared_ptr<SMPTESubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
+ReelSMPTESubtitleAsset::ReelSMPTESubtitleAsset(shared_ptr<SMPTETextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelSubtitleAsset (asset, edit_rate, intrinsic_duration, entry_point)
 {
 
index 2a097309ef452c3a3585ce7b620561ec2e6a3ec3..d2ba3fd4033caef0931e858ac89dc15c07295f01 100644 (file)
 
 
 #include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 
 
 namespace dcp {
 
 
-class SMPTESubtitleAsset;
+class SMPTETextAsset;
 
 
 /** @class ReelSMPTESubtitleAsset
@@ -53,15 +53,15 @@ class SMPTESubtitleAsset;
 class ReelSMPTESubtitleAsset : public ReelSubtitleAsset
 {
 public:
-       ReelSMPTESubtitleAsset (std::shared_ptr<SMPTESubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
+       ReelSMPTESubtitleAsset(std::shared_ptr<SMPTETextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
        explicit ReelSMPTESubtitleAsset (std::shared_ptr<const cxml::Node>);
 
-       std::shared_ptr<const SMPTESubtitleAsset> smpte_asset () const {
-               return asset_of_type<const SMPTESubtitleAsset>();
+       std::shared_ptr<const SMPTETextAsset> smpte_asset() const {
+               return asset_of_type<const SMPTETextAsset>();
        }
 
-       std::shared_ptr<SMPTESubtitleAsset> smpte_asset () {
-               return asset_of_type<SMPTESubtitleAsset>();
+       std::shared_ptr<SMPTETextAsset> smpte_asset() {
+               return asset_of_type<SMPTETextAsset>();
        }
 
 private:
index d856a05e0bf1f9f7f1575b6feb7796dcec597aa6..6e091c81aec77a1ea2eb78594499f67a97c72b63 100644 (file)
@@ -39,8 +39,8 @@
 
 #include "language_tag.h"
 #include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
-#include "subtitle_asset.h"
+#include "smpte_text_asset.h"
+#include "text_asset.h"
 #include "warnings.h"
 LIBDCP_DISABLE_WARNINGS
 #include <libxml++/libxml++.h>
@@ -54,10 +54,10 @@ using boost::optional;
 using namespace dcp;
 
 
-ReelSubtitleAsset::ReelSubtitleAsset (std::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
+ReelSubtitleAsset::ReelSubtitleAsset(std::shared_ptr<TextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point)
        : ReelFileAsset (
                asset,
-               dynamic_pointer_cast<SMPTESubtitleAsset>(asset) ? dynamic_pointer_cast<SMPTESubtitleAsset>(asset)->key_id() : boost::none,
+               dynamic_pointer_cast<SMPTETextAsset>(asset) ? dynamic_pointer_cast<SMPTETextAsset>(asset)->key_id() : boost::none,
                asset->id(),
                edit_rate,
                intrinsic_duration,
index 8b694fd675494ecac9bc9b419faeaedcd4e71d90..7bc38a0ff43554adfd9188949293d8fb41dc7112 100644 (file)
@@ -44,7 +44,7 @@
 #include "language_tag.h"
 #include "reel_asset.h"
 #include "reel_file_asset.h"
-#include "subtitle_asset.h"
+#include "text_asset.h"
 
 
 struct verify_invalid_language1;
@@ -53,7 +53,7 @@ struct verify_invalid_language1;
 namespace dcp {
 
 
-class SubtitleAsset;
+class TextAsset;
 
 
 /** @class ReelSubtitleAsset
@@ -62,15 +62,15 @@ class SubtitleAsset;
 class ReelSubtitleAsset : public ReelFileAsset
 {
 public:
-       ReelSubtitleAsset (std::shared_ptr<SubtitleAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
+       ReelSubtitleAsset(std::shared_ptr<TextAsset> asset, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point);
        explicit ReelSubtitleAsset (std::shared_ptr<const cxml::Node>);
 
-       std::shared_ptr<const SubtitleAsset> asset () const {
-               return asset_of_type<const SubtitleAsset>();
+       std::shared_ptr<const TextAsset> asset() const {
+               return asset_of_type<const TextAsset>();
        }
 
-       std::shared_ptr<SubtitleAsset> asset () {
-               return asset_of_type<SubtitleAsset>();
+       std::shared_ptr<TextAsset> asset() {
+               return asset_of_type<TextAsset>();
        }
 
        xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override;
diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc
deleted file mode 100644 (file)
index 0ff1d7e..0000000
+++ /dev/null
@@ -1,618 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/smpte_subtitle_asset.cc
- *  @brief SMPTESubtitleAsset class
- */
-
-
-#include "compose.hpp"
-#include "crypto_context.h"
-#include "dcp_assert.h"
-#include "equality_options.h"
-#include "exceptions.h"
-#include "filesystem.h"
-#include "raw_convert.h"
-#include "smpte_load_font_node.h"
-#include "smpte_subtitle_asset.h"
-#include "subtitle_image.h"
-#include "util.h"
-#include "warnings.h"
-#include "xml.h"
-LIBDCP_DISABLE_WARNINGS
-#include <asdcp/AS_DCP.h>
-#include <asdcp/KM_util.h>
-#include <asdcp/KM_log.h>
-#include <libxml++/libxml++.h>
-LIBDCP_ENABLE_WARNINGS
-#include <boost/algorithm/string.hpp>
-
-
-using std::string;
-using std::list;
-using std::vector;
-using std::map;
-using std::shared_ptr;
-using std::dynamic_pointer_cast;
-using std::make_shared;
-using boost::split;
-using boost::is_any_of;
-using boost::shared_array;
-using boost::optional;
-using boost::starts_with;
-using namespace dcp;
-
-
-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(SubtitleStandard standard)
-       : MXF(Standard::SMPTE)
-       , _edit_rate (24, 1)
-       , _time_code_rate (24)
-       , _subtitle_standard(standard)
-       , _xml_id (make_uuid())
-{
-
-}
-
-
-SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
-       : SubtitleAsset (file)
-{
-       auto xml = make_shared<cxml::Document>("SubtitleReel");
-
-       auto reader = make_shared<ASDCP::TimedText::MXFReader>();
-       auto r = Kumu::RESULT_OK;
-       {
-               ASDCPErrorSuspender sus;
-               r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str());
-       }
-       if (!ASDCP_FAILURE(r)) {
-               /* MXF-wrapped */
-               ASDCP::WriterInfo info;
-               reader->FillWriterInfo (info);
-               _id = read_writer_info (info);
-               if (!_key_id) {
-                       /* Not encrypted; read it in now */
-                       string xml_string;
-                       reader->ReadTimedTextResource (xml_string);
-                       _raw_xml = xml_string;
-                       xml->read_string (xml_string);
-                       parse_xml (xml);
-                       read_mxf_descriptor (reader);
-                       read_mxf_resources(reader, std::make_shared<DecryptionContext>(optional<Key>(), Standard::SMPTE));
-               } else {
-                       read_mxf_descriptor (reader);
-               }
-       } else {
-               /* Plain XML */
-               try {
-                       _raw_xml = dcp::file_to_string (file);
-                       xml = make_shared<cxml::Document>("SubtitleReel");
-                       xml->read_file(dcp::filesystem::fix_long_path(file));
-                       parse_xml (xml);
-               } catch (cxml::Error& e) {
-                       boost::throw_exception (
-                               ReadError (
-                                       String::compose (
-                                               "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3",
-                                               file, static_cast<int>(r), e.what()
-                                               )
-                                       )
-                               );
-               }
-
-               /* Try to read PNG files from the same folder that the XML is in; the wisdom of this is
-                  debatable, at best...
-               */
-               for (auto i: _subtitles) {
-                       auto im = dynamic_pointer_cast<SubtitleImage>(i);
-                       if (im && im->png_image().size() == 0) {
-                               /* Even more dubious; allow <id>.png or urn:uuid:<id>.png */
-                               auto p = file.parent_path() / String::compose("%1.png", im->id());
-                               if (filesystem::is_regular_file(p)) {
-                                       im->read_png_file (p);
-                               } else if (starts_with (im->id(), "urn:uuid:")) {
-                                       p = file.parent_path() / String::compose("%1.png", remove_urn_uuid(im->id()));
-                                       if (filesystem::is_regular_file(p)) {
-                                               im->read_png_file (p);
-                                       }
-                               }
-                       }
-               }
-               _standard = Standard::SMPTE;
-       }
-
-       /* Check that all required image data have been found */
-       for (auto i: _subtitles) {
-               auto im = dynamic_pointer_cast<SubtitleImage>(i);
-               if (im && im->png_image().size() == 0) {
-                       throw MissingSubtitleImageError (im->id());
-               }
-       }
-}
-
-
-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");
-
-       _content_title_text = xml->string_child ("ContentTitleText");
-       _annotation_text = xml->optional_string_child ("AnnotationText");
-       _issue_date = LocalTime (xml->string_child ("IssueDate"));
-       _reel_number = xml->optional_number_child<int> ("ReelNumber");
-       _language = xml->optional_string_child ("Language");
-
-       /* This is supposed to be two numbers, but a single number has been seen in the wild */
-       auto const er = xml->string_child ("EditRate");
-       vector<string> er_parts;
-       split (er_parts, er, is_any_of (" "));
-       if (er_parts.size() == 1) {
-               _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
-       } else if (er_parts.size() == 2) {
-               _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
-       } else {
-               throw XMLError ("malformed EditRate " + er);
-       }
-
-       _time_code_rate = xml->number_child<int> ("TimeCodeRate");
-       if (xml->optional_string_child ("StartTime")) {
-               _start_time = Time (xml->string_child("StartTime"), _time_code_rate);
-       }
-
-       /* Now we need to drop down to xmlpp */
-
-       vector<ParseState> ps;
-       for (auto i: xml->node()->get_children()) {
-               auto const e = dynamic_cast<xmlpp::Element const *>(i);
-               if (e && e->get_name() == "SubtitleList") {
-                       parse_subtitles (e, ps, _time_code_rate, Standard::SMPTE);
-               }
-       }
-
-       /* Guess intrinsic duration */
-       _intrinsic_duration = latest_subtitle_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator);
-}
-
-
-void
-SMPTESubtitleAsset::read_mxf_resources (shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
-{
-       ASDCP::TimedText::TimedTextDescriptor descriptor;
-       reader->FillTimedTextDescriptor (descriptor);
-
-       /* Load fonts and images */
-
-       for (
-               auto i = descriptor.ResourceList.begin();
-               i != descriptor.ResourceList.end();
-               ++i) {
-
-               ASDCP::TimedText::FrameBuffer buffer;
-               buffer.Capacity(32 * 1024 * 1024);
-               auto const result = reader->ReadAncillaryResource(i->ResourceID, buffer, dec->context(), dec->hmac());
-               if (ASDCP_FAILURE(result)) {
-                       switch (i->Type) {
-                       case ASDCP::TimedText::MT_OPENTYPE:
-                               throw ReadError(String::compose("Could not read font from MXF file (%1)", static_cast<int>(result)));
-                       case ASDCP::TimedText::MT_PNG:
-                               throw ReadError(String::compose("Could not read subtitle image from MXF file (%1)", static_cast<int>(result)));
-                       default:
-                               throw ReadError(String::compose("Could not read resource from MXF file (%1)", static_cast<int>(result)));
-                       }
-               }
-
-               char id[64];
-               Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof(id));
-
-               switch (i->Type) {
-               case ASDCP::TimedText::MT_OPENTYPE:
-               {
-                       auto j = _load_font_nodes.begin();
-                       while (j != _load_font_nodes.end() && (*j)->urn != id) {
-                               ++j;
-                       }
-
-                       if (j != _load_font_nodes.end ()) {
-                               _fonts.push_back(Font((*j)->id, (*j)->urn, ArrayData(buffer.RoData(), buffer.Size())));
-                       }
-                       break;
-               }
-               case ASDCP::TimedText::MT_PNG:
-               {
-                       auto j = _subtitles.begin();
-                       while (j != _subtitles.end() && ((!dynamic_pointer_cast<SubtitleImage>(*j)) || dynamic_pointer_cast<SubtitleImage>(*j)->id() != id)) {
-                               ++j;
-                       }
-
-                       if (j != _subtitles.end()) {
-                               dynamic_pointer_cast<SubtitleImage>(*j)->set_png_image(ArrayData(buffer.RoData(), buffer.Size()));
-                       }
-                       break;
-               }
-               default:
-                       break;
-               }
-       }
-}
-
-
-void
-SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr<ASDCP::TimedText::MXFReader> reader)
-{
-       ASDCP::TimedText::TimedTextDescriptor descriptor;
-       reader->FillTimedTextDescriptor (descriptor);
-
-       _intrinsic_duration = descriptor.ContainerDuration;
-       /* The thing which is called AssetID in the descriptor is also known as the
-        * ResourceID of the MXF.  We store that, at present just for verification
-        * purposes.
-        */
-       char id[64];
-       Kumu::bin2UUIDhex (descriptor.AssetID, ASDCP::UUIDlen, id, sizeof(id));
-       _resource_id = id;
-}
-
-
-void
-SMPTESubtitleAsset::set_key (Key key)
-{
-       /* See if we already have a key; if we do, and we have a file, we'll already
-          have read that file.
-       */
-       auto const had_key = static_cast<bool>(_key);
-       auto const had_key_id = static_cast<bool>(_key_id);
-
-       MXF::set_key (key);
-
-       if (!had_key_id || !_file || had_key) {
-               /* Either we don't have any data to read, it wasn't
-                  encrypted, or we've already read it, so we don't
-                  need to do anything else.
-               */
-               return;
-       }
-
-       /* Our data was encrypted; now we can decrypt it */
-
-       auto reader = make_shared<ASDCP::TimedText::MXFReader>();
-       auto r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str());
-       if (ASDCP_FAILURE (r)) {
-               boost::throw_exception (
-                       ReadError (
-                               String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast<int> (r))
-                               )
-                       );
-       }
-
-       auto dec = make_shared<DecryptionContext>(key, Standard::SMPTE);
-       string xml_string;
-       reader->ReadTimedTextResource (xml_string, dec->context(), dec->hmac());
-       _raw_xml = xml_string;
-       auto xml = make_shared<cxml::Document>("SubtitleReel");
-       xml->read_string (xml_string);
-       parse_xml (xml);
-       read_mxf_descriptor(reader);
-       read_mxf_resources (reader, dec);
-}
-
-
-vector<shared_ptr<LoadFontNode>>
-SMPTESubtitleAsset::load_font_nodes () const
-{
-       vector<shared_ptr<LoadFontNode>> lf;
-       copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter(lf));
-       return lf;
-}
-
-
-bool
-SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
-{
-       ASDCP::TimedText::MXFReader reader;
-       Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL);
-       auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str());
-       Kumu::DefaultLogSink().SetFilterFlag(Kumu::LOG_ALLOW_ALL);
-       return !ASDCP_FAILURE (r);
-}
-
-
-string
-SMPTESubtitleAsset::xml_as_string () const
-{
-       xmlpp::Document doc;
-       auto root = doc.create_root_node ("SubtitleReel");
-
-       DCP_ASSERT (_xml_id);
-       root->add_child("Id")->add_child_text("urn:uuid:" + *_xml_id);
-       root->add_child("ContentTitleText")->add_child_text(_content_title_text);
-       if (_annotation_text) {
-               root->add_child("AnnotationText")->add_child_text(_annotation_text.get());
-       }
-       root->add_child("IssueDate")->add_child_text(_issue_date.as_string(false, false));
-       if (_reel_number) {
-               root->add_child("ReelNumber")->add_child_text(raw_convert<string>(_reel_number.get()));
-       }
-       if (_language) {
-               root->add_child("Language")->add_child_text(_language.get());
-       }
-       root->add_child("EditRate")->add_child_text(_edit_rate.as_string());
-       root->add_child("TimeCodeRate")->add_child_text(raw_convert<string>(_time_code_rate));
-       if (_start_time) {
-               root->add_child("StartTime")->add_child_text(_start_time.get().as_string(Standard::SMPTE));
-       }
-
-       for (auto i: _load_font_nodes) {
-               auto load_font = root->add_child("LoadFont");
-               load_font->add_child_text ("urn:uuid:" + i->urn);
-               load_font->set_attribute ("ID", i->id);
-       }
-
-       subtitles_as_xml (root->add_child("SubtitleList"), _time_code_rate, Standard::SMPTE);
-
-       return format_xml(doc, std::make_pair(string{}, schema_namespace()));
-}
-
-
-void
-SMPTESubtitleAsset::write (boost::filesystem::path p) const
-{
-       EncryptionContext enc (key(), Standard::SMPTE);
-
-       ASDCP::WriterInfo writer_info;
-       fill_writer_info (&writer_info, _id);
-
-       ASDCP::TimedText::TimedTextDescriptor descriptor;
-       descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
-       descriptor.EncodingName = "UTF-8";
-
-       /* Font references */
-
-       for (auto i: _load_font_nodes) {
-               auto j = _fonts.begin();
-               while (j != _fonts.end() && j->load_id != i->id) {
-                       ++j;
-               }
-               if (j != _fonts.end ()) {
-                       ASDCP::TimedText::TimedTextResourceDescriptor res;
-                       unsigned int c;
-                       Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
-                       DCP_ASSERT (c == Kumu::UUID_Length);
-                       res.Type = ASDCP::TimedText::MT_OPENTYPE;
-                       descriptor.ResourceList.push_back (res);
-               }
-       }
-
-       /* Image subtitle references */
-
-       for (auto i: _subtitles) {
-               auto si = dynamic_pointer_cast<SubtitleImage>(i);
-               if (si) {
-                       ASDCP::TimedText::TimedTextResourceDescriptor res;
-                       unsigned int c;
-                       Kumu::hex2bin (si->id().c_str(), res.ResourceID, Kumu::UUID_Length, &c);
-                       DCP_ASSERT (c == Kumu::UUID_Length);
-                       res.Type = ASDCP::TimedText::MT_PNG;
-                       descriptor.ResourceList.push_back (res);
-               }
-       }
-
-       descriptor.NamespaceName = schema_namespace();
-       unsigned int c;
-       DCP_ASSERT (_xml_id);
-       Kumu::hex2bin (_xml_id->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
-       DCP_ASSERT (c == Kumu::UUID_Length);
-       descriptor.ContainerDuration = _intrinsic_duration;
-
-       ASDCP::TimedText::MXFWriter writer;
-       /* This header size is a guess.  Empirically it seems that each subtitle reference is 90 bytes, and we need some extra.
-          The default size is not enough for some feature-length PNG sub projects (see DCP-o-matic #1561).
-       */
-       ASDCP::Result_t r = writer.OpenWrite(dcp::filesystem::fix_long_path(p).string().c_str(), writer_info, descriptor, _subtitles.size() * 90 + 16384);
-       if (ASDCP_FAILURE (r)) {
-               boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
-       }
-
-       _raw_xml = xml_as_string ();
-
-       r = writer.WriteTimedTextResource (*_raw_xml, enc.context(), enc.hmac());
-       if (ASDCP_FAILURE (r)) {
-               boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
-       }
-
-       /* Font payload */
-
-       for (auto i: _load_font_nodes) {
-               auto j = _fonts.begin();
-               while (j != _fonts.end() && j->load_id != i->id) {
-                       ++j;
-               }
-               if (j != _fonts.end ()) {
-                       ASDCP::TimedText::FrameBuffer buffer;
-                       ArrayData data_copy(j->data);
-                       buffer.SetData (data_copy.data(), data_copy.size());
-                       buffer.Size (j->data.size());
-                       r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
-                       if (ASDCP_FAILURE(r)) {
-                               boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
-                       }
-               }
-       }
-
-       /* Image subtitle payload */
-
-       for (auto i: _subtitles) {
-               auto si = dynamic_pointer_cast<SubtitleImage>(i);
-               if (si) {
-                       ASDCP::TimedText::FrameBuffer buffer;
-                       buffer.SetData (si->png_image().data(), si->png_image().size());
-                       buffer.Size (si->png_image().size());
-                       r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
-                       if (ASDCP_FAILURE(r)) {
-                               boost::throw_exception (MXFFileError ("could not write PNG data to timed text resource", p.string(), r));
-                       }
-               }
-       }
-
-       writer.Finalize ();
-
-       _file = p;
-}
-
-bool
-SMPTESubtitleAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
-{
-       if (!SubtitleAsset::equals (other_asset, options, note)) {
-               return false;
-       }
-
-       auto other = dynamic_pointer_cast<const SMPTESubtitleAsset>(other_asset);
-       if (!other) {
-               note (NoteType::ERROR, "Subtitles are in different standards");
-               return false;
-       }
-
-       auto i = _load_font_nodes.begin();
-       auto j = other->_load_font_nodes.begin();
-
-       while (i != _load_font_nodes.end ()) {
-               if (j == other->_load_font_nodes.end ()) {
-                       note (NoteType::ERROR, "<LoadFont> nodes differ");
-                       return false;
-               }
-
-               if ((*i)->id != (*j)->id) {
-                       note (NoteType::ERROR, "<LoadFont> nodes differ");
-                       return false;
-               }
-
-               ++i;
-               ++j;
-       }
-
-       if (_content_title_text != other->_content_title_text) {
-               note (NoteType::ERROR, "Subtitle content title texts differ");
-               return false;
-       }
-
-       if (_language != other->_language) {
-               note (NoteType::ERROR, String::compose("Subtitle languages differ (`%1' vs `%2')", _language.get_value_or("[none]"), other->_language.get_value_or("[none]")));
-               return false;
-       }
-
-       if (_annotation_text != other->_annotation_text) {
-               note (NoteType::ERROR, "Subtitle annotation texts differ");
-               return false;
-       }
-
-       if (_issue_date != other->_issue_date) {
-               if (options.issue_dates_can_differ) {
-                       note (NoteType::NOTE, "Subtitle issue dates differ");
-               } else {
-                       note (NoteType::ERROR, "Subtitle issue dates differ");
-                       return false;
-               }
-       }
-
-       if (_reel_number != other->_reel_number) {
-               note (NoteType::ERROR, "Subtitle reel numbers differ");
-               return false;
-       }
-
-       if (_edit_rate != other->_edit_rate) {
-               note (NoteType::ERROR, "Subtitle edit rates differ");
-               return false;
-       }
-
-       if (_time_code_rate != other->_time_code_rate) {
-               note (NoteType::ERROR, "Subtitle time code rates differ");
-               return false;
-       }
-
-       if (_start_time != other->_start_time) {
-               note (NoteType::ERROR, "Subtitle start times differ");
-               return false;
-       }
-
-       return true;
-}
-
-
-void
-SMPTESubtitleAsset::add_font (string load_id, dcp::ArrayData data)
-{
-       string const uuid = make_uuid ();
-       _fonts.push_back (Font(load_id, uuid, data));
-       _load_font_nodes.push_back (make_shared<SMPTELoadFontNode>(load_id, uuid));
-}
-
-
-void
-SMPTESubtitleAsset::add (shared_ptr<Subtitle> s)
-{
-       SubtitleAsset::add (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);
-}
diff --git a/src/smpte_subtitle_asset.h b/src/smpte_subtitle_asset.h
deleted file mode 100644 (file)
index 26144fe..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
-    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_SMPTE_SUBTITLE_ASSET_H
-#define LIBDCP_SMPTE_SUBTITLE_ASSET_H
-
-
-/** @file  src/smpte_subtitle_asset.h
- *  @brief SMPTESubtitleAsset class
- */
-
-
-#include "crypto_context.h"
-#include "language_tag.h"
-#include "local_time.h"
-#include "mxf.h"
-#include "subtitle_asset.h"
-#include "subtitle_standard.h"
-#include <boost/filesystem.hpp>
-
-
-namespace ASDCP {
-       namespace TimedText {
-               class MXFReader;
-       }
-}
-
-
-struct verify_invalid_language1;
-struct verify_invalid_language2;
-struct write_subtitles_in_vertical_order_with_top_alignment;
-struct write_subtitles_in_vertical_order_with_bottom_alignment;
-
-
-namespace dcp {
-
-
-class SMPTELoadFontNode;
-
-
-/** @class SMPTESubtitleAsset
- *  @brief A set of subtitles to be read and/or written in the SMPTE format
- */
-class SMPTESubtitleAsset : public SubtitleAsset, public MXF
-{
-public:
-       explicit SMPTESubtitleAsset(SubtitleStandard standard = SubtitleStandard::SMPTE_2014);
-
-       /** Construct a SMPTESubtitleAsset by reading an MXF or XML file
-        *  @param file Filename
-        */
-       explicit SMPTESubtitleAsset (boost::filesystem::path file);
-
-       bool equals (
-               std::shared_ptr<const Asset>,
-               EqualityOptions const&,
-               NoteHandler note
-               ) const override;
-
-       std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const override;
-
-       std::string xml_as_string () const override;
-
-       /** Write this content to a MXF file */
-       void write (boost::filesystem::path path) const override;
-
-       void add (std::shared_ptr<Subtitle>) override;
-       void add_font (std::string id, dcp::ArrayData data) override;
-       void set_key (Key key) override;
-
-       void set_content_title_text (std::string t) {
-               _content_title_text = t;
-       }
-
-       void set_language (dcp::LanguageTag l) {
-               _language = l.to_string();
-       }
-
-       void set_issue_date (LocalTime t) {
-               _issue_date = t;
-       }
-
-       void set_reel_number (int r) {
-               _reel_number = r;
-       }
-
-       void set_edit_rate (Fraction e) {
-               _edit_rate = e;
-       }
-
-       void set_time_code_rate (int t) {
-               _time_code_rate = t;
-       }
-
-       void set_start_time (Time t) {
-               _start_time = t;
-       }
-
-       void set_intrinsic_duration (int64_t d) {
-               _intrinsic_duration = d;
-       }
-
-       int64_t intrinsic_duration () const {
-               return _intrinsic_duration;
-       }
-
-       /** @return title of the film that these subtitles are for,
-        *  to be presented to the user
-        */
-       std::string content_title_text () const {
-               return _content_title_text;
-       }
-
-       /** @return Language, if one was set.  This should be a xs:language, but
-        *  it might not be if a non-compliant DCP was read in.
-        */
-       boost::optional<std::string> language () const {
-               return _language;
-       }
-
-       /** @return annotation text, to be presented to the user */
-       boost::optional<std::string> annotation_text () const {
-               return _annotation_text;
-       }
-
-       /** @return file issue time and date */
-       LocalTime issue_date () const {
-               return _issue_date;
-       }
-
-       boost::optional<int> reel_number () const {
-               return _reel_number;
-       }
-
-       Fraction edit_rate () const {
-               return _edit_rate;
-       }
-
-       /** @return subdivision of 1 second that is used for subtitle times;
-        *  e.g. a time_code_rate of 250 means that a subtitle time of 0:0:0:001
-        *  represents 4ms.
-        */
-       int time_code_rate () const override {
-               return _time_code_rate;
-       }
-
-       boost::optional<Time> start_time () const {
-               return _start_time;
-       }
-
-       /** @return ID from XML's <Id> tag, or the <Id> that will be used when writing the XML,
-        *  or boost::none if this content is encrypted and no key is available.
-        */
-       boost::optional<std::string> xml_id () const {
-               return _xml_id;
-       }
-
-       /** @return ResourceID read from any MXF that was read */
-       boost::optional<std::string> resource_id () const {
-               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";
-       }
-
-protected:
-
-       std::string pkl_type (Standard s) const override {
-               return static_pkl_type (s);
-       }
-
-private:
-       friend struct ::write_smpte_subtitle_test;
-       friend struct ::write_smpte_subtitle_test2;
-       friend struct ::verify_invalid_language1;
-       friend struct ::verify_invalid_language2;
-       friend struct ::write_subtitles_in_vertical_order_with_top_alignment;
-       friend struct ::write_subtitles_in_vertical_order_with_bottom_alignment;
-
-       void read_fonts (std::shared_ptr<ASDCP::TimedText::MXFReader>);
-       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.
-        */
-       int64_t _intrinsic_duration = 0;
-       /** <ContentTitleText> from the asset */
-       std::string _content_title_text;
-       /** This is stored and returned as a string so that we can tolerate non-RFC-5646 strings,
-        *  but must be set as a dcp::LanguageTag to try to ensure that we create compliant output.
-        */
-       boost::optional<std::string> _language;
-       boost::optional<std::string> _annotation_text;
-       LocalTime _issue_date;
-       boost::optional<int> _reel_number;
-       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)
-        *  but different to the AssetUUID in the MXF (our _id) according to SMPTE Bv2.1 and Doremi's 2.8.18 release notes.
-        *  May be boost::none if this object has been made from an encrypted object without a key.
-        */
-       boost::optional<std::string> _xml_id;
-
-       /** ResourceID read from the MXF, if there was one */
-       boost::optional<std::string> _resource_id;
-};
-
-
-}
-
-
-#endif
diff --git a/src/smpte_text_asset.cc b/src/smpte_text_asset.cc
new file mode 100644 (file)
index 0000000..d373f86
--- /dev/null
@@ -0,0 +1,618 @@
+/*
+    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.
+*/
+
+
+/** @file  src/smpte_subtitle_asset.cc
+ *  @brief SMPTESubtitleAsset class
+ */
+
+
+#include "compose.hpp"
+#include "crypto_context.h"
+#include "dcp_assert.h"
+#include "equality_options.h"
+#include "exceptions.h"
+#include "filesystem.h"
+#include "raw_convert.h"
+#include "smpte_load_font_node.h"
+#include "smpte_text_asset.h"
+#include "subtitle_image.h"
+#include "util.h"
+#include "warnings.h"
+#include "xml.h"
+LIBDCP_DISABLE_WARNINGS
+#include <asdcp/AS_DCP.h>
+#include <asdcp/KM_util.h>
+#include <asdcp/KM_log.h>
+#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
+#include <boost/algorithm/string.hpp>
+
+
+using std::string;
+using std::list;
+using std::vector;
+using std::map;
+using std::shared_ptr;
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using boost::split;
+using boost::is_any_of;
+using boost::shared_array;
+using boost::optional;
+using boost::starts_with;
+using namespace dcp;
+
+
+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";
+
+
+SMPTETextAsset::SMPTETextAsset(TextStandard standard)
+       : MXF(Standard::SMPTE)
+       , _edit_rate (24, 1)
+       , _time_code_rate (24)
+       , _text_standard(standard)
+       , _xml_id (make_uuid())
+{
+
+}
+
+
+SMPTETextAsset::SMPTETextAsset(boost::filesystem::path file)
+       : TextAsset(file)
+{
+       auto xml = make_shared<cxml::Document>("SubtitleReel");
+
+       auto reader = make_shared<ASDCP::TimedText::MXFReader>();
+       auto r = Kumu::RESULT_OK;
+       {
+               ASDCPErrorSuspender sus;
+               r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str());
+       }
+       if (!ASDCP_FAILURE(r)) {
+               /* MXF-wrapped */
+               ASDCP::WriterInfo info;
+               reader->FillWriterInfo (info);
+               _id = read_writer_info (info);
+               if (!_key_id) {
+                       /* Not encrypted; read it in now */
+                       string xml_string;
+                       reader->ReadTimedTextResource (xml_string);
+                       _raw_xml = xml_string;
+                       xml->read_string (xml_string);
+                       parse_xml (xml);
+                       read_mxf_descriptor (reader);
+                       read_mxf_resources(reader, std::make_shared<DecryptionContext>(optional<Key>(), Standard::SMPTE));
+               } else {
+                       read_mxf_descriptor (reader);
+               }
+       } else {
+               /* Plain XML */
+               try {
+                       _raw_xml = dcp::file_to_string (file);
+                       xml = make_shared<cxml::Document>("SubtitleReel");
+                       xml->read_file(dcp::filesystem::fix_long_path(file));
+                       parse_xml (xml);
+               } catch (cxml::Error& e) {
+                       boost::throw_exception (
+                               ReadError (
+                                       String::compose (
+                                               "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3",
+                                               file, static_cast<int>(r), e.what()
+                                               )
+                                       )
+                               );
+               }
+
+               /* Try to read PNG files from the same folder that the XML is in; the wisdom of this is
+                  debatable, at best...
+               */
+               for (auto i: _texts) {
+                       auto im = dynamic_pointer_cast<SubtitleImage>(i);
+                       if (im && im->png_image().size() == 0) {
+                               /* Even more dubious; allow <id>.png or urn:uuid:<id>.png */
+                               auto p = file.parent_path() / String::compose("%1.png", im->id());
+                               if (filesystem::is_regular_file(p)) {
+                                       im->read_png_file (p);
+                               } else if (starts_with (im->id(), "urn:uuid:")) {
+                                       p = file.parent_path() / String::compose("%1.png", remove_urn_uuid(im->id()));
+                                       if (filesystem::is_regular_file(p)) {
+                                               im->read_png_file (p);
+                                       }
+                               }
+                       }
+               }
+               _standard = Standard::SMPTE;
+       }
+
+       /* Check that all required image data have been found */
+       for (auto i: _texts) {
+               auto im = dynamic_pointer_cast<SubtitleImage>(i);
+               if (im && im->png_image().size() == 0) {
+                       throw MissingSubtitleImageError (im->id());
+               }
+       }
+}
+
+
+void
+SMPTETextAsset::parse_xml(shared_ptr<cxml::Document> xml)
+{
+       if (xml->namespace_uri() == subtitle_smpte_ns_2007) {
+               _text_standard = TextStandard::SMPTE_2007;
+       } else if (xml->namespace_uri() == subtitle_smpte_ns_2010) {
+               _text_standard = TextStandard::SMPTE_2010;
+       } else if (xml->namespace_uri() == subtitle_smpte_ns_2014) {
+               _text_standard = TextStandard::SMPTE_2014;
+       } else {
+               throw XMLError("Unrecognised subtitle or closed caption namespace " + xml->namespace_uri());
+       }
+       _xml_id = remove_urn_uuid(xml->string_child("Id"));
+       _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
+
+       _content_title_text = xml->string_child ("ContentTitleText");
+       _annotation_text = xml->optional_string_child ("AnnotationText");
+       _issue_date = LocalTime (xml->string_child ("IssueDate"));
+       _reel_number = xml->optional_number_child<int> ("ReelNumber");
+       _language = xml->optional_string_child ("Language");
+
+       /* This is supposed to be two numbers, but a single number has been seen in the wild */
+       auto const er = xml->string_child ("EditRate");
+       vector<string> er_parts;
+       split (er_parts, er, is_any_of (" "));
+       if (er_parts.size() == 1) {
+               _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
+       } else if (er_parts.size() == 2) {
+               _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
+       } else {
+               throw XMLError ("malformed EditRate " + er);
+       }
+
+       _time_code_rate = xml->number_child<int> ("TimeCodeRate");
+       if (xml->optional_string_child ("StartTime")) {
+               _start_time = Time (xml->string_child("StartTime"), _time_code_rate);
+       }
+
+       /* Now we need to drop down to xmlpp */
+
+       vector<ParseState> ps;
+       for (auto i: xml->node()->get_children()) {
+               auto const e = dynamic_cast<xmlpp::Element const *>(i);
+               if (e && e->get_name() == "SubtitleList") {
+                       parse_texts(e, ps, _time_code_rate, Standard::SMPTE);
+               }
+       }
+
+       /* Guess intrinsic duration */
+       _intrinsic_duration = latest_text_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator);
+}
+
+
+void
+SMPTETextAsset::read_mxf_resources(shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
+{
+       ASDCP::TimedText::TimedTextDescriptor descriptor;
+       reader->FillTimedTextDescriptor (descriptor);
+
+       /* Load fonts and images */
+
+       for (
+               auto i = descriptor.ResourceList.begin();
+               i != descriptor.ResourceList.end();
+               ++i) {
+
+               ASDCP::TimedText::FrameBuffer buffer;
+               buffer.Capacity(32 * 1024 * 1024);
+               auto const result = reader->ReadAncillaryResource(i->ResourceID, buffer, dec->context(), dec->hmac());
+               if (ASDCP_FAILURE(result)) {
+                       switch (i->Type) {
+                       case ASDCP::TimedText::MT_OPENTYPE:
+                               throw ReadError(String::compose("Could not read font from MXF file (%1)", static_cast<int>(result)));
+                       case ASDCP::TimedText::MT_PNG:
+                               throw ReadError(String::compose("Could not read subtitle image from MXF file (%1)", static_cast<int>(result)));
+                       default:
+                               throw ReadError(String::compose("Could not read resource from MXF file (%1)", static_cast<int>(result)));
+                       }
+               }
+
+               char id[64];
+               Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof(id));
+
+               switch (i->Type) {
+               case ASDCP::TimedText::MT_OPENTYPE:
+               {
+                       auto j = _load_font_nodes.begin();
+                       while (j != _load_font_nodes.end() && (*j)->urn != id) {
+                               ++j;
+                       }
+
+                       if (j != _load_font_nodes.end ()) {
+                               _fonts.push_back(Font((*j)->id, (*j)->urn, ArrayData(buffer.RoData(), buffer.Size())));
+                       }
+                       break;
+               }
+               case ASDCP::TimedText::MT_PNG:
+               {
+                       auto j = _texts.begin();
+                       while (j != _texts.end() && ((!dynamic_pointer_cast<SubtitleImage>(*j)) || dynamic_pointer_cast<SubtitleImage>(*j)->id() != id)) {
+                               ++j;
+                       }
+
+                       if (j != _texts.end()) {
+                               dynamic_pointer_cast<SubtitleImage>(*j)->set_png_image(ArrayData(buffer.RoData(), buffer.Size()));
+                       }
+                       break;
+               }
+               default:
+                       break;
+               }
+       }
+}
+
+
+void
+SMPTETextAsset::read_mxf_descriptor(shared_ptr<ASDCP::TimedText::MXFReader> reader)
+{
+       ASDCP::TimedText::TimedTextDescriptor descriptor;
+       reader->FillTimedTextDescriptor (descriptor);
+
+       _intrinsic_duration = descriptor.ContainerDuration;
+       /* The thing which is called AssetID in the descriptor is also known as the
+        * ResourceID of the MXF.  We store that, at present just for verification
+        * purposes.
+        */
+       char id[64];
+       Kumu::bin2UUIDhex (descriptor.AssetID, ASDCP::UUIDlen, id, sizeof(id));
+       _resource_id = id;
+}
+
+
+void
+SMPTETextAsset::set_key(Key key)
+{
+       /* See if we already have a key; if we do, and we have a file, we'll already
+          have read that file.
+       */
+       auto const had_key = static_cast<bool>(_key);
+       auto const had_key_id = static_cast<bool>(_key_id);
+
+       MXF::set_key (key);
+
+       if (!had_key_id || !_file || had_key) {
+               /* Either we don't have any data to read, it wasn't
+                  encrypted, or we've already read it, so we don't
+                  need to do anything else.
+               */
+               return;
+       }
+
+       /* Our data was encrypted; now we can decrypt it */
+
+       auto reader = make_shared<ASDCP::TimedText::MXFReader>();
+       auto r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str());
+       if (ASDCP_FAILURE (r)) {
+               boost::throw_exception (
+                       ReadError (
+                               String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast<int> (r))
+                               )
+                       );
+       }
+
+       auto dec = make_shared<DecryptionContext>(key, Standard::SMPTE);
+       string xml_string;
+       reader->ReadTimedTextResource (xml_string, dec->context(), dec->hmac());
+       _raw_xml = xml_string;
+       auto xml = make_shared<cxml::Document>("SubtitleReel");
+       xml->read_string (xml_string);
+       parse_xml (xml);
+       read_mxf_descriptor(reader);
+       read_mxf_resources (reader, dec);
+}
+
+
+vector<shared_ptr<LoadFontNode>>
+SMPTETextAsset::load_font_nodes() const
+{
+       vector<shared_ptr<LoadFontNode>> lf;
+       copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter(lf));
+       return lf;
+}
+
+
+bool
+SMPTETextAsset::valid_mxf(boost::filesystem::path file)
+{
+       ASDCP::TimedText::MXFReader reader;
+       Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL);
+       auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str());
+       Kumu::DefaultLogSink().SetFilterFlag(Kumu::LOG_ALLOW_ALL);
+       return !ASDCP_FAILURE (r);
+}
+
+
+string
+SMPTETextAsset::xml_as_string() const
+{
+       xmlpp::Document doc;
+       auto root = doc.create_root_node ("SubtitleReel");
+
+       DCP_ASSERT (_xml_id);
+       root->add_child("Id")->add_child_text("urn:uuid:" + *_xml_id);
+       root->add_child("ContentTitleText")->add_child_text(_content_title_text);
+       if (_annotation_text) {
+               root->add_child("AnnotationText")->add_child_text(_annotation_text.get());
+       }
+       root->add_child("IssueDate")->add_child_text(_issue_date.as_string(false, false));
+       if (_reel_number) {
+               root->add_child("ReelNumber")->add_child_text(raw_convert<string>(_reel_number.get()));
+       }
+       if (_language) {
+               root->add_child("Language")->add_child_text(_language.get());
+       }
+       root->add_child("EditRate")->add_child_text(_edit_rate.as_string());
+       root->add_child("TimeCodeRate")->add_child_text(raw_convert<string>(_time_code_rate));
+       if (_start_time) {
+               root->add_child("StartTime")->add_child_text(_start_time.get().as_string(Standard::SMPTE));
+       }
+
+       for (auto i: _load_font_nodes) {
+               auto load_font = root->add_child("LoadFont");
+               load_font->add_child_text ("urn:uuid:" + i->urn);
+               load_font->set_attribute ("ID", i->id);
+       }
+
+       texts_as_xml(root->add_child("SubtitleList"), _time_code_rate, Standard::SMPTE);
+
+       return format_xml(doc, std::make_pair(string{}, schema_namespace()));
+}
+
+
+void
+SMPTETextAsset::write(boost::filesystem::path p) const
+{
+       EncryptionContext enc (key(), Standard::SMPTE);
+
+       ASDCP::WriterInfo writer_info;
+       fill_writer_info (&writer_info, _id);
+
+       ASDCP::TimedText::TimedTextDescriptor descriptor;
+       descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
+       descriptor.EncodingName = "UTF-8";
+
+       /* Font references */
+
+       for (auto i: _load_font_nodes) {
+               auto j = _fonts.begin();
+               while (j != _fonts.end() && j->load_id != i->id) {
+                       ++j;
+               }
+               if (j != _fonts.end ()) {
+                       ASDCP::TimedText::TimedTextResourceDescriptor res;
+                       unsigned int c;
+                       Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
+                       DCP_ASSERT (c == Kumu::UUID_Length);
+                       res.Type = ASDCP::TimedText::MT_OPENTYPE;
+                       descriptor.ResourceList.push_back (res);
+               }
+       }
+
+       /* Image subtitle references */
+
+       for (auto i: _texts) {
+               auto si = dynamic_pointer_cast<SubtitleImage>(i);
+               if (si) {
+                       ASDCP::TimedText::TimedTextResourceDescriptor res;
+                       unsigned int c;
+                       Kumu::hex2bin (si->id().c_str(), res.ResourceID, Kumu::UUID_Length, &c);
+                       DCP_ASSERT (c == Kumu::UUID_Length);
+                       res.Type = ASDCP::TimedText::MT_PNG;
+                       descriptor.ResourceList.push_back (res);
+               }
+       }
+
+       descriptor.NamespaceName = schema_namespace();
+       unsigned int c;
+       DCP_ASSERT (_xml_id);
+       Kumu::hex2bin (_xml_id->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
+       DCP_ASSERT (c == Kumu::UUID_Length);
+       descriptor.ContainerDuration = _intrinsic_duration;
+
+       ASDCP::TimedText::MXFWriter writer;
+       /* This header size is a guess.  Empirically it seems that each subtitle reference is 90 bytes, and we need some extra.
+          The default size is not enough for some feature-length PNG sub projects (see DCP-o-matic #1561).
+       */
+       ASDCP::Result_t r = writer.OpenWrite(dcp::filesystem::fix_long_path(p).string().c_str(), writer_info, descriptor, _texts.size() * 90 + 16384);
+       if (ASDCP_FAILURE (r)) {
+               boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
+       }
+
+       _raw_xml = xml_as_string ();
+
+       r = writer.WriteTimedTextResource (*_raw_xml, enc.context(), enc.hmac());
+       if (ASDCP_FAILURE (r)) {
+               boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
+       }
+
+       /* Font payload */
+
+       for (auto i: _load_font_nodes) {
+               auto j = _fonts.begin();
+               while (j != _fonts.end() && j->load_id != i->id) {
+                       ++j;
+               }
+               if (j != _fonts.end ()) {
+                       ASDCP::TimedText::FrameBuffer buffer;
+                       ArrayData data_copy(j->data);
+                       buffer.SetData (data_copy.data(), data_copy.size());
+                       buffer.Size (j->data.size());
+                       r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
+                       if (ASDCP_FAILURE(r)) {
+                               boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
+                       }
+               }
+       }
+
+       /* Image subtitle payload */
+
+       for (auto i: _texts) {
+               auto si = dynamic_pointer_cast<SubtitleImage>(i);
+               if (si) {
+                       ASDCP::TimedText::FrameBuffer buffer;
+                       buffer.SetData (si->png_image().data(), si->png_image().size());
+                       buffer.Size (si->png_image().size());
+                       r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
+                       if (ASDCP_FAILURE(r)) {
+                               boost::throw_exception (MXFFileError ("could not write PNG data to timed text resource", p.string(), r));
+                       }
+               }
+       }
+
+       writer.Finalize ();
+
+       _file = p;
+}
+
+bool
+SMPTETextAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
+{
+       if (!TextAsset::equals (other_asset, options, note)) {
+               return false;
+       }
+
+       auto other = dynamic_pointer_cast<const SMPTETextAsset>(other_asset);
+       if (!other) {
+               note(NoteType::ERROR, "subtitles or closed captions are in different standards");
+               return false;
+       }
+
+       auto i = _load_font_nodes.begin();
+       auto j = other->_load_font_nodes.begin();
+
+       while (i != _load_font_nodes.end ()) {
+               if (j == other->_load_font_nodes.end ()) {
+                       note (NoteType::ERROR, "<LoadFont> nodes differ");
+                       return false;
+               }
+
+               if ((*i)->id != (*j)->id) {
+                       note (NoteType::ERROR, "<LoadFont> nodes differ");
+                       return false;
+               }
+
+               ++i;
+               ++j;
+       }
+
+       if (_content_title_text != other->_content_title_text) {
+               note(NoteType::ERROR, "Subtitle / closed caption content title texts differ");
+               return false;
+       }
+
+       if (_language != other->_language) {
+               note(NoteType::ERROR, String::compose("Subtitle / closed caption languages differ (`%1' vs `%2')", _language.get_value_or("[none]"), other->_language.get_value_or("[none]")));
+               return false;
+       }
+
+       if (_annotation_text != other->_annotation_text) {
+               note(NoteType::ERROR, "Subtitle / closed caption annotation texts differ");
+               return false;
+       }
+
+       if (_issue_date != other->_issue_date) {
+               if (options.issue_dates_can_differ) {
+                       note(NoteType::NOTE, "Subtitle / closed caption issue dates differ");
+               } else {
+                       note(NoteType::ERROR, "Subtitle / closed caption issue dates differ");
+                       return false;
+               }
+       }
+
+       if (_reel_number != other->_reel_number) {
+               note(NoteType::ERROR, "Subtitle / closed caption reel numbers differ");
+               return false;
+       }
+
+       if (_edit_rate != other->_edit_rate) {
+               note(NoteType::ERROR, "Subtitle / closed caption edit rates differ");
+               return false;
+       }
+
+       if (_time_code_rate != other->_time_code_rate) {
+               note(NoteType::ERROR, "Subtitle / closed caption time code rates differ");
+               return false;
+       }
+
+       if (_start_time != other->_start_time) {
+               note(NoteType::ERROR, "Subtitle / closed caption start times differ");
+               return false;
+       }
+
+       return true;
+}
+
+
+void
+SMPTETextAsset::add_font(string load_id, dcp::ArrayData data)
+{
+       string const uuid = make_uuid ();
+       _fonts.push_back (Font(load_id, uuid, data));
+       _load_font_nodes.push_back (make_shared<SMPTELoadFontNode>(load_id, uuid));
+}
+
+
+void
+SMPTETextAsset::add(shared_ptr<Text> s)
+{
+       TextAsset::add(s);
+       _intrinsic_duration = latest_text_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator);
+}
+
+
+string
+SMPTETextAsset::schema_namespace() const
+{
+       switch (_text_standard) {
+       case TextStandard::SMPTE_2007:
+               return subtitle_smpte_ns_2007;
+       case TextStandard::SMPTE_2010:
+               return subtitle_smpte_ns_2010;
+       case TextStandard::SMPTE_2014:
+               return subtitle_smpte_ns_2014;
+       default:
+               DCP_ASSERT(false);
+       }
+
+       DCP_ASSERT(false);
+}
diff --git a/src/smpte_text_asset.h b/src/smpte_text_asset.h
new file mode 100644 (file)
index 0000000..b052fa7
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+    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_SMPTE_TEXT_ASSET_H
+#define LIBDCP_SMPTE_TEXT_ASSET_H
+
+
+/** @file  src/smpte_text_asset.h
+ *  @brief SMPTETextAsset class
+ */
+
+
+#include "crypto_context.h"
+#include "language_tag.h"
+#include "local_time.h"
+#include "mxf.h"
+#include "text_asset.h"
+#include "text_standard.h"
+#include <boost/filesystem.hpp>
+
+
+namespace ASDCP {
+       namespace TimedText {
+               class MXFReader;
+       }
+}
+
+
+struct verify_invalid_language1;
+struct verify_invalid_language2;
+struct write_subtitles_in_vertical_order_with_top_alignment;
+struct write_subtitles_in_vertical_order_with_bottom_alignment;
+
+
+namespace dcp {
+
+
+class SMPTELoadFontNode;
+
+
+/** @class SMPTETextAsset
+ *  @brief A set of subtitles or closed captions to be read and/or written in the SMPTE format
+ */
+class SMPTETextAsset : public TextAsset, public MXF
+{
+public:
+       explicit SMPTETextAsset(TextStandard standard = TextStandard::SMPTE_2014);
+
+       /** Construct a SMPTETextAsset by reading an MXF or XML file
+        *  @param file Filename
+        */
+       explicit SMPTETextAsset(boost::filesystem::path file);
+
+       bool equals (
+               std::shared_ptr<const Asset>,
+               EqualityOptions const&,
+               NoteHandler note
+               ) const override;
+
+       std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const override;
+
+       std::string xml_as_string () const override;
+
+       /** Write this content to a MXF file */
+       void write (boost::filesystem::path path) const override;
+
+       void add(std::shared_ptr<Text>) override;
+       void add_font (std::string id, dcp::ArrayData data) override;
+       void set_key (Key key) override;
+
+       void set_content_title_text (std::string t) {
+               _content_title_text = t;
+       }
+
+       void set_language (dcp::LanguageTag l) {
+               _language = l.to_string();
+       }
+
+       void set_issue_date (LocalTime t) {
+               _issue_date = t;
+       }
+
+       void set_reel_number (int r) {
+               _reel_number = r;
+       }
+
+       void set_edit_rate (Fraction e) {
+               _edit_rate = e;
+       }
+
+       void set_time_code_rate (int t) {
+               _time_code_rate = t;
+       }
+
+       void set_start_time (Time t) {
+               _start_time = t;
+       }
+
+       void set_intrinsic_duration (int64_t d) {
+               _intrinsic_duration = d;
+       }
+
+       int64_t intrinsic_duration () const {
+               return _intrinsic_duration;
+       }
+
+       /** @return title of the film that these subtitles / closed captions are for,
+        *  to be presented to the user
+        */
+       std::string content_title_text () const {
+               return _content_title_text;
+       }
+
+       /** @return Language, if one was set.  This should be a xs:language, but
+        *  it might not be if a non-compliant DCP was read in.
+        */
+       boost::optional<std::string> language () const {
+               return _language;
+       }
+
+       /** @return annotation text, to be presented to the user */
+       boost::optional<std::string> annotation_text () const {
+               return _annotation_text;
+       }
+
+       /** @return file issue time and date */
+       LocalTime issue_date () const {
+               return _issue_date;
+       }
+
+       boost::optional<int> reel_number () const {
+               return _reel_number;
+       }
+
+       Fraction edit_rate () const {
+               return _edit_rate;
+       }
+
+       /** @return subdivision of 1 second that is used for subtitle / closed caption times;
+        *  e.g. a time_code_rate of 250 means that a subtitle time of 0:0:0:001
+        *  represents 4ms.
+        */
+       int time_code_rate () const override {
+               return _time_code_rate;
+       }
+
+       boost::optional<Time> start_time () const {
+               return _start_time;
+       }
+
+       /** @return ID from XML's <Id> tag, or the <Id> that will be used when writing the XML,
+        *  or boost::none if this content is encrypted and no key is available.
+        */
+       boost::optional<std::string> xml_id () const {
+               return _xml_id;
+       }
+
+       /** @return ResourceID read from any MXF that was read */
+       boost::optional<std::string> resource_id () const {
+               return _resource_id;
+       }
+
+       TextStandard text_standard() const override {
+               return _text_standard;
+       }
+
+       static bool valid_mxf (boost::filesystem::path);
+       static std::string static_pkl_type (Standard) {
+               return "application/mxf";
+       }
+
+protected:
+
+       std::string pkl_type (Standard s) const override {
+               return static_pkl_type (s);
+       }
+
+private:
+       friend struct ::write_smpte_subtitle_test;
+       friend struct ::write_smpte_subtitle_test2;
+       friend struct ::verify_invalid_language1;
+       friend struct ::verify_invalid_language2;
+       friend struct ::write_subtitles_in_vertical_order_with_top_alignment;
+       friend struct ::write_subtitles_in_vertical_order_with_bottom_alignment;
+
+       void read_fonts (std::shared_ptr<ASDCP::TimedText::MXFReader>);
+       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.
+        */
+       int64_t _intrinsic_duration = 0;
+       /** <ContentTitleText> from the asset */
+       std::string _content_title_text;
+       /** This is stored and returned as a string so that we can tolerate non-RFC-5646 strings,
+        *  but must be set as a dcp::LanguageTag to try to ensure that we create compliant output.
+        */
+       boost::optional<std::string> _language;
+       boost::optional<std::string> _annotation_text;
+       LocalTime _issue_date;
+       boost::optional<int> _reel_number;
+       Fraction _edit_rate;
+       int _time_code_rate = 0;
+       boost::optional<Time> _start_time;
+       /** There are three SMPTE standards describing subtitles: 427-7:2007, 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).
+        */
+       TextStandard _text_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)
+        *  but different to the AssetUUID in the MXF (our _id) according to SMPTE Bv2.1 and Doremi's 2.8.18 release notes.
+        *  May be boost::none if this object has been made from an encrypted object without a key.
+        */
+       boost::optional<std::string> _xml_id;
+
+       /** ResourceID read from the MXF, if there was one */
+       boost::optional<std::string> _resource_id;
+};
+
+
+}
+
+
+#endif
diff --git a/src/subtitle.cc b/src/subtitle.cc
deleted file mode 100644 (file)
index 248d0cf..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle.cc
- *  @brief Subtitle class
- */
-
-
-#include "compose.hpp"
-#include "dcp_time.h"
-#include "equality_options.h"
-#include "subtitle.h"
-
-
-using std::shared_ptr;
-using namespace dcp;
-
-
-/** @param v_position Vertical position as a fraction of the screen height (between 0 and 1) from v_align */
-Subtitle::Subtitle (
-       Time in,
-       Time out,
-       float h_position,
-       HAlign h_align,
-       float v_position,
-       VAlign v_align,
-       float z_position,
-       Time fade_up_time,
-       Time fade_down_time
-       )
-       : _in (in)
-       , _out (out)
-       , _h_position (h_position)
-       , _h_align (h_align)
-       , _v_position (v_position)
-       , _v_align (v_align)
-       , _z_position(z_position)
-       , _fade_up_time (fade_up_time)
-       , _fade_down_time (fade_down_time)
-{
-
-}
-
-
-bool
-Subtitle::equals(shared_ptr<const Subtitle> other, EqualityOptions const& options, NoteHandler note) const
-{
-       bool same = true;
-
-       if (in() != other->in()) {
-               note(NoteType::ERROR, "subtitle in times differ");
-               same = false;
-       }
-
-       if (out() != other->out()) {
-               note(NoteType::ERROR, "subtitle out times differ");
-               same = false;
-       }
-
-       if (h_position() != other->h_position()) {
-               note(NoteType::ERROR, "subtitle horizontal positions differ");
-               same = false;
-       }
-
-       if (h_align() != other->h_align()) {
-               note(NoteType::ERROR, "subtitle horizontal alignments differ");
-               same = false;
-       }
-
-       auto const vpos = std::abs(v_position() - other->v_position());
-       if (vpos > options.max_subtitle_vertical_position_error)  {
-               note(
-                       NoteType::ERROR,
-                       String::compose("subtitle vertical positions differ by %1 (more than the allowed difference of %2)", vpos, options.max_subtitle_vertical_position_error)
-                   );
-               same = false;
-       }
-
-       if (v_align() != other->v_align()) {
-               note(NoteType::ERROR, "subtitle vertical alignments differ");
-               same = false;
-       }
-
-       if (z_position() != other->z_position()) {
-               note(NoteType::ERROR, "subtitle Z positions differ");
-               same = false;
-       }
-
-       if (fade_up_time() != other->fade_up_time()) {
-               note(NoteType::ERROR, "subtitle fade-up times differ");
-               same = false;
-       }
-
-       if (fade_down_time() != other->fade_down_time()) {
-               note(NoteType::ERROR, "subtitle fade-down times differ");
-               same = false;
-       }
-
-       return same;
-}
diff --git a/src/subtitle.h b/src/subtitle.h
deleted file mode 100644 (file)
index 1ca3f9d..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle.h
- *  @brief Subtitle class
- */
-
-
-#ifndef LIBDCP_SUBTITLE_H
-#define LIBDCP_SUBTITLE_H
-
-
-#include "dcp_time.h"
-#include "h_align.h"
-#include "v_align.h"
-
-
-namespace dcp {
-
-
-class EqualityOptions;
-
-
-class Subtitle
-{
-public:
-       virtual ~Subtitle () {}
-
-       /** @return subtitle start time (relative to the start of the reel) */
-       Time in () const {
-               return _in;
-       }
-
-       /** @return subtitle finish time (relative to the start of the reel) */
-       Time out () const {
-               return _out;
-       }
-
-       float h_position () const {
-               return _h_position;
-       }
-
-       HAlign h_align () const {
-               return _h_align;
-       }
-
-       /** @return vertical position as a proportion of the screen height from the
-        *  vertical alignment point.
-        *  (between 0 and 1)
-        */
-       float v_position () const {
-               return _v_position;
-       }
-
-       VAlign v_align () const {
-               return _v_align;
-       }
-
-       float z_position() const {
-               return _z_position;
-       }
-
-       Time fade_up_time () const {
-               return _fade_up_time;
-       }
-
-       Time fade_down_time () const {
-               return _fade_down_time;
-       }
-
-       void set_in (Time i) {
-               _in = i;
-       }
-
-       void set_out (Time o) {
-               _out = o;
-       }
-
-       void set_h_position (float p) {
-               _h_position = p;
-       }
-
-       /** @param p New vertical position as a proportion of the screen height
-        *  from the top (between 0 and 1)
-        */
-       void set_v_position (float p) {
-               _v_position = p;
-       }
-
-       void set_z_position(float z) {
-               _z_position = z;
-       }
-
-       void set_fade_up_time (Time t) {
-               _fade_up_time = t;
-       }
-
-       void set_fade_down_time (Time t) {
-               _fade_down_time = t;
-       }
-
-       virtual bool equals(std::shared_ptr<const dcp::Subtitle> other, EqualityOptions const& options, NoteHandler note) const;
-
-protected:
-
-       Subtitle (
-               Time in,
-               Time out,
-               float h_position,
-               HAlign h_align,
-               float v_position,
-               VAlign v_align,
-               float z_position,
-               Time fade_up_time,
-               Time fade_down_time
-               );
-
-       Time _in;
-       Time _out;
-       /** Horizontal position as a proportion of the screen width from the _h_align
-        *  (between 0 and 1)
-        */
-       float _h_position = 0;
-       HAlign _h_align = HAlign::CENTER;
-       /** Vertical position as a proportion of the screen height from the _v_align
-        *  (between 0 and 1)
-        */
-       float _v_position = 0;
-       VAlign _v_align = VAlign::CENTER;
-       float _z_position = 0;
-       Time _fade_up_time;
-       Time _fade_down_time;
-};
-
-
-}
-
-
-#endif
diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc
deleted file mode 100644 (file)
index 1cd4fc0..0000000
+++ /dev/null
@@ -1,1006 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle_asset.cc
- *  @brief SubtitleAsset class
- */
-
-
-#include "compose.hpp"
-#include "dcp_assert.h"
-#include "load_font_node.h"
-#include "raw_convert.h"
-#include "reel_asset.h"
-#include "subtitle_asset.h"
-#include "subtitle_asset_internal.h"
-#include "subtitle_image.h"
-#include "subtitle_string.h"
-#include "util.h"
-#include "xml.h"
-#include <asdcp/AS_DCP.h>
-#include <asdcp/KM_util.h>
-#include <libxml++/nodes/element.h>
-#include <boost/algorithm/string.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/shared_array.hpp>
-#include <algorithm>
-
-
-using std::cerr;
-using std::cout;
-using std::dynamic_pointer_cast;
-using std::make_shared;
-using std::map;
-using std::pair;
-using std::shared_ptr;
-using std::string;
-using std::vector;
-using boost::lexical_cast;
-using boost::optional;
-using namespace dcp;
-
-
-SubtitleAsset::SubtitleAsset ()
-{
-
-}
-
-
-SubtitleAsset::SubtitleAsset (boost::filesystem::path file)
-       : Asset (file)
-{
-
-}
-
-
-string
-string_attribute (xmlpp::Element const * node, string name)
-{
-       auto a = node->get_attribute (name);
-       if (!a) {
-               throw XMLError (String::compose ("missing attribute %1", name));
-       }
-       return string (a->get_value ());
-}
-
-
-optional<string>
-optional_string_attribute (xmlpp::Element const * node, string name)
-{
-       auto a = node->get_attribute (name);
-       if (!a) {
-               return {};
-       }
-       return string (a->get_value ());
-}
-
-
-optional<bool>
-optional_bool_attribute (xmlpp::Element const * node, string name)
-{
-       auto s = optional_string_attribute (node, name);
-       if (!s) {
-               return {};
-       }
-
-       return (s.get() == "1" || s.get() == "yes");
-}
-
-
-template <class T>
-optional<T>
-optional_number_attribute (xmlpp::Element const * node, string name)
-{
-       auto s = optional_string_attribute (node, name);
-       if (!s) {
-               return boost::optional<T> ();
-       }
-
-       std::string t = s.get ();
-       boost::erase_all (t, " ");
-       return raw_convert<T> (t);
-}
-
-
-SubtitleAsset::ParseState
-SubtitleAsset::font_node_state (xmlpp::Element const * node, Standard standard) const
-{
-       ParseState ps;
-
-       if (standard == Standard::INTEROP) {
-               ps.font_id = optional_string_attribute (node, "Id");
-       } else {
-               ps.font_id = optional_string_attribute (node, "ID");
-       }
-       ps.size = optional_number_attribute<int64_t> (node, "Size");
-       ps.aspect_adjust = optional_number_attribute<float> (node, "AspectAdjust");
-       ps.italic = optional_bool_attribute (node, "Italic");
-       ps.bold = optional_string_attribute(node, "Weight").get_value_or("normal") == "bold";
-       if (standard == Standard::INTEROP) {
-               ps.underline = optional_bool_attribute (node, "Underlined");
-       } else {
-               ps.underline = optional_bool_attribute (node, "Underline");
-       }
-       auto c = optional_string_attribute (node, "Color");
-       if (c) {
-               ps.colour = Colour (c.get ());
-       }
-       auto const e = optional_string_attribute (node, "Effect");
-       if (e) {
-               ps.effect = string_to_effect (e.get ());
-       }
-       c = optional_string_attribute (node, "EffectColor");
-       if (c) {
-               ps.effect_colour = Colour (c.get ());
-       }
-
-       return ps;
-}
-
-void
-SubtitleAsset::position_align (SubtitleAsset::ParseState& ps, xmlpp::Element const * node) const
-{
-       auto hp = optional_number_attribute<float> (node, "HPosition");
-       if (!hp) {
-               hp = optional_number_attribute<float> (node, "Hposition");
-       }
-       if (hp) {
-               ps.h_position = hp.get () / 100;
-       }
-
-       auto ha = optional_string_attribute (node, "HAlign");
-       if (!ha) {
-               ha = optional_string_attribute (node, "Halign");
-       }
-       if (ha) {
-               ps.h_align = string_to_halign (ha.get ());
-       }
-
-       auto vp = optional_number_attribute<float> (node, "VPosition");
-       if (!vp) {
-               vp = optional_number_attribute<float> (node, "Vposition");
-       }
-       if (vp) {
-               ps.v_position = vp.get () / 100;
-       }
-
-       auto va = optional_string_attribute (node, "VAlign");
-       if (!va) {
-               va = optional_string_attribute (node, "Valign");
-       }
-       if (va) {
-               ps.v_align = string_to_valign (va.get ());
-       }
-
-       auto zp = optional_number_attribute<float>(node, "Zposition");
-       if (zp) {
-               ps.z_position = zp.get() / 100;
-       }
-}
-
-
-SubtitleAsset::ParseState
-SubtitleAsset::text_node_state (xmlpp::Element const * node) const
-{
-       ParseState ps;
-
-       position_align (ps, node);
-
-       auto d = optional_string_attribute (node, "Direction");
-       if (d) {
-               ps.direction = string_to_direction (d.get ());
-       }
-
-       ps.type = ParseState::Type::TEXT;
-
-       return ps;
-}
-
-
-SubtitleAsset::ParseState
-SubtitleAsset::image_node_state (xmlpp::Element const * node) const
-{
-       ParseState ps;
-
-       position_align (ps, node);
-
-       ps.type = ParseState::Type::IMAGE;
-
-       return ps;
-}
-
-
-SubtitleAsset::ParseState
-SubtitleAsset::subtitle_node_state (xmlpp::Element const * node, optional<int> tcr) const
-{
-       ParseState ps;
-       ps.in = Time (string_attribute(node, "TimeIn"), tcr);
-       ps.out = Time (string_attribute(node, "TimeOut"), tcr);
-       ps.fade_up_time = fade_time (node, "FadeUpTime", tcr);
-       ps.fade_down_time = fade_time (node, "FadeDownTime", tcr);
-       return ps;
-}
-
-
-Time
-SubtitleAsset::fade_time (xmlpp::Element const * node, string name, optional<int> tcr) const
-{
-       auto const u = optional_string_attribute(node, name).get_value_or ("");
-       Time t;
-
-       if (u.empty ()) {
-               t = Time (0, 0, 0, 20, 250);
-       } else if (u.find (":") != string::npos) {
-               t = Time (u, tcr);
-       } else {
-               t = Time (0, 0, 0, lexical_cast<int> (u), tcr.get_value_or(250));
-       }
-
-       if (t > Time (0, 0, 8, 0, 250)) {
-               t = Time (0, 0, 8, 0, 250);
-       }
-
-       return t;
-}
-
-
-void
-SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>& state, optional<int> tcr, Standard standard)
-{
-       if (node->get_name() == "Font") {
-               state.push_back (font_node_state (node, standard));
-       } else if (node->get_name() == "Subtitle") {
-               state.push_back (subtitle_node_state (node, tcr));
-       } else if (node->get_name() == "Text") {
-               state.push_back (text_node_state (node));
-       } else if (node->get_name() == "SubtitleList") {
-               state.push_back (ParseState ());
-       } else if (node->get_name() == "Image") {
-               state.push_back (image_node_state (node));
-       } else {
-               throw XMLError ("unexpected node " + node->get_name());
-       }
-
-       float space_before = 0;
-
-       /* Collect <Ruby>s first */
-       auto get_text_content = [](xmlpp::Element const* element) {
-               string all_content;
-               for (auto child: element->get_children()) {
-                       auto content = dynamic_cast<xmlpp::ContentNode const*>(child);
-                       if (content) {
-                               all_content += content->get_content();
-                       }
-               }
-               return all_content;
-       };
-
-       vector<Ruby> rubies;
-       for (auto child: node->get_children()) {
-               auto element = dynamic_cast<xmlpp::Element const*>(child);
-               if (element && element->get_name() == "Ruby") {
-                       optional<string> base;
-                       optional<string> annotation;
-                       optional<float> size;
-                       optional<RubyPosition> position;
-                       optional<float> offset;
-                       optional<float> spacing;
-                       optional<float> aspect_adjust;
-                       for (auto ruby_child: element->get_children()) {
-                               if (auto ruby_element = dynamic_cast<xmlpp::Element const*>(ruby_child)) {
-                                       if (ruby_element->get_name() == "Rb") {
-                                               base = get_text_content(ruby_element);
-                                       } else if (ruby_element->get_name() == "Rt") {
-                                               annotation = get_text_content(ruby_element);
-                                               size = optional_number_attribute<float>(ruby_element, "Size");
-                                               if (auto position_string = optional_string_attribute(ruby_element, "Position")) {
-                                                       if (*position_string == "before") {
-                                                               position = RubyPosition::BEFORE;
-                                                       } else if (*position_string == "after") {
-                                                               position = RubyPosition::AFTER;
-                                                       } else {
-                                                               DCP_ASSERT(false);
-                                                       }
-                                               }
-                                               offset = optional_number_attribute<float>(ruby_element, "Offset");
-                                               spacing = optional_number_attribute<float>(ruby_element, "Spacing");
-                                               aspect_adjust = optional_number_attribute<float>(ruby_element, "AspectAdjust");
-                                       }
-                               }
-                       }
-                       DCP_ASSERT(base);
-                       DCP_ASSERT(annotation);
-                       auto ruby = Ruby{*base, *annotation};
-                       if (size) {
-                               ruby.size = *size;
-                       }
-                       if (position) {
-                               ruby.position = *position;
-                       }
-                       if (offset) {
-                               ruby.offset = *offset;
-                       }
-                       if (spacing) {
-                               ruby.spacing = *spacing;
-                       }
-                       if (aspect_adjust) {
-                               ruby.aspect_adjust = *aspect_adjust;
-                       }
-                       rubies.push_back(ruby);
-               }
-       }
-
-       for (auto i: node->get_children()) {
-
-               /* Handle actual content e.g. text */
-               auto const v = dynamic_cast<xmlpp::ContentNode const *>(i);
-               if (v) {
-                       maybe_add_subtitle (v->get_content(), state, space_before, standard, rubies);
-                       space_before = 0;
-               }
-
-               /* Handle other nodes */
-               auto const e = dynamic_cast<xmlpp::Element const *>(i);
-               if (e) {
-                       if (e->get_name() == "Space") {
-                               if (node->get_name() != "Text") {
-                                       throw XMLError ("Space node found outside Text");
-                               }
-                               auto size = optional_string_attribute(e, "Size").get_value_or("0.5");
-                               if (standard == dcp::Standard::INTEROP) {
-                                       boost::replace_all(size, "em", "");
-                               }
-                               space_before += raw_convert<float>(size);
-                       } else if (e->get_name() != "Ruby") {
-                               parse_subtitles (e, state, tcr, standard);
-                       }
-               }
-       }
-
-       state.pop_back ();
-}
-
-
-void
-SubtitleAsset::maybe_add_subtitle(
-       string text,
-       vector<ParseState> const & parse_state,
-       float space_before,
-       Standard standard,
-       vector<Ruby> const& rubies
-       )
-{
-       auto wanted = [](ParseState const& ps) {
-               return ps.type && (ps.type.get() == ParseState::Type::TEXT || ps.type.get() == ParseState::Type::IMAGE);
-       };
-
-       if (find_if(parse_state.begin(), parse_state.end(), wanted) == parse_state.end()) {
-               return;
-       }
-
-       ParseState ps;
-       for (auto const& i: parse_state) {
-               if (i.font_id) {
-                       ps.font_id = i.font_id.get();
-               }
-               if (i.size) {
-                       ps.size = i.size.get();
-               }
-               if (i.aspect_adjust) {
-                       ps.aspect_adjust = i.aspect_adjust.get();
-               }
-               if (i.italic) {
-                       ps.italic = i.italic.get();
-               }
-               if (i.bold) {
-                       ps.bold = i.bold.get();
-               }
-               if (i.underline) {
-                       ps.underline = i.underline.get();
-               }
-               if (i.colour) {
-                       ps.colour = i.colour.get();
-               }
-               if (i.effect) {
-                       ps.effect = i.effect.get();
-               }
-               if (i.effect_colour) {
-                       ps.effect_colour = i.effect_colour.get();
-               }
-               if (i.h_position) {
-                       ps.h_position = i.h_position.get();
-               }
-               if (i.h_align) {
-                       ps.h_align = i.h_align.get();
-               }
-               if (i.v_position) {
-                       ps.v_position = i.v_position.get();
-               }
-               if (i.v_align) {
-                       ps.v_align = i.v_align.get();
-               }
-               if (i.z_position) {
-                       ps.z_position = i.z_position.get();
-               }
-               if (i.direction) {
-                       ps.direction = i.direction.get();
-               }
-               if (i.in) {
-                       ps.in = i.in.get();
-               }
-               if (i.out) {
-                       ps.out = i.out.get();
-               }
-               if (i.fade_up_time) {
-                       ps.fade_up_time = i.fade_up_time.get();
-               }
-               if (i.fade_down_time) {
-                       ps.fade_down_time = i.fade_down_time.get();
-               }
-               if (i.type) {
-                       ps.type = i.type.get();
-               }
-       }
-
-       if (!ps.in || !ps.out) {
-               /* We're not in a <Subtitle> node; just ignore this content */
-               return;
-       }
-
-       DCP_ASSERT (ps.type);
-
-       switch (ps.type.get()) {
-       case ParseState::Type::TEXT:
-               _subtitles.push_back (
-                       make_shared<SubtitleString>(
-                               ps.font_id,
-                               ps.italic.get_value_or (false),
-                               ps.bold.get_value_or (false),
-                               ps.underline.get_value_or (false),
-                               ps.colour.get_value_or (dcp::Colour (255, 255, 255)),
-                               ps.size.get_value_or (42),
-                               ps.aspect_adjust.get_value_or (1.0),
-                               ps.in.get(),
-                               ps.out.get(),
-                               ps.h_position.get_value_or(0),
-                               ps.h_align.get_value_or(HAlign::CENTER),
-                               ps.v_position.get_value_or(0),
-                               ps.v_align.get_value_or(VAlign::CENTER),
-                               ps.z_position.get_value_or(0),
-                               ps.direction.get_value_or (Direction::LTR),
-                               text,
-                               ps.effect.get_value_or (Effect::NONE),
-                               ps.effect_colour.get_value_or (dcp::Colour (0, 0, 0)),
-                               ps.fade_up_time.get_value_or(Time()),
-                               ps.fade_down_time.get_value_or(Time()),
-                               space_before,
-                               rubies
-                               )
-                       );
-               break;
-       case ParseState::Type::IMAGE:
-       {
-               switch (standard) {
-               case Standard::INTEROP:
-                       if (text.size() >= 4) {
-                               /* Remove file extension */
-                               text = text.substr(0, text.size() - 4);
-                       }
-                       break;
-               case Standard::SMPTE:
-                       /* It looks like this urn:uuid: is required, but DoM wasn't expecting it (and not writing it)
-                        * until around 2.15.140 so I guess either:
-                        *   a) it is not (always) used in the field, or
-                        *   b) nobody noticed / complained.
-                        */
-                       if (text.substr(0, 9) == "urn:uuid:") {
-                               text = text.substr(9);
-                       }
-                       break;
-               }
-
-               /* Add a subtitle with no image data and we'll fill that in later */
-               _subtitles.push_back (
-                       make_shared<SubtitleImage>(
-                               ArrayData(),
-                               text,
-                               ps.in.get(),
-                               ps.out.get(),
-                               ps.h_position.get_value_or(0),
-                               ps.h_align.get_value_or(HAlign::CENTER),
-                               ps.v_position.get_value_or(0),
-                               ps.v_align.get_value_or(VAlign::CENTER),
-                               ps.z_position.get_value_or(0),
-                               ps.fade_up_time.get_value_or(Time()),
-                               ps.fade_down_time.get_value_or(Time())
-                               )
-                       );
-               break;
-       }
-       }
-}
-
-
-vector<shared_ptr<const Subtitle>>
-SubtitleAsset::subtitles () const
-{
-       vector<shared_ptr<const Subtitle>> s;
-       for (auto i: _subtitles) {
-               s.push_back (i);
-       }
-       return s;
-}
-
-
-vector<shared_ptr<const Subtitle>>
-SubtitleAsset::subtitles_during (Time from, Time to, bool starting) const
-{
-       vector<shared_ptr<const Subtitle>> s;
-       for (auto i: _subtitles) {
-               if ((starting && from <= i->in() && i->in() < to) || (!starting && i->out() >= from && i->in() <= to)) {
-                       s.push_back (i);
-               }
-       }
-
-       return s;
-}
-
-
-void
-SubtitleAsset::add (shared_ptr<Subtitle> s)
-{
-       _subtitles.push_back (s);
-}
-
-
-Time
-SubtitleAsset::latest_subtitle_out () const
-{
-       Time t;
-       for (auto i: _subtitles) {
-               if (i->out() > t) {
-                       t = i->out ();
-               }
-       }
-
-       return t;
-}
-
-
-bool
-SubtitleAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
-{
-       if (!Asset::equals (other_asset, options, note)) {
-               return false;
-       }
-
-       auto other = dynamic_pointer_cast<const SubtitleAsset> (other_asset);
-       if (!other) {
-               return false;
-       }
-
-       if (_subtitles.size() != other->_subtitles.size()) {
-               note (NoteType::ERROR, String::compose("different number of subtitles: %1 vs %2", _subtitles.size(), other->_subtitles.size()));
-               return false;
-       }
-
-       auto i = _subtitles.begin();
-       auto j = other->_subtitles.begin();
-
-       while (i != _subtitles.end()) {
-               auto string_i = dynamic_pointer_cast<SubtitleString> (*i);
-               auto string_j = dynamic_pointer_cast<SubtitleString> (*j);
-               auto image_i = dynamic_pointer_cast<SubtitleImage> (*i);
-               auto image_j = dynamic_pointer_cast<SubtitleImage> (*j);
-
-               if ((string_i && !string_j) || (image_i && !image_j)) {
-                       note (NoteType::ERROR, "subtitles differ: string vs. image");
-                       return false;
-               }
-
-               if (string_i && !string_i->equals(string_j, options, note)) {
-                       return false;
-               }
-
-               if (image_i && !image_i->equals(image_j, options, note)) {
-                       return false;
-               }
-
-               ++i;
-               ++j;
-       }
-
-       return true;
-}
-
-
-struct SubtitleSorter
-{
-       bool operator() (shared_ptr<Subtitle> a, shared_ptr<Subtitle> b) {
-               if (a->in() != b->in()) {
-                       return a->in() < b->in();
-               }
-               if (a->v_align() == VAlign::BOTTOM) {
-                       return a->v_position() > b->v_position();
-               }
-               return a->v_position() < b->v_position();
-       }
-};
-
-
-void
-SubtitleAsset::pull_fonts (shared_ptr<order::Part> part)
-{
-       if (part->children.empty ()) {
-               return;
-       }
-
-       /* Pull up from children */
-       for (auto i: part->children) {
-               pull_fonts (i);
-       }
-
-       if (part->parent) {
-               /* Establish the common font features that each of part's children have;
-                  these features go into part's font.
-               */
-               part->font = part->children.front()->font;
-               for (auto i: part->children) {
-                       part->font.take_intersection (i->font);
-               }
-
-               /* Remove common values from part's children's fonts */
-               for (auto i: part->children) {
-                       i->font.take_difference (part->font);
-               }
-       }
-
-       /* Merge adjacent children with the same font */
-       auto i = part->children.begin();
-       vector<shared_ptr<order::Part>> merged;
-
-       while (i != part->children.end()) {
-
-               if ((*i)->font.empty ()) {
-                       merged.push_back (*i);
-                       ++i;
-               } else {
-                       auto j = i;
-                       ++j;
-                       while (j != part->children.end() && (*i)->font == (*j)->font) {
-                               ++j;
-                       }
-                       if (std::distance (i, j) == 1) {
-                               merged.push_back (*i);
-                               ++i;
-                       } else {
-                               shared_ptr<order::Part> group (new order::Part (part, (*i)->font));
-                               for (auto k = i; k != j; ++k) {
-                                       (*k)->font.clear ();
-                                       group->children.push_back (*k);
-                               }
-                               merged.push_back (group);
-                               i = j;
-                       }
-               }
-       }
-
-       part->children = merged;
-}
-
-
-/** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
- *  class because the differences between the two are fairly subtle.
- */
-void
-SubtitleAsset::subtitles_as_xml (xmlpp::Element* xml_root, int time_code_rate, Standard standard) const
-{
-       auto sorted = _subtitles;
-       std::stable_sort(sorted.begin(), sorted.end(), SubtitleSorter());
-
-       /* Gather our subtitles into a hierarchy of Subtitle/Text/String objects, writing
-          font information into the bottom level (String) objects.
-       */
-
-       auto root = make_shared<order::Part>(shared_ptr<order::Part>());
-       shared_ptr<order::Subtitle> subtitle;
-       shared_ptr<order::Text> text;
-
-       Time last_in;
-       Time last_out;
-       Time last_fade_up_time;
-       Time last_fade_down_time;
-       HAlign last_h_align;
-       float last_h_position;
-       VAlign last_v_align;
-       float last_v_position;
-       float last_z_position;
-       Direction last_direction;
-
-       for (auto i: sorted) {
-               if (!subtitle ||
-                   (last_in != i->in() ||
-                    last_out != i->out() ||
-                    last_fade_up_time != i->fade_up_time() ||
-                    last_fade_down_time != i->fade_down_time())
-                       ) {
-
-                       subtitle = make_shared<order::Subtitle>(root, i->in(), i->out(), i->fade_up_time(), i->fade_down_time());
-                       root->children.push_back (subtitle);
-
-                       last_in = i->in ();
-                       last_out = i->out ();
-                       last_fade_up_time = i->fade_up_time ();
-                       last_fade_down_time = i->fade_down_time ();
-                       text.reset ();
-               }
-
-               auto is = dynamic_pointer_cast<SubtitleString>(i);
-               if (is) {
-                       if (!text ||
-                           last_h_align != is->h_align() ||
-                           fabs(last_h_position - is->h_position()) > ALIGN_EPSILON ||
-                           last_v_align != is->v_align() ||
-                           fabs(last_v_position - is->v_position()) > ALIGN_EPSILON ||
-                           fabs(last_z_position - is->z_position()) > ALIGN_EPSILON ||
-                           last_direction != is->direction()
-                               ) {
-                               text = make_shared<order::Text>(
-                                       subtitle,
-                                       is->h_align(),
-                                       is->h_position(),
-                                       is->v_align(),
-                                       is->v_position(),
-                                       is->z_position(),
-                                       is->direction(),
-                                       is->rubies()
-                                       );
-                               subtitle->children.push_back (text);
-
-                               last_h_align = is->h_align ();
-                               last_h_position = is->h_position ();
-                               last_v_align = is->v_align ();
-                               last_v_position = is->v_position ();
-                               last_z_position = is->z_position();
-                               last_direction = is->direction ();
-                       }
-
-                       text->children.push_back (make_shared<order::String>(text, order::Font (is, standard), is->text(), is->space_before()));
-               }
-
-               auto ii = dynamic_pointer_cast<SubtitleImage>(i);
-               if (ii) {
-                       text.reset ();
-                       subtitle->children.push_back (
-                               make_shared<order::Image>(subtitle, ii->id(), ii->png_image(), ii->h_align(), ii->h_position(), ii->v_align(), ii->v_position(), ii->z_position())
-                               );
-               }
-       }
-
-       /* Pull font changes as high up the hierarchy as we can */
-
-       pull_fonts (root);
-
-       /* Write XML */
-
-       order::Context context;
-       context.time_code_rate = time_code_rate;
-       context.standard = standard;
-       context.spot_number = 1;
-
-       root->write_xml (xml_root, context);
-}
-
-
-map<string, ArrayData>
-SubtitleAsset::font_data () const
-{
-       map<string, ArrayData> out;
-       for (auto const& i: _fonts) {
-               out[i.load_id] = i.data;
-       }
-       return out;
-}
-
-
-map<string, boost::filesystem::path>
-SubtitleAsset::font_filenames () const
-{
-       map<string, boost::filesystem::path> out;
-       for (auto const& i: _fonts) {
-               if (i.file) {
-                       out[i.load_id] = *i.file;
-               }
-       }
-       return out;
-}
-
-
-/** Replace empty IDs in any <LoadFontId> and <Font> tags with
- *  a dummy string.  Some systems give errors with empty font IDs
- *  (see DCP-o-matic bug #1689).
- */
-void
-SubtitleAsset::fix_empty_font_ids ()
-{
-       bool have_empty = false;
-       vector<string> ids;
-       for (auto i: load_font_nodes()) {
-               if (i->id == "") {
-                       have_empty = true;
-               } else {
-                       ids.push_back (i->id);
-               }
-       }
-
-       if (!have_empty) {
-               return;
-       }
-
-       string const empty_id = unique_string (ids, "font");
-
-       for (auto i: load_font_nodes()) {
-               if (i->id == "") {
-                       i->id = empty_id;
-               }
-       }
-
-       for (auto i: _subtitles) {
-               auto j = dynamic_pointer_cast<SubtitleString> (i);
-               if (j && j->font() && j->font().get() == "") {
-                       j->set_font (empty_id);
-               }
-       }
-}
-
-
-namespace {
-
-struct State
-{
-       int indent;
-       string xml;
-       int disable_formatting;
-};
-
-}
-
-
-static
-void
-format_xml_node (xmlpp::Node const* node, State& state)
-{
-       if (auto text_node = dynamic_cast<const xmlpp::TextNode*>(node)) {
-               string content = text_node->get_content();
-               boost::replace_all(content, "&", "&amp;");
-               boost::replace_all(content, "<", "&lt;");
-               boost::replace_all(content, ">", "&gt;");
-               state.xml += content;
-       } else if (auto element = dynamic_cast<const xmlpp::Element*>(node)) {
-               ++state.indent;
-
-               auto children = element->get_children();
-               auto const should_disable_formatting =
-                       std::any_of(
-                               children.begin(), children.end(),
-                               [](xmlpp::Node const* node) { return static_cast<bool>(dynamic_cast<const xmlpp::ContentNode*>(node)); }
-                               ) || element->get_name() == "Text";
-
-               if (!state.disable_formatting) {
-                       state.xml += "\n" + string(state.indent * 2, ' ');
-               }
-
-               state.xml += "<" + element->get_name();
-
-               for (auto attribute: element->get_attributes()) {
-                       state.xml += String::compose(" %1=\"%2\"", attribute->get_name().raw(), attribute->get_value().raw());
-               }
-
-               if (children.empty()) {
-                       state.xml += "/>";
-               } else {
-                       state.xml += ">";
-
-                       if (should_disable_formatting) {
-                               ++state.disable_formatting;
-                       }
-
-                       for (auto child: children) {
-                               format_xml_node(child, state);
-                       }
-
-                       if (!state.disable_formatting) {
-                               state.xml += "\n" + string(state.indent * 2, ' ');
-                       }
-
-                       state.xml += String::compose("</%1>", element->get_name().raw());
-
-                       if (should_disable_formatting) {
-                               --state.disable_formatting;
-                       }
-               }
-
-               --state.indent;
-       }
-}
-
-
-/** Format XML much as write_to_string_formatted() would do, except without adding any white space
- *  to <Text> nodes.  This is an attempt to avoid changing what is actually displayed as subtitles
- *  while also formatting the XML in such a way as to avoid DoM bug 2205.
- *
- *  xml_namespace is an optional namespace for the root node; it would be nicer to set this up with
- *  set_namespace_declaration in the caller and then to extract it here but I couldn't find a way
- *  to get all namespaces with the libxml++ API.
- */
-string
-SubtitleAsset::format_xml(xmlpp::Document const& document, optional<pair<string, string>> xml_namespace)
-{
-       auto root = document.get_root_node();
-
-       State state = {};
-       state.xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<" + root->get_name();
-
-       if (xml_namespace) {
-               if (xml_namespace->first.empty()) {
-                       state.xml += String::compose(" xmlns=\"%1\"", xml_namespace->second);
-               } else {
-                       state.xml += String::compose(" xmlns:%1=\"%2\"", xml_namespace->first, xml_namespace->second);
-               }
-       }
-
-       for (auto attribute: root->get_attributes()) {
-               state.xml += String::compose(" %1=\"%2\"", attribute->get_name().raw(), attribute->get_value().raw());
-       }
-
-       state.xml += ">";
-
-       for (auto child: document.get_root_node()->get_children()) {
-               format_xml_node(child, state);
-       }
-
-       state.xml += String::compose("\n</%1>\n", root->get_name().raw());
-
-       return state.xml;
-}
-
-
-void
-SubtitleAsset::ensure_font(string load_id, dcp::ArrayData data)
-{
-       if (std::find_if(_fonts.begin(), _fonts.end(), [load_id](Font const& font) { return font.load_id == load_id; }) == _fonts.end()) {
-               add_font(load_id, data);
-       }
-}
-
diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h
deleted file mode 100644 (file)
index 25758c2..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle_asset.h
- *  @brief SubtitleAsset class
- */
-
-
-#ifndef LIBDCP_SUBTITLE_ASSET_H
-#define LIBDCP_SUBTITLE_ASSET_H
-
-
-#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>
-#include <map>
-#include <string>
-#include <utility>
-#include <vector>
-
-
-namespace xmlpp {
-       class Document;
-       class Element;
-}
-
-
-struct interop_dcp_font_test;
-struct smpte_dcp_font_test;
-struct pull_fonts_test1;
-struct pull_fonts_test2;
-struct pull_fonts_test3;
-
-
-namespace dcp {
-
-
-class SubtitleString;
-class SubtitleImage;
-class FontNode;
-class TextNode;
-class SubtitleNode;
-class LoadFontNode;
-class ReelAsset;
-
-
-namespace order {
-       class Part;
-       struct Context;
-}
-
-
-/** @class SubtitleAsset
- *  @brief A parent for classes representing a file containing subtitles
- *
- *  This class holds a list of Subtitle objects which it can extract
- *  from the appropriate part of either an Interop or SMPTE XML file.
- *  Its subclasses InteropSubtitleAsset and SMPTESubtitleAsset handle the
- *  differences between the two types.
- */
-class SubtitleAsset : public Asset
-{
-public:
-       SubtitleAsset ();
-       explicit SubtitleAsset (boost::filesystem::path file);
-
-       bool equals (
-               std::shared_ptr<const Asset>,
-               EqualityOptions const&,
-               NoteHandler note
-               ) const override;
-
-       std::vector<std::shared_ptr<const Subtitle>> subtitles_during (Time from, Time to, bool starting) const;
-       std::vector<std::shared_ptr<const Subtitle>> subtitles () const;
-
-       virtual void add (std::shared_ptr<Subtitle>);
-       virtual void add_font (std::string id, dcp::ArrayData data) = 0;
-       void ensure_font(std::string id, dcp::ArrayData data);
-       std::map<std::string, ArrayData> font_data () const;
-       std::map<std::string, boost::filesystem::path> font_filenames () const;
-
-       virtual void write (boost::filesystem::path) const = 0;
-       virtual std::string xml_as_string () const = 0;
-
-       Time latest_subtitle_out () const;
-
-       void fix_empty_font_ids ();
-
-       virtual std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const = 0;
-
-       virtual int time_code_rate () const = 0;
-
-       /** @return Raw XML loaded from, or written to, an on-disk asset, or boost::none if
-        *  - this object was not created from an existing on-disk asset and has not been written to one, or
-        *  - this asset is encrypted and no key is available.
-        */
-       virtual boost::optional<std::string> raw_xml () const {
-               return _raw_xml;
-       }
-
-       virtual SubtitleStandard subtitle_standard() const = 0;
-
-       static std::string format_xml(xmlpp::Document const& document, boost::optional<std::pair<std::string, std::string>> xml_namespace);
-
-protected:
-       friend struct ::interop_dcp_font_test;
-       friend struct ::smpte_dcp_font_test;
-
-       struct ParseState {
-               boost::optional<std::string> font_id;
-               boost::optional<int64_t> size;
-               boost::optional<float> aspect_adjust;
-               boost::optional<bool> italic;
-               boost::optional<bool> bold;
-               boost::optional<bool> underline;
-               boost::optional<Colour> colour;
-               boost::optional<Effect> effect;
-               boost::optional<Colour> effect_colour;
-               boost::optional<float> h_position;
-               boost::optional<HAlign> h_align;
-               boost::optional<float> v_position;
-               boost::optional<VAlign> v_align;
-               boost::optional<float> z_position;
-               boost::optional<Direction> direction;
-               boost::optional<Time> in;
-               boost::optional<Time> out;
-               boost::optional<Time> fade_up_time;
-               boost::optional<Time> fade_down_time;
-               enum class Type {
-                       TEXT,
-                       IMAGE
-               };
-               boost::optional<Type> type;
-               float space_before = 0;
-       };
-
-       void parse_subtitles (xmlpp::Element const * node, std::vector<ParseState>& state, boost::optional<int> tcr, Standard standard);
-       ParseState font_node_state (xmlpp::Element const * node, Standard standard) const;
-       ParseState text_node_state (xmlpp::Element const * node) const;
-       ParseState image_node_state (xmlpp::Element const * node) const;
-       ParseState subtitle_node_state (xmlpp::Element const * node, boost::optional<int> tcr) const;
-       Time fade_time (xmlpp::Element const * node, std::string name, boost::optional<int> tcr) const;
-       void position_align (ParseState& ps, xmlpp::Element const * node) const;
-
-       void subtitles_as_xml (xmlpp::Element* root, int time_code_rate, Standard standard) const;
-
-       /** All our subtitles, in no particular order */
-       std::vector<std::shared_ptr<Subtitle>> _subtitles;
-
-       class Font
-       {
-       public:
-               Font (std::string load_id_, std::string uuid_, boost::filesystem::path file_)
-                       : load_id (load_id_)
-                       , uuid (uuid_)
-                       , data (file_)
-                       , file (file_)
-               {}
-
-               Font (std::string load_id_, std::string uuid_, ArrayData data_)
-                       : load_id (load_id_)
-                       , uuid (uuid_)
-                       , data (data_)
-               {}
-
-               std::string load_id;
-               std::string uuid;
-               ArrayData data;
-               /** .ttf file that this data was last written to, if applicable */
-               mutable boost::optional<boost::filesystem::path> file;
-       };
-
-       /** TTF font data that we need */
-       std::vector<Font> _fonts;
-
-       /** The raw XML data that we read from or wrote to our asset; useful for validation */
-       mutable boost::optional<std::string> _raw_xml;
-
-private:
-       friend struct ::pull_fonts_test1;
-       friend struct ::pull_fonts_test2;
-       friend struct ::pull_fonts_test3;
-
-       void maybe_add_subtitle(
-               std::string text,
-               std::vector<ParseState> const & parse_state,
-               float space_before,
-               Standard standard,
-               std::vector<Ruby> const& rubies
-               );
-
-       static void pull_fonts (std::shared_ptr<order::Part> part);
-};
-
-
-}
-
-
-#endif
diff --git a/src/subtitle_asset_internal.cc b/src/subtitle_asset_internal.cc
deleted file mode 100644 (file)
index 99d8411..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle_asset_internal.cc
- *  @brief Internal SubtitleAsset helpers
- */
-
-
-#include "subtitle_asset_internal.h"
-#include "subtitle_string.h"
-#include "compose.hpp"
-#include <cmath>
-
-
-using std::string;
-using std::map;
-using std::shared_ptr;
-using namespace dcp;
-
-
-order::Font::Font (shared_ptr<SubtitleString> s, Standard standard)
-{
-       if (s->font()) {
-               if (standard == Standard::SMPTE) {
-                       _values["ID"] = s->font().get ();
-               } else {
-                       _values["Id"] = s->font().get ();
-               }
-       }
-       _values["Italic"] = s->italic() ? "yes" : "no";
-       _values["Color"] = s->colour().to_argb_string();
-       _values["Size"] = raw_convert<string> (s->size());
-       _values["AspectAdjust"] = raw_convert<string>(s->aspect_adjust(), 1, true);
-       _values["Effect"] = effect_to_string (s->effect());
-       _values["EffectColor"] = s->effect_colour().to_argb_string();
-       _values["Script"] = "normal";
-       if (standard == Standard::SMPTE) {
-               _values["Underline"] = s->underline() ? "yes" : "no";
-       } else {
-               _values["Underlined"] = s->underline() ? "yes" : "no";
-       }
-       _values["Weight"] = s->bold() ? "bold" : "normal";
-}
-
-
-xmlpp::Element*
-order::Font::as_xml (xmlpp::Element* parent, Context&) const
-{
-       auto e = parent->add_child("Font");
-       for (const auto& i: _values) {
-               e->set_attribute (i.first, i.second);
-       }
-       return e;
-}
-
-
-/** Modify our values so that they contain only those that are common to us and
- *  other.
- */
-void
-order::Font::take_intersection (Font other)
-{
-       map<string, string> inter;
-
-       for (auto const& i: other._values) {
-               auto t = _values.find (i.first);
-               if (t != _values.end() && t->second == i.second) {
-                       inter.insert (i);
-               }
-       }
-
-       _values = inter;
-}
-
-
-/** Modify our values so that it contains only those keys that are not in other */
-void
-order::Font::take_difference (Font other)
-{
-       map<string, string> diff;
-       for (auto const& i: _values) {
-               if (other._values.find (i.first) == other._values.end()) {
-                       diff.insert (i);
-               }
-       }
-
-       _values = diff;
-}
-
-
-bool
-order::Font::empty () const
-{
-       return _values.empty ();
-}
-
-
-xmlpp::Element*
-order::Part::as_xml (xmlpp::Element* parent, Context &) const
-{
-       return parent;
-}
-
-
-xmlpp::Element*
-order::String::as_xml (xmlpp::Element* parent, Context& context) const
-{
-       if (fabs(_space_before) > SPACE_BEFORE_EPSILON) {
-               auto space = parent->add_child("Space");
-               auto size = raw_convert<string>(_space_before, 2);
-               if (context.standard == Standard::INTEROP) {
-                       size += "em";
-               }
-               space->set_attribute("Size", size);
-       }
-       parent->add_child_text (_text);
-       return 0;
-}
-
-
-void
-order::Part::write_xml (xmlpp::Element* parent, order::Context& context) const
-{
-       if (!font.empty ()) {
-               parent = font.as_xml (parent, context);
-       }
-
-       parent = as_xml (parent, context);
-
-       for (auto i: children) {
-               i->write_xml (parent, context);
-       }
-}
-
-
-static void
-position_align (xmlpp::Element* e, order::Context& context, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position)
-{
-       if (h_align != HAlign::CENTER) {
-               if (context.standard == Standard::SMPTE) {
-                       e->set_attribute ("Halign", halign_to_string (h_align));
-               } else {
-                       e->set_attribute ("HAlign", halign_to_string (h_align));
-               }
-       }
-
-       if (fabs(h_position) > ALIGN_EPSILON) {
-               if (context.standard == Standard::SMPTE) {
-                       e->set_attribute ("Hposition", raw_convert<string> (h_position * 100, 6));
-               } else {
-                       e->set_attribute ("HPosition", raw_convert<string> (h_position * 100, 6));
-               }
-       }
-
-       if (context.standard == Standard::SMPTE) {
-               e->set_attribute ("Valign", valign_to_string (v_align));
-       } else {
-               e->set_attribute ("VAlign", valign_to_string (v_align));
-       }
-
-       if (fabs(v_position) > ALIGN_EPSILON) {
-               if (context.standard == Standard::SMPTE) {
-                       e->set_attribute ("Vposition", raw_convert<string> (v_position * 100, 6));
-               } else {
-                       e->set_attribute ("VPosition", raw_convert<string> (v_position * 100, 6));
-               }
-       } else {
-               if (context.standard == Standard::SMPTE) {
-                       e->set_attribute ("Vposition", "0");
-               } else {
-                       e->set_attribute ("VPosition", "0");
-               }
-       }
-
-       if (fabs(z_position) > ALIGN_EPSILON && context.standard == Standard::SMPTE) {
-               e->set_attribute("Zposition", raw_convert<string>(z_position * 100, 6));
-       }
-}
-
-
-xmlpp::Element*
-order::Text::as_xml (xmlpp::Element* parent, Context& context) const
-{
-       auto e = parent->add_child ("Text");
-
-       position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position);
-
-       /* Interop only supports "horizontal" or "vertical" for direction, so only write this
-          for SMPTE.
-       */
-       if (_direction != Direction::LTR && context.standard == Standard::SMPTE) {
-               e->set_attribute ("Direction", direction_to_string (_direction));
-       }
-
-       for (auto const& ruby: _rubies) {
-               auto xml = e->add_child("Ruby");
-               xml->add_child("Rb")->add_child_text(ruby.base);
-               auto rt = xml->add_child("Rt");
-               rt->add_child_text(ruby.annotation);
-               rt->set_attribute("Size", dcp::raw_convert<string>(ruby.size, 6));
-               rt->set_attribute("Position", ruby.position == RubyPosition::BEFORE ? "before" : "after");
-               rt->set_attribute("Offset", dcp::raw_convert<string>(ruby.offset, 6));
-               rt->set_attribute("Spacing", dcp::raw_convert<string>(ruby.spacing, 6));
-               rt->set_attribute("AspectAdjust", dcp::raw_convert<string>(ruby.aspect_adjust, 6));
-       }
-
-       return e;
-}
-
-
-xmlpp::Element*
-order::Subtitle::as_xml (xmlpp::Element* parent, Context& context) const
-{
-       auto e = parent->add_child ("Subtitle");
-       e->set_attribute ("SpotNumber", raw_convert<string> (context.spot_number++));
-       e->set_attribute ("TimeIn", _in.rebase(context.time_code_rate).as_string(context.standard));
-       e->set_attribute ("TimeOut", _out.rebase(context.time_code_rate).as_string(context.standard));
-       if (context.standard == Standard::SMPTE) {
-               e->set_attribute ("FadeUpTime", _fade_up.rebase(context.time_code_rate).as_string(context.standard));
-               e->set_attribute ("FadeDownTime", _fade_down.rebase(context.time_code_rate).as_string(context.standard));
-       } else {
-               e->set_attribute ("FadeUpTime", raw_convert<string> (_fade_up.as_editable_units_ceil(context.time_code_rate)));
-               e->set_attribute ("FadeDownTime", raw_convert<string> (_fade_down.as_editable_units_ceil(context.time_code_rate)));
-       }
-       return e;
-}
-
-
-bool
-order::Font::operator== (Font const & other) const
-{
-       return _values == other._values;
-}
-
-
-void
-order::Font::clear ()
-{
-       _values.clear ();
-}
-
-
-xmlpp::Element *
-order::Image::as_xml (xmlpp::Element* parent, Context& context) const
-{
-       auto e = parent->add_child ("Image");
-
-       position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position);
-       if (context.standard == Standard::SMPTE) {
-               e->add_child_text ("urn:uuid:" + _id);
-       } else {
-               e->add_child_text (_id + ".png");
-       }
-
-       return e;
-}
diff --git a/src/subtitle_asset_internal.h b/src/subtitle_asset_internal.h
deleted file mode 100644 (file)
index 557db2e..0000000
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle_asset_internal.h
- *  @brief Internal SubtitleAsset helpers
- */
-
-
-#ifndef LIBDCP_SUBTITLE_ASSET_INTERNAL_H
-#define LIBDCP_SUBTITLE_ASSET_INTERNAL_H
-
-
-#include "array_data.h"
-#include "dcp_time.h"
-#include "h_align.h"
-#include "raw_convert.h"
-#include "v_align.h"
-#include "warnings.h"
-LIBDCP_DISABLE_WARNINGS
-#include <libxml++/libxml++.h>
-LIBDCP_ENABLE_WARNINGS
-
-
-struct take_intersection_test;
-struct take_difference_test;
-struct pull_fonts_test1;
-struct pull_fonts_test2;
-struct pull_fonts_test3;
-
-
-namespace dcp {
-
-
-class Ruby;
-class SubtitleString;
-
-
-namespace order {
-
-
-struct Context
-{
-       int time_code_rate;
-       Standard standard;
-       int spot_number;
-};
-
-
-class Font
-{
-public:
-       Font () {}
-
-       Font (std::shared_ptr<SubtitleString> s, Standard standard);
-
-       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const;
-
-       void take_intersection (Font other);
-       void take_difference (Font other);
-       bool empty () const;
-       void clear ();
-       bool operator== (Font const & other) const;
-
-private:
-       friend struct ::take_intersection_test;
-       friend struct ::take_difference_test;
-       friend struct ::pull_fonts_test1;
-       friend struct ::pull_fonts_test2;
-       friend struct ::pull_fonts_test3;
-
-       std::map<std::string, std::string> _values;
-};
-
-
-class Part
-{
-public:
-       Part (std::shared_ptr<Part> parent_)
-               : parent (parent_)
-       {}
-
-       Part (std::shared_ptr<Part> parent_, Font font_)
-               : parent (parent_)
-               , font (font_)
-       {}
-
-       virtual ~Part () {}
-
-       virtual xmlpp::Element* as_xml (xmlpp::Element* parent, Context &) const;
-       void write_xml (xmlpp::Element* parent, order::Context& context) const;
-
-       std::shared_ptr<Part> parent;
-       Font font;
-       std::vector<std::shared_ptr<Part>> children;
-};
-
-
-class String : public Part
-{
-public:
-       String (std::shared_ptr<Part> parent, Font font, std::string text, float space_before)
-               : Part (parent, font)
-               , _text (text)
-               , _space_before (space_before)
-       {}
-
-       virtual xmlpp::Element* as_xml (xmlpp::Element* parent, Context &) const override;
-
-private:
-       std::string _text;
-       float _space_before;
-};
-
-
-class Text : public Part
-{
-public:
-       Text(std::shared_ptr<Part> parent, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position, Direction direction, std::vector<Ruby> rubies)
-               : Part (parent)
-               , _h_align (h_align)
-               , _h_position (h_position)
-               , _v_align (v_align)
-               , _v_position (v_position)
-               , _z_position(z_position)
-               , _direction (direction)
-               , _rubies(rubies)
-       {}
-
-       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
-
-private:
-       HAlign _h_align;
-       float _h_position;
-       VAlign _v_align;
-       float _v_position;
-       float _z_position;
-       Direction _direction;
-       std::vector<Ruby> _rubies;
-};
-
-
-class Subtitle : public Part
-{
-public:
-       Subtitle (std::shared_ptr<Part> parent, Time in, Time out, Time fade_up, Time fade_down)
-               : Part (parent)
-               , _in (in)
-               , _out (out)
-               , _fade_up (fade_up)
-               , _fade_down (fade_down)
-       {}
-
-       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
-
-private:
-       Time _in;
-       Time _out;
-       Time _fade_up;
-       Time _fade_down;
-};
-
-
-class Image : public Part
-{
-public:
-       Image (std::shared_ptr<Part> parent, std::string id, ArrayData png_data, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position)
-               : Part (parent)
-               , _png_data (png_data)
-               , _id (id)
-               , _h_align (h_align)
-               , _h_position (h_position)
-               , _v_align (v_align)
-               , _v_position (v_position)
-               , _z_position(z_position)
-       {}
-
-       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
-
-private:
-       ArrayData _png_data;
-       std::string _id; ///< the ID of this image
-       HAlign _h_align;
-       float _h_position;
-       VAlign _v_align;
-       float _v_position;
-       float _z_position;
-};
-
-
-}
-}
-
-
-#endif
index 9340bc54ad8075ef6b2efd3cf750bf0cd99b9d56..df9e117806bd1ece2c5ced20d1b4b5cb6313cac6 100644 (file)
@@ -62,7 +62,7 @@ SubtitleImage::SubtitleImage (
        Time fade_up_time,
        Time fade_down_time
        )
-       : Subtitle(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
+       : Text(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
        , _png_image (png_image)
        , _id (make_uuid ())
 {
@@ -83,7 +83,7 @@ SubtitleImage::SubtitleImage (
        Time fade_up_time,
        Time fade_down_time
        )
-       : Subtitle(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
+       : Text(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
        , _png_image (png_image)
        , _id (id)
 {
@@ -134,9 +134,9 @@ dcp::operator!= (SubtitleImage const & a, SubtitleImage const & b)
 
 
 bool
-SubtitleImage::equals(shared_ptr<const Subtitle> other_sub, EqualityOptions const& options, NoteHandler note) const
+SubtitleImage::equals(shared_ptr<const Text> other_sub, EqualityOptions const& options, NoteHandler note) const
 {
-       if (!Subtitle::equals(other_sub, options, note)) {
+       if (!Text::equals(other_sub, options, note)) {
                return false;
        }
 
index ae733fe499e1ac2c4a4d7ea9d39f597388783fe3..af2ff37b966b8a0a6e7c8b2eceb1913722ee948a 100644 (file)
@@ -42,8 +42,8 @@
 
 
 #include "array_data.h"
-#include "subtitle.h"
 #include "dcp_time.h"
+#include "text.h"
 #include <boost/optional.hpp>
 #include <string>
 
@@ -54,7 +54,7 @@ namespace dcp {
 /** @class SubtitleImage
  *  @brief A bitmap subtitle with all the associated attributes
  */
-class SubtitleImage : public Subtitle
+class SubtitleImage : public Text
 {
 public:
        SubtitleImage (
@@ -104,7 +104,7 @@ public:
                return _file;
        }
 
-       bool equals(std::shared_ptr<const dcp::Subtitle> other_sub, EqualityOptions const& options, NoteHandler note) const override;
+       bool equals(std::shared_ptr<const dcp::Text> other_sub, EqualityOptions const& options, NoteHandler note) const override;
 
 private:
        ArrayData _png_image;
diff --git a/src/subtitle_standard.cc b/src/subtitle_standard.cc
deleted file mode 100644 (file)
index 101f84d..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-    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
deleted file mode 100644 (file)
index 95972e2..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-    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
-
diff --git a/src/subtitle_string.cc b/src/subtitle_string.cc
deleted file mode 100644 (file)
index af61d92..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle_string.cc
- *  @brief SubtitleString class
- */
-
-
-#include "compose.hpp"
-#include "subtitle_string.h"
-#include "xml.h"
-#include <cmath>
-
-
-using std::dynamic_pointer_cast;
-using std::max;
-using std::min;
-using std::ostream;
-using std::shared_ptr;
-using std::string;
-using std::vector;
-using boost::optional;
-using namespace dcp;
-
-
-SubtitleString::SubtitleString (
-       optional<string> font,
-       bool italic,
-       bool bold,
-       bool underline,
-       Colour colour,
-       int size,
-       float aspect_adjust,
-       Time in,
-       Time out,
-       float h_position,
-       HAlign h_align,
-       float v_position,
-       VAlign v_align,
-       float z_position,
-       Direction direction,
-       string text,
-       Effect effect,
-       Colour effect_colour,
-       Time fade_up_time,
-       Time fade_down_time,
-       float space_before,
-       vector<Ruby> rubies
-       )
-       : Subtitle(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
-       , _font (font)
-       , _italic (italic)
-       , _bold (bold)
-       , _underline (underline)
-       , _colour (colour)
-       , _size (size)
-       , _aspect_adjust (aspect_adjust)
-       , _direction (direction)
-       , _text (text)
-       , _effect (effect)
-       , _effect_colour (effect_colour)
-       , _space_before (space_before)
-       , _rubies(rubies)
-{
-       _aspect_adjust = max(min(_aspect_adjust, 4.0f), 0.25f);
-}
-
-
-float
-SubtitleString::size_in_pixels (int screen_height) const
-{
-       /* Size in the subtitle file is given in points as if the screen
-          height is 11 inches, so a 72pt font would be 1/11th of the screen
-          height.
-       */
-
-       return _size * static_cast<float>(screen_height) / (11.0f * 72.0f);
-}
-
-
-bool
-dcp::operator== (SubtitleString const & a, SubtitleString const & b)
-{
-       return (
-               a.font() == b.font() &&
-               a.italic() == b.italic() &&
-               a.bold() == b.bold() &&
-               a.underline() == b.underline() &&
-               a.colour() == b.colour() &&
-               a.size() == b.size() &&
-               fabs (a.aspect_adjust() - b.aspect_adjust()) < ASPECT_ADJUST_EPSILON &&
-               a.in() == b.in() &&
-               a.out() == b.out() &&
-               a.h_position() == b.h_position() &&
-               a.h_align() == b.h_align() &&
-               a.v_position() == b.v_position() &&
-               a.v_align() == b.v_align() &&
-               a.z_position() == b.z_position() &&
-               a.direction() == b.direction() &&
-               a.text() == b.text() &&
-               a.effect() == b.effect() &&
-               a.effect_colour() == b.effect_colour() &&
-               a.fade_up_time() == b.fade_up_time() &&
-               a.fade_down_time() == b.fade_down_time() &&
-               fabs (a.space_before() - b.space_before()) < SPACE_BEFORE_EPSILON &&
-               a.rubies() == b.rubies()
-               );
-}
-
-
-bool
-dcp::operator!= (SubtitleString const & a, SubtitleString const & b)
-{
-       return !(a == b);
-}
-
-
-ostream&
-dcp::operator<< (ostream& s, SubtitleString const & sub)
-{
-       s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n"
-         << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n"
-         << "font " << sub.font().get_value_or ("[default]") << ", ";
-
-       if (sub.italic()) {
-               s << "italic, ";
-       } else {
-               s << "non-italic, ";
-       }
-
-       if (sub.bold()) {
-               s << "bold, ";
-       } else {
-               s << "normal, ";
-       }
-
-       if (sub.underline()) {
-               s << "underlined, ";
-       }
-
-       s << "size " << sub.size() << ", aspect " << sub.aspect_adjust()
-         << ", colour (" << sub.colour().r << ", " << sub.colour().g << ", " << sub.colour().b << ")"
-         << ", vpos " << sub.v_position() << ", valign " << ((int) sub.v_align())
-         << ", hpos " << sub.h_position() << ", halign " << ((int) sub.h_align())
-         << ", zpos " << sub.z_position()
-         << ", direction " << ((int) sub.direction())
-         << ", effect " << ((int) sub.effect())
-         << ", effect colour (" << sub.effect_colour().r << ", " << sub.effect_colour().g << ", " << sub.effect_colour().b << ")"
-         << ", space before " << sub.space_before();
-
-       for (auto ruby: sub.rubies()) {
-               s << ", ruby " << ruby.base << " " << ruby.annotation;
-       }
-
-       return s;
-}
-
-
-bool
-SubtitleString::equals(shared_ptr<const Subtitle> other_sub, EqualityOptions const& options, NoteHandler note) const
-{
-       if (!Subtitle::equals(other_sub, options, note)) {
-               return false;
-       }
-
-       auto other = dynamic_pointer_cast<const SubtitleString>(other_sub);
-       if (!other) {
-               note(NoteType::ERROR, "Subtitle types differ: string vs image");
-               return false;
-       }
-
-       bool same = true;
-
-       if (_font != other->_font) {
-               note(NoteType::ERROR, String::compose("subtitle font differs: %1 vs %2", _font.get_value_or("[none]"), other->_font.get_value_or("[none]")));
-               same = false;
-       }
-
-       if (_italic != other->_italic) {
-               note(NoteType::ERROR, String::compose("subtitle italic flag differs: %1 vs %2", _italic ? "true" : "false", other->_italic ? "true" : "false"));
-               same = false;
-       }
-
-       if (_bold != other->_bold) {
-               note(NoteType::ERROR, String::compose("subtitle bold flag differs: %1 vs %2", _bold ? "true" : "false", other->_bold ? "true" : "false"));
-               same = false;
-       }
-
-       if (_underline != other->_underline) {
-               note(NoteType::ERROR, String::compose("subtitle underline flag differs: %1 vs %2", _underline ? "true" : "false", other->_underline ? "true" : "false"));
-               same = false;
-       }
-
-       if (_colour != other->_colour) {
-               note(NoteType::ERROR, String::compose("subtitle colour differs: %1 vs %2", _colour.to_rgb_string(), other->_colour.to_rgb_string()));
-               same = false;
-       }
-
-       if (_size != other->_size) {
-               note(NoteType::ERROR, String::compose("subtitle size differs: %1 vs %2", _size, other->_size));
-               same = false;
-       }
-
-       if (_aspect_adjust != other->_aspect_adjust) {
-               note(NoteType::ERROR, String::compose("subtitle aspect_adjust differs: %1 vs %2", _aspect_adjust, other->_aspect_adjust));
-               same = false;
-       }
-
-       if (_direction != other->_direction) {
-               note(NoteType::ERROR, String::compose("subtitle direction differs: %1 vs %2", direction_to_string(_direction), direction_to_string(other->_direction)));
-               same = false;
-       }
-
-       if (_text != other->_text) {
-               note(NoteType::ERROR, String::compose("subtitle text differs: %1 vs %2", _text, other->_text));
-               same = false;
-       }
-
-       if (_effect != other->_effect) {
-               note(NoteType::ERROR, String::compose("subtitle effect differs: %1 vs %2", effect_to_string(_effect), effect_to_string(other->_effect)));
-               same = false;
-       }
-
-       if (_effect_colour != other->_effect_colour) {
-               note(NoteType::ERROR, String::compose("subtitle effect colour differs: %1 vs %2", _effect_colour.to_rgb_string(), other->_effect_colour.to_rgb_string()));
-               same = false;
-       }
-
-       if (_space_before != other->_space_before) {
-               note(NoteType::ERROR, String::compose("subtitle space before differs: %1 vs %2", _space_before, other->_space_before));
-               same = false;
-       }
-
-       if (_rubies != other->_rubies) {
-               note(NoteType::ERROR, "rubies differ");
-               same = false;
-       }
-
-       return same;
-}
-
diff --git a/src/subtitle_string.h b/src/subtitle_string.h
deleted file mode 100644 (file)
index 1ef57ff..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
-    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.
-*/
-
-
-/** @file  src/subtitle_string.h
- *  @brief SubtitleString class
- */
-
-
-#ifndef LIBDCP_SUBTITLE_STRING_H
-#define LIBDCP_SUBTITLE_STRING_H
-
-
-#include "dcp_time.h"
-#include "ruby.h"
-#include "subtitle.h"
-#include <boost/optional.hpp>
-#include <string>
-
-
-namespace dcp {
-
-
-/** @class SubtitleString
- *  @brief A single line of subtitle text with all the associated attributes.
- */
-class SubtitleString : public Subtitle
-{
-public:
-       /** @param font Font ID, or empty to use the default
-        *  @param italic true for italic text
-        *  @param bold true for bold text
-        *  @param underline true for underlined text
-        *  @param colour Colour of the text
-        *  @param size Size in points as if the screen height is 11 inches, so a 72pt font would be 1/11th of the screen height
-        *  @param aspect_adjust greater than 1 to stretch text to be wider, less than 1 to shrink text to be narrower (must be between 0.25 and 4)
-        *  @param in start time
-        *  @param out finish time
-        *  @param h_position Horizontal position as a fraction of the screen width (between 0 and 1) from h_align
-        *  @param h_align Horizontal alignment point
-        *  @param v_position Vertical position as a fraction of the screen height (between 0 and 1) from v_align
-        *  @param v_align Vertical alignment point
-        *  @param z_position Z position as a proportion of the primary picture width between -1 and +1;
-        *  +ve moves the image away from the viewer, -ve moves it toward the viewer, 0 is in the plane of the screen.
-        *  @param direction Direction of text
-        *  @param text The text to display
-        *  @param effect Effect to use
-        *  @param effect_colour Colour of the effect
-        *  @param fade_up_time Time to fade the text in
-        *  @param fade_down_time Time to fade the text out
-        *  @param space_before Space to add before this string, in ems (could be negative to remove space).
-        */
-       SubtitleString (
-               boost::optional<std::string> font,
-               bool italic,
-               bool bold,
-               bool underline,
-               Colour colour,
-               int size,
-               float aspect_adjust,
-               Time in,
-               Time out,
-               float h_position,
-               HAlign h_align,
-               float v_position,
-               VAlign v_align,
-               float z_position,
-               Direction direction,
-               std::string text,
-               Effect effect,
-               Colour effect_colour,
-               Time fade_up_time,
-               Time fade_down_time,
-               float space_before,
-               std::vector<Ruby> rubies
-               );
-
-       /** @return font ID */
-       boost::optional<std::string> font () const {
-               return _font;
-       }
-
-       bool italic () const {
-               return _italic;
-       }
-
-       bool bold () const {
-               return _bold;
-       }
-
-       bool underline () const {
-               return _underline;
-       }
-
-       Colour colour () const {
-               return _colour;
-       }
-
-       std::string text () const {
-               return _text;
-       }
-
-       Direction direction () const {
-               return _direction;
-       }
-
-       Effect effect () const {
-               return _effect;
-       }
-
-       Colour effect_colour () const {
-               return _effect_colour;
-       }
-
-       int size () const {
-               return _size;
-       }
-
-       float size_in_pixels (int screen_height) const;
-
-       float space_before () const {
-               return _space_before;
-       }
-
-       /** @return Aspect ratio `adjustment' of the font size.
-        *  Values greater than 1 widen each character, values less than 1 narrow each character,
-        *  and the value must be between 0.25 and 4.
-        */
-       float aspect_adjust () const {
-               return _aspect_adjust;
-       }
-
-       std::vector<Ruby> const& rubies() const {
-               return _rubies;
-       }
-
-       void set_font (std::string id) {
-               _font = id;
-       }
-
-       void unset_font () {
-               _font = boost::optional<std::string>();
-       }
-
-       void set_size (int s) {
-               _size = s;
-       }
-
-       void set_aspect_adjust (float a) {
-               _aspect_adjust = a;
-       }
-
-       void set_text (std::string t) {
-               _text = t;
-       }
-
-       void set_colour (Colour c) {
-               _colour = c;
-       }
-
-       void set_effect (Effect e) {
-               _effect = e;
-       }
-
-       void set_effect_colour (Colour c) {
-               _effect_colour = c;
-       }
-
-       void set_rubies(std::vector<Ruby> rubies) {
-               _rubies = std::move(rubies);
-       }
-
-       bool equals(std::shared_ptr<const dcp::Subtitle> other_sub, EqualityOptions const& options, NoteHandler node) const override;
-
-private:
-       /** font ID */
-       boost::optional<std::string> _font;
-       /** true if the text is italic */
-       bool _italic;
-       /** true if the weight is bold, false for normal */
-       bool _bold;
-       /** true to enable underlining, false otherwise */
-       bool _underline;
-       /** text colour */
-       Colour _colour;
-       /** Size in points as if the screen height is 11 inches, so a 72pt font
-        *  would be 1/11th of the screen height.
-        */
-       int _size;
-       float _aspect_adjust;
-       Direction _direction;
-       std::string _text;
-       Effect _effect;
-       Colour _effect_colour;
-       float _space_before;
-       std::vector<Ruby> _rubies;
-};
-
-bool operator== (SubtitleString const & a, SubtitleString const & b);
-bool operator!= (SubtitleString const & a, SubtitleString const & b);
-std::ostream& operator<< (std::ostream& s, SubtitleString const & sub);
-
-
-}
-
-
-#endif
-
diff --git a/src/text.cc b/src/text.cc
new file mode 100644 (file)
index 0000000..2762bb6
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text.cc
+ *  @brief Text class
+ */
+
+
+#include "compose.hpp"
+#include "dcp_time.h"
+#include "equality_options.h"
+#include "text.h"
+
+
+using std::shared_ptr;
+using namespace dcp;
+
+
+/** @param v_position Vertical position as a fraction of the screen height (between 0 and 1) from v_align */
+Text::Text(
+       Time in,
+       Time out,
+       float h_position,
+       HAlign h_align,
+       float v_position,
+       VAlign v_align,
+       float z_position,
+       Time fade_up_time,
+       Time fade_down_time
+       )
+       : _in (in)
+       , _out (out)
+       , _h_position (h_position)
+       , _h_align (h_align)
+       , _v_position (v_position)
+       , _v_align (v_align)
+       , _z_position(z_position)
+       , _fade_up_time (fade_up_time)
+       , _fade_down_time (fade_down_time)
+{
+
+}
+
+
+bool
+Text::equals(shared_ptr<const Text> other, EqualityOptions const& options, NoteHandler note) const
+{
+       bool same = true;
+
+       if (in() != other->in()) {
+               note(NoteType::ERROR, "subtitle in times differ");
+               same = false;
+       }
+
+       if (out() != other->out()) {
+               note(NoteType::ERROR, "subtitle out times differ");
+               same = false;
+       }
+
+       if (h_position() != other->h_position()) {
+               note(NoteType::ERROR, "subtitle horizontal positions differ");
+               same = false;
+       }
+
+       if (h_align() != other->h_align()) {
+               note(NoteType::ERROR, "subtitle horizontal alignments differ");
+               same = false;
+       }
+
+       auto const vpos = std::abs(v_position() - other->v_position());
+       if (vpos > options.max_subtitle_vertical_position_error)  {
+               note(
+                       NoteType::ERROR,
+                       String::compose("subtitle vertical positions differ by %1 (more than the allowed difference of %2)", vpos, options.max_subtitle_vertical_position_error)
+                   );
+               same = false;
+       }
+
+       if (v_align() != other->v_align()) {
+               note(NoteType::ERROR, "subtitle vertical alignments differ");
+               same = false;
+       }
+
+       if (z_position() != other->z_position()) {
+               note(NoteType::ERROR, "subtitle Z positions differ");
+               same = false;
+       }
+
+       if (fade_up_time() != other->fade_up_time()) {
+               note(NoteType::ERROR, "subtitle fade-up times differ");
+               same = false;
+       }
+
+       if (fade_down_time() != other->fade_down_time()) {
+               note(NoteType::ERROR, "subtitle fade-down times differ");
+               same = false;
+       }
+
+       return same;
+}
diff --git a/src/text.h b/src/text.h
new file mode 100644 (file)
index 0000000..b8f1575
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text.h
+ *  @brief Text class
+ */
+
+
+#ifndef LIBDCP_TEXT_H
+#define LIBDCP_TEXT_H
+
+
+#include "dcp_time.h"
+#include "h_align.h"
+#include "v_align.h"
+
+
+namespace dcp {
+
+
+class EqualityOptions;
+
+
+class Text
+{
+public:
+       virtual ~Text() {}
+
+       /** @return text start time (relative to the start of the reel) */
+       Time in () const {
+               return _in;
+       }
+
+       /** @return text finish time (relative to the start of the reel) */
+       Time out () const {
+               return _out;
+       }
+
+       float h_position () const {
+               return _h_position;
+       }
+
+       HAlign h_align () const {
+               return _h_align;
+       }
+
+       /** @return vertical position as a proportion of the screen height from the
+        *  vertical alignment point.
+        *  (between 0 and 1)
+        */
+       float v_position () const {
+               return _v_position;
+       }
+
+       VAlign v_align () const {
+               return _v_align;
+       }
+
+       float z_position() const {
+               return _z_position;
+       }
+
+       Time fade_up_time () const {
+               return _fade_up_time;
+       }
+
+       Time fade_down_time () const {
+               return _fade_down_time;
+       }
+
+       void set_in (Time i) {
+               _in = i;
+       }
+
+       void set_out (Time o) {
+               _out = o;
+       }
+
+       void set_h_position (float p) {
+               _h_position = p;
+       }
+
+       /** @param p New vertical position as a proportion of the screen height
+        *  from the top (between 0 and 1)
+        */
+       void set_v_position (float p) {
+               _v_position = p;
+       }
+
+       void set_z_position(float z) {
+               _z_position = z;
+       }
+
+       void set_fade_up_time (Time t) {
+               _fade_up_time = t;
+       }
+
+       void set_fade_down_time (Time t) {
+               _fade_down_time = t;
+       }
+
+       virtual bool equals(std::shared_ptr<const dcp::Text> other, EqualityOptions const& options, NoteHandler note) const;
+
+protected:
+
+       Text(
+               Time in,
+               Time out,
+               float h_position,
+               HAlign h_align,
+               float v_position,
+               VAlign v_align,
+               float z_position,
+               Time fade_up_time,
+               Time fade_down_time
+               );
+
+       Time _in;
+       Time _out;
+       /** Horizontal position as a proportion of the screen width from the _h_align
+        *  (between 0 and 1)
+        */
+       float _h_position = 0;
+       HAlign _h_align = HAlign::CENTER;
+       /** Vertical position as a proportion of the screen height from the _v_align
+        *  (between 0 and 1)
+        */
+       float _v_position = 0;
+       VAlign _v_align = VAlign::CENTER;
+       float _z_position = 0;
+       Time _fade_up_time;
+       Time _fade_down_time;
+};
+
+
+}
+
+
+#endif
diff --git a/src/text_asset.cc b/src/text_asset.cc
new file mode 100644 (file)
index 0000000..ff6c00c
--- /dev/null
@@ -0,0 +1,1000 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text_asset.cc
+ *  @brief TextAsset class
+ */
+
+
+#include "compose.hpp"
+#include "dcp_assert.h"
+#include "load_font_node.h"
+#include "raw_convert.h"
+#include "reel_asset.h"
+#include "subtitle_image.h"
+#include "text_asset.h"
+#include "text_asset_internal.h"
+#include "text_string.h"
+#include "util.h"
+#include "xml.h"
+#include <asdcp/AS_DCP.h>
+#include <asdcp/KM_util.h>
+#include <libxml++/nodes/element.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_array.hpp>
+#include <algorithm>
+
+
+using std::cerr;
+using std::cout;
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using std::map;
+using std::pair;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using boost::lexical_cast;
+using boost::optional;
+using namespace dcp;
+
+
+TextAsset::TextAsset (boost::filesystem::path file)
+       : Asset (file)
+{
+
+}
+
+
+string
+string_attribute (xmlpp::Element const * node, string name)
+{
+       auto a = node->get_attribute (name);
+       if (!a) {
+               throw XMLError (String::compose ("missing attribute %1", name));
+       }
+       return string (a->get_value ());
+}
+
+
+optional<string>
+optional_string_attribute (xmlpp::Element const * node, string name)
+{
+       auto a = node->get_attribute (name);
+       if (!a) {
+               return {};
+       }
+       return string (a->get_value ());
+}
+
+
+optional<bool>
+optional_bool_attribute (xmlpp::Element const * node, string name)
+{
+       auto s = optional_string_attribute (node, name);
+       if (!s) {
+               return {};
+       }
+
+       return (s.get() == "1" || s.get() == "yes");
+}
+
+
+template <class T>
+optional<T>
+optional_number_attribute (xmlpp::Element const * node, string name)
+{
+       auto s = optional_string_attribute (node, name);
+       if (!s) {
+               return boost::optional<T> ();
+       }
+
+       std::string t = s.get ();
+       boost::erase_all (t, " ");
+       return raw_convert<T> (t);
+}
+
+
+TextAsset::ParseState
+TextAsset::font_node_state (xmlpp::Element const * node, Standard standard) const
+{
+       ParseState ps;
+
+       if (standard == Standard::INTEROP) {
+               ps.font_id = optional_string_attribute (node, "Id");
+       } else {
+               ps.font_id = optional_string_attribute (node, "ID");
+       }
+       ps.size = optional_number_attribute<int64_t> (node, "Size");
+       ps.aspect_adjust = optional_number_attribute<float> (node, "AspectAdjust");
+       ps.italic = optional_bool_attribute (node, "Italic");
+       ps.bold = optional_string_attribute(node, "Weight").get_value_or("normal") == "bold";
+       if (standard == Standard::INTEROP) {
+               ps.underline = optional_bool_attribute (node, "Underlined");
+       } else {
+               ps.underline = optional_bool_attribute (node, "Underline");
+       }
+       auto c = optional_string_attribute (node, "Color");
+       if (c) {
+               ps.colour = Colour (c.get ());
+       }
+       auto const e = optional_string_attribute (node, "Effect");
+       if (e) {
+               ps.effect = string_to_effect (e.get ());
+       }
+       c = optional_string_attribute (node, "EffectColor");
+       if (c) {
+               ps.effect_colour = Colour (c.get ());
+       }
+
+       return ps;
+}
+
+void
+TextAsset::position_align (TextAsset::ParseState& ps, xmlpp::Element const * node) const
+{
+       auto hp = optional_number_attribute<float> (node, "HPosition");
+       if (!hp) {
+               hp = optional_number_attribute<float> (node, "Hposition");
+       }
+       if (hp) {
+               ps.h_position = hp.get () / 100;
+       }
+
+       auto ha = optional_string_attribute (node, "HAlign");
+       if (!ha) {
+               ha = optional_string_attribute (node, "Halign");
+       }
+       if (ha) {
+               ps.h_align = string_to_halign (ha.get ());
+       }
+
+       auto vp = optional_number_attribute<float> (node, "VPosition");
+       if (!vp) {
+               vp = optional_number_attribute<float> (node, "Vposition");
+       }
+       if (vp) {
+               ps.v_position = vp.get () / 100;
+       }
+
+       auto va = optional_string_attribute (node, "VAlign");
+       if (!va) {
+               va = optional_string_attribute (node, "Valign");
+       }
+       if (va) {
+               ps.v_align = string_to_valign (va.get ());
+       }
+
+       auto zp = optional_number_attribute<float>(node, "Zposition");
+       if (zp) {
+               ps.z_position = zp.get() / 100;
+       }
+}
+
+
+TextAsset::ParseState
+TextAsset::text_node_state (xmlpp::Element const * node) const
+{
+       ParseState ps;
+
+       position_align (ps, node);
+
+       auto d = optional_string_attribute (node, "Direction");
+       if (d) {
+               ps.direction = string_to_direction (d.get ());
+       }
+
+       ps.type = ParseState::Type::TEXT;
+
+       return ps;
+}
+
+
+TextAsset::ParseState
+TextAsset::image_node_state (xmlpp::Element const * node) const
+{
+       ParseState ps;
+
+       position_align (ps, node);
+
+       ps.type = ParseState::Type::IMAGE;
+
+       return ps;
+}
+
+
+TextAsset::ParseState
+TextAsset::subtitle_node_state (xmlpp::Element const * node, optional<int> tcr) const
+{
+       ParseState ps;
+       ps.in = Time (string_attribute(node, "TimeIn"), tcr);
+       ps.out = Time (string_attribute(node, "TimeOut"), tcr);
+       ps.fade_up_time = fade_time (node, "FadeUpTime", tcr);
+       ps.fade_down_time = fade_time (node, "FadeDownTime", tcr);
+       return ps;
+}
+
+
+Time
+TextAsset::fade_time (xmlpp::Element const * node, string name, optional<int> tcr) const
+{
+       auto const u = optional_string_attribute(node, name).get_value_or ("");
+       Time t;
+
+       if (u.empty ()) {
+               t = Time (0, 0, 0, 20, 250);
+       } else if (u.find (":") != string::npos) {
+               t = Time (u, tcr);
+       } else {
+               t = Time (0, 0, 0, lexical_cast<int> (u), tcr.get_value_or(250));
+       }
+
+       if (t > Time (0, 0, 8, 0, 250)) {
+               t = Time (0, 0, 8, 0, 250);
+       }
+
+       return t;
+}
+
+
+void
+TextAsset::parse_texts(xmlpp::Element const * node, vector<ParseState>& state, optional<int> tcr, Standard standard)
+{
+       if (node->get_name() == "Font") {
+               state.push_back (font_node_state (node, standard));
+       } else if (node->get_name() == "Subtitle") {
+               state.push_back (subtitle_node_state (node, tcr));
+       } else if (node->get_name() == "Text") {
+               state.push_back (text_node_state (node));
+       } else if (node->get_name() == "SubtitleList") {
+               state.push_back (ParseState ());
+       } else if (node->get_name() == "Image") {
+               state.push_back (image_node_state (node));
+       } else {
+               throw XMLError ("unexpected node " + node->get_name());
+       }
+
+       float space_before = 0;
+
+       /* Collect <Ruby>s first */
+       auto get_text_content = [](xmlpp::Element const* element) {
+               string all_content;
+               for (auto child: element->get_children()) {
+                       auto content = dynamic_cast<xmlpp::ContentNode const*>(child);
+                       if (content) {
+                               all_content += content->get_content();
+                       }
+               }
+               return all_content;
+       };
+
+       vector<Ruby> rubies;
+       for (auto child: node->get_children()) {
+               auto element = dynamic_cast<xmlpp::Element const*>(child);
+               if (element && element->get_name() == "Ruby") {
+                       optional<string> base;
+                       optional<string> annotation;
+                       optional<float> size;
+                       optional<RubyPosition> position;
+                       optional<float> offset;
+                       optional<float> spacing;
+                       optional<float> aspect_adjust;
+                       for (auto ruby_child: element->get_children()) {
+                               if (auto ruby_element = dynamic_cast<xmlpp::Element const*>(ruby_child)) {
+                                       if (ruby_element->get_name() == "Rb") {
+                                               base = get_text_content(ruby_element);
+                                       } else if (ruby_element->get_name() == "Rt") {
+                                               annotation = get_text_content(ruby_element);
+                                               size = optional_number_attribute<float>(ruby_element, "Size");
+                                               if (auto position_string = optional_string_attribute(ruby_element, "Position")) {
+                                                       if (*position_string == "before") {
+                                                               position = RubyPosition::BEFORE;
+                                                       } else if (*position_string == "after") {
+                                                               position = RubyPosition::AFTER;
+                                                       } else {
+                                                               DCP_ASSERT(false);
+                                                       }
+                                               }
+                                               offset = optional_number_attribute<float>(ruby_element, "Offset");
+                                               spacing = optional_number_attribute<float>(ruby_element, "Spacing");
+                                               aspect_adjust = optional_number_attribute<float>(ruby_element, "AspectAdjust");
+                                       }
+                               }
+                       }
+                       DCP_ASSERT(base);
+                       DCP_ASSERT(annotation);
+                       auto ruby = Ruby{*base, *annotation};
+                       if (size) {
+                               ruby.size = *size;
+                       }
+                       if (position) {
+                               ruby.position = *position;
+                       }
+                       if (offset) {
+                               ruby.offset = *offset;
+                       }
+                       if (spacing) {
+                               ruby.spacing = *spacing;
+                       }
+                       if (aspect_adjust) {
+                               ruby.aspect_adjust = *aspect_adjust;
+                       }
+                       rubies.push_back(ruby);
+               }
+       }
+
+       for (auto i: node->get_children()) {
+
+               /* Handle actual content e.g. text */
+               auto const v = dynamic_cast<xmlpp::ContentNode const *>(i);
+               if (v) {
+                       maybe_add_text(v->get_content(), state, space_before, standard, rubies);
+                       space_before = 0;
+               }
+
+               /* Handle other nodes */
+               auto const e = dynamic_cast<xmlpp::Element const *>(i);
+               if (e) {
+                       if (e->get_name() == "Space") {
+                               if (node->get_name() != "Text") {
+                                       throw XMLError ("Space node found outside Text");
+                               }
+                               auto size = optional_string_attribute(e, "Size").get_value_or("0.5");
+                               if (standard == dcp::Standard::INTEROP) {
+                                       boost::replace_all(size, "em", "");
+                               }
+                               space_before += raw_convert<float>(size);
+                       } else if (e->get_name() != "Ruby") {
+                               parse_texts(e, state, tcr, standard);
+                       }
+               }
+       }
+
+       state.pop_back ();
+}
+
+
+void
+TextAsset::maybe_add_text(
+       string text,
+       vector<ParseState> const & parse_state,
+       float space_before,
+       Standard standard,
+       vector<Ruby> const& rubies
+       )
+{
+       auto wanted = [](ParseState const& ps) {
+               return ps.type && (ps.type.get() == ParseState::Type::TEXT || ps.type.get() == ParseState::Type::IMAGE);
+       };
+
+       if (find_if(parse_state.begin(), parse_state.end(), wanted) == parse_state.end()) {
+               return;
+       }
+
+       ParseState ps;
+       for (auto const& i: parse_state) {
+               if (i.font_id) {
+                       ps.font_id = i.font_id.get();
+               }
+               if (i.size) {
+                       ps.size = i.size.get();
+               }
+               if (i.aspect_adjust) {
+                       ps.aspect_adjust = i.aspect_adjust.get();
+               }
+               if (i.italic) {
+                       ps.italic = i.italic.get();
+               }
+               if (i.bold) {
+                       ps.bold = i.bold.get();
+               }
+               if (i.underline) {
+                       ps.underline = i.underline.get();
+               }
+               if (i.colour) {
+                       ps.colour = i.colour.get();
+               }
+               if (i.effect) {
+                       ps.effect = i.effect.get();
+               }
+               if (i.effect_colour) {
+                       ps.effect_colour = i.effect_colour.get();
+               }
+               if (i.h_position) {
+                       ps.h_position = i.h_position.get();
+               }
+               if (i.h_align) {
+                       ps.h_align = i.h_align.get();
+               }
+               if (i.v_position) {
+                       ps.v_position = i.v_position.get();
+               }
+               if (i.v_align) {
+                       ps.v_align = i.v_align.get();
+               }
+               if (i.z_position) {
+                       ps.z_position = i.z_position.get();
+               }
+               if (i.direction) {
+                       ps.direction = i.direction.get();
+               }
+               if (i.in) {
+                       ps.in = i.in.get();
+               }
+               if (i.out) {
+                       ps.out = i.out.get();
+               }
+               if (i.fade_up_time) {
+                       ps.fade_up_time = i.fade_up_time.get();
+               }
+               if (i.fade_down_time) {
+                       ps.fade_down_time = i.fade_down_time.get();
+               }
+               if (i.type) {
+                       ps.type = i.type.get();
+               }
+       }
+
+       if (!ps.in || !ps.out) {
+               /* We're not in a <Text> node; just ignore this content */
+               return;
+       }
+
+       DCP_ASSERT (ps.type);
+
+       switch (ps.type.get()) {
+       case ParseState::Type::TEXT:
+               _texts.push_back(
+                       make_shared<TextString>(
+                               ps.font_id,
+                               ps.italic.get_value_or (false),
+                               ps.bold.get_value_or (false),
+                               ps.underline.get_value_or (false),
+                               ps.colour.get_value_or (dcp::Colour (255, 255, 255)),
+                               ps.size.get_value_or (42),
+                               ps.aspect_adjust.get_value_or (1.0),
+                               ps.in.get(),
+                               ps.out.get(),
+                               ps.h_position.get_value_or(0),
+                               ps.h_align.get_value_or(HAlign::CENTER),
+                               ps.v_position.get_value_or(0),
+                               ps.v_align.get_value_or(VAlign::CENTER),
+                               ps.z_position.get_value_or(0),
+                               ps.direction.get_value_or (Direction::LTR),
+                               text,
+                               ps.effect.get_value_or (Effect::NONE),
+                               ps.effect_colour.get_value_or (dcp::Colour (0, 0, 0)),
+                               ps.fade_up_time.get_value_or(Time()),
+                               ps.fade_down_time.get_value_or(Time()),
+                               space_before,
+                               rubies
+                               )
+                       );
+               break;
+       case ParseState::Type::IMAGE:
+       {
+               switch (standard) {
+               case Standard::INTEROP:
+                       if (text.size() >= 4) {
+                               /* Remove file extension */
+                               text = text.substr(0, text.size() - 4);
+                       }
+                       break;
+               case Standard::SMPTE:
+                       /* It looks like this urn:uuid: is required, but DoM wasn't expecting it (and not writing it)
+                        * until around 2.15.140 so I guess either:
+                        *   a) it is not (always) used in the field, or
+                        *   b) nobody noticed / complained.
+                        */
+                       if (text.substr(0, 9) == "urn:uuid:") {
+                               text = text.substr(9);
+                       }
+                       break;
+               }
+
+               /* Add a subtitle with no image data and we'll fill that in later */
+               _texts.push_back(
+                       make_shared<SubtitleImage>(
+                               ArrayData(),
+                               text,
+                               ps.in.get(),
+                               ps.out.get(),
+                               ps.h_position.get_value_or(0),
+                               ps.h_align.get_value_or(HAlign::CENTER),
+                               ps.v_position.get_value_or(0),
+                               ps.v_align.get_value_or(VAlign::CENTER),
+                               ps.z_position.get_value_or(0),
+                               ps.fade_up_time.get_value_or(Time()),
+                               ps.fade_down_time.get_value_or(Time())
+                               )
+                       );
+               break;
+       }
+       }
+}
+
+
+vector<shared_ptr<const Text>>
+TextAsset::texts() const
+{
+       vector<shared_ptr<const Text>> s;
+       for (auto i: _texts) {
+               s.push_back (i);
+       }
+       return s;
+}
+
+
+vector<shared_ptr<const Text>>
+TextAsset::texts_during(Time from, Time to, bool starting) const
+{
+       vector<shared_ptr<const Text>> s;
+       for (auto i: _texts) {
+               if ((starting && from <= i->in() && i->in() < to) || (!starting && i->out() >= from && i->in() <= to)) {
+                       s.push_back (i);
+               }
+       }
+
+       return s;
+}
+
+
+void
+TextAsset::add(shared_ptr<Text> s)
+{
+       _texts.push_back (s);
+}
+
+
+Time
+TextAsset::latest_text_out() const
+{
+       Time t;
+       for (auto i: _texts) {
+               if (i->out() > t) {
+                       t = i->out ();
+               }
+       }
+
+       return t;
+}
+
+
+bool
+TextAsset::equals(shared_ptr<const Asset> other_asset, EqualityOptions const& options, NoteHandler note) const
+{
+       if (!Asset::equals (other_asset, options, note)) {
+               return false;
+       }
+
+       auto other = dynamic_pointer_cast<const TextAsset> (other_asset);
+       if (!other) {
+               return false;
+       }
+
+       if (_texts.size() != other->_texts.size()) {
+               note(NoteType::ERROR, String::compose("different number of subtitles / closed captions: %1 vs %2", _texts.size(), other->_texts.size()));
+               return false;
+       }
+
+       auto i = _texts.begin();
+       auto j = other->_texts.begin();
+
+       while (i != _texts.end()) {
+               auto string_i = dynamic_pointer_cast<TextString> (*i);
+               auto string_j = dynamic_pointer_cast<TextString> (*j);
+               auto image_i = dynamic_pointer_cast<SubtitleImage>(*i);
+               auto image_j = dynamic_pointer_cast<SubtitleImage>(*j);
+
+               if ((string_i && !string_j) || (image_i && !image_j)) {
+                       note (NoteType::ERROR, "subtitles differ: string vs. image");
+                       return false;
+               }
+
+               if (string_i && !string_i->equals(string_j, options, note)) {
+                       return false;
+               }
+
+               if (image_i && !image_i->equals(image_j, options, note)) {
+                       return false;
+               }
+
+               ++i;
+               ++j;
+       }
+
+       return true;
+}
+
+
+struct TextSorter
+{
+       bool operator() (shared_ptr<Text> a, shared_ptr<Text> b) {
+               if (a->in() != b->in()) {
+                       return a->in() < b->in();
+               }
+               if (a->v_align() == VAlign::BOTTOM) {
+                       return a->v_position() > b->v_position();
+               }
+               return a->v_position() < b->v_position();
+       }
+};
+
+
+void
+TextAsset::pull_fonts(shared_ptr<order::Part> part)
+{
+       if (part->children.empty ()) {
+               return;
+       }
+
+       /* Pull up from children */
+       for (auto i: part->children) {
+               pull_fonts (i);
+       }
+
+       if (part->parent) {
+               /* Establish the common font features that each of part's children have;
+                  these features go into part's font.
+               */
+               part->font = part->children.front()->font;
+               for (auto i: part->children) {
+                       part->font.take_intersection (i->font);
+               }
+
+               /* Remove common values from part's children's fonts */
+               for (auto i: part->children) {
+                       i->font.take_difference (part->font);
+               }
+       }
+
+       /* Merge adjacent children with the same font */
+       auto i = part->children.begin();
+       vector<shared_ptr<order::Part>> merged;
+
+       while (i != part->children.end()) {
+
+               if ((*i)->font.empty ()) {
+                       merged.push_back (*i);
+                       ++i;
+               } else {
+                       auto j = i;
+                       ++j;
+                       while (j != part->children.end() && (*i)->font == (*j)->font) {
+                               ++j;
+                       }
+                       if (std::distance (i, j) == 1) {
+                               merged.push_back (*i);
+                               ++i;
+                       } else {
+                               shared_ptr<order::Part> group (new order::Part (part, (*i)->font));
+                               for (auto k = i; k != j; ++k) {
+                                       (*k)->font.clear ();
+                                       group->children.push_back (*k);
+                               }
+                               merged.push_back (group);
+                               i = j;
+                       }
+               }
+       }
+
+       part->children = merged;
+}
+
+
+/** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
+ *  class because the differences between the two are fairly subtle.
+ */
+void
+TextAsset::texts_as_xml(xmlpp::Element* xml_root, int time_code_rate, Standard standard) const
+{
+       auto sorted = _texts;
+       std::stable_sort(sorted.begin(), sorted.end(), TextSorter());
+
+       /* Gather our subtitles into a hierarchy of Text/Text/String objects, writing
+          font information into the bottom level (String) objects.
+       */
+
+       auto root = make_shared<order::Part>(shared_ptr<order::Part>());
+       shared_ptr<order::Subtitle> subtitle;
+       shared_ptr<order::Text> text;
+
+       Time last_in;
+       Time last_out;
+       Time last_fade_up_time;
+       Time last_fade_down_time;
+       HAlign last_h_align;
+       float last_h_position;
+       VAlign last_v_align;
+       float last_v_position;
+       float last_z_position;
+       Direction last_direction;
+
+       for (auto i: sorted) {
+               if (!subtitle ||
+                   (last_in != i->in() ||
+                    last_out != i->out() ||
+                    last_fade_up_time != i->fade_up_time() ||
+                    last_fade_down_time != i->fade_down_time())
+                       ) {
+
+                       subtitle = make_shared<order::Subtitle>(root, i->in(), i->out(), i->fade_up_time(), i->fade_down_time());
+                       root->children.push_back (subtitle);
+
+                       last_in = i->in ();
+                       last_out = i->out ();
+                       last_fade_up_time = i->fade_up_time ();
+                       last_fade_down_time = i->fade_down_time ();
+                       text.reset ();
+               }
+
+               auto is = dynamic_pointer_cast<TextString>(i);
+               if (is) {
+                       if (!text ||
+                           last_h_align != is->h_align() ||
+                           fabs(last_h_position - is->h_position()) > ALIGN_EPSILON ||
+                           last_v_align != is->v_align() ||
+                           fabs(last_v_position - is->v_position()) > ALIGN_EPSILON ||
+                           fabs(last_z_position - is->z_position()) > ALIGN_EPSILON ||
+                           last_direction != is->direction()
+                               ) {
+                               text = make_shared<order::Text>(
+                                       subtitle,
+                                       is->h_align(),
+                                       is->h_position(),
+                                       is->v_align(),
+                                       is->v_position(),
+                                       is->z_position(),
+                                       is->direction(),
+                                       is->rubies()
+                                       );
+                               subtitle->children.push_back (text);
+
+                               last_h_align = is->h_align ();
+                               last_h_position = is->h_position ();
+                               last_v_align = is->v_align ();
+                               last_v_position = is->v_position ();
+                               last_z_position = is->z_position();
+                               last_direction = is->direction ();
+                       }
+
+                       text->children.push_back (make_shared<order::String>(text, order::Font (is, standard), is->text(), is->space_before()));
+               }
+
+               auto ii = dynamic_pointer_cast<SubtitleImage>(i);
+               if (ii) {
+                       text.reset ();
+                       subtitle->children.push_back (
+                               make_shared<order::Image>(subtitle, ii->id(), ii->png_image(), ii->h_align(), ii->h_position(), ii->v_align(), ii->v_position(), ii->z_position())
+                               );
+               }
+       }
+
+       /* Pull font changes as high up the hierarchy as we can */
+
+       pull_fonts (root);
+
+       /* Write XML */
+
+       order::Context context;
+       context.time_code_rate = time_code_rate;
+       context.standard = standard;
+       context.spot_number = 1;
+
+       root->write_xml (xml_root, context);
+}
+
+
+map<string, ArrayData>
+TextAsset::font_data() const
+{
+       map<string, ArrayData> out;
+       for (auto const& i: _fonts) {
+               out[i.load_id] = i.data;
+       }
+       return out;
+}
+
+
+map<string, boost::filesystem::path>
+TextAsset::font_filenames() const
+{
+       map<string, boost::filesystem::path> out;
+       for (auto const& i: _fonts) {
+               if (i.file) {
+                       out[i.load_id] = *i.file;
+               }
+       }
+       return out;
+}
+
+
+/** Replace empty IDs in any <LoadFontId> and <Font> tags with
+ *  a dummy string.  Some systems give errors with empty font IDs
+ *  (see DCP-o-matic bug #1689).
+ */
+void
+TextAsset::fix_empty_font_ids()
+{
+       bool have_empty = false;
+       vector<string> ids;
+       for (auto i: load_font_nodes()) {
+               if (i->id == "") {
+                       have_empty = true;
+               } else {
+                       ids.push_back (i->id);
+               }
+       }
+
+       if (!have_empty) {
+               return;
+       }
+
+       string const empty_id = unique_string (ids, "font");
+
+       for (auto i: load_font_nodes()) {
+               if (i->id == "") {
+                       i->id = empty_id;
+               }
+       }
+
+       for (auto i: _texts) {
+               auto j = dynamic_pointer_cast<TextString> (i);
+               if (j && j->font() && j->font().get() == "") {
+                       j->set_font (empty_id);
+               }
+       }
+}
+
+
+namespace {
+
+struct State
+{
+       int indent;
+       string xml;
+       int disable_formatting;
+};
+
+}
+
+
+static
+void
+format_xml_node (xmlpp::Node const* node, State& state)
+{
+       if (auto text_node = dynamic_cast<const xmlpp::TextNode*>(node)) {
+               string content = text_node->get_content();
+               boost::replace_all(content, "&", "&amp;");
+               boost::replace_all(content, "<", "&lt;");
+               boost::replace_all(content, ">", "&gt;");
+               state.xml += content;
+       } else if (auto element = dynamic_cast<const xmlpp::Element*>(node)) {
+               ++state.indent;
+
+               auto children = element->get_children();
+               auto const should_disable_formatting =
+                       std::any_of(
+                               children.begin(), children.end(),
+                               [](xmlpp::Node const* node) { return static_cast<bool>(dynamic_cast<const xmlpp::ContentNode*>(node)); }
+                               ) || element->get_name() == "Text";
+
+               if (!state.disable_formatting) {
+                       state.xml += "\n" + string(state.indent * 2, ' ');
+               }
+
+               state.xml += "<" + element->get_name();
+
+               for (auto attribute: element->get_attributes()) {
+                       state.xml += String::compose(" %1=\"%2\"", attribute->get_name().raw(), attribute->get_value().raw());
+               }
+
+               if (children.empty()) {
+                       state.xml += "/>";
+               } else {
+                       state.xml += ">";
+
+                       if (should_disable_formatting) {
+                               ++state.disable_formatting;
+                       }
+
+                       for (auto child: children) {
+                               format_xml_node(child, state);
+                       }
+
+                       if (!state.disable_formatting) {
+                               state.xml += "\n" + string(state.indent * 2, ' ');
+                       }
+
+                       state.xml += String::compose("</%1>", element->get_name().raw());
+
+                       if (should_disable_formatting) {
+                               --state.disable_formatting;
+                       }
+               }
+
+               --state.indent;
+       }
+}
+
+
+/** Format XML much as write_to_string_formatted() would do, except without adding any white space
+ *  to <Text> nodes.  This is an attempt to avoid changing what is actually displayed as subtitles
+ *  while also formatting the XML in such a way as to avoid DoM bug 2205.
+ *
+ *  xml_namespace is an optional namespace for the root node; it would be nicer to set this up with
+ *  set_namespace_declaration in the caller and then to extract it here but I couldn't find a way
+ *  to get all namespaces with the libxml++ API.
+ */
+string
+TextAsset::format_xml(xmlpp::Document const& document, optional<pair<string, string>> xml_namespace)
+{
+       auto root = document.get_root_node();
+
+       State state = {};
+       state.xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<" + root->get_name();
+
+       if (xml_namespace) {
+               if (xml_namespace->first.empty()) {
+                       state.xml += String::compose(" xmlns=\"%1\"", xml_namespace->second);
+               } else {
+                       state.xml += String::compose(" xmlns:%1=\"%2\"", xml_namespace->first, xml_namespace->second);
+               }
+       }
+
+       for (auto attribute: root->get_attributes()) {
+               state.xml += String::compose(" %1=\"%2\"", attribute->get_name().raw(), attribute->get_value().raw());
+       }
+
+       state.xml += ">";
+
+       for (auto child: document.get_root_node()->get_children()) {
+               format_xml_node(child, state);
+       }
+
+       state.xml += String::compose("\n</%1>\n", root->get_name().raw());
+
+       return state.xml;
+}
+
+
+void
+TextAsset::ensure_font(string load_id, dcp::ArrayData data)
+{
+       if (std::find_if(_fonts.begin(), _fonts.end(), [load_id](Font const& font) { return font.load_id == load_id; }) == _fonts.end()) {
+               add_font(load_id, data);
+       }
+}
+
diff --git a/src/text_asset.h b/src/text_asset.h
new file mode 100644 (file)
index 0000000..c8422c3
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text_asset.h
+ *  @brief TextAsset class
+ */
+
+
+#ifndef LIBDCP_TEXT_ASSET_H
+#define LIBDCP_TEXT_ASSET_H
+
+
+#include "array_data.h"
+#include "asset.h"
+#include "dcp_time.h"
+#include "text_standard.h"
+#include "text_string.h"
+#include <libcxml/cxml.h>
+#include <boost/shared_array.hpp>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+
+namespace xmlpp {
+       class Document;
+       class Element;
+}
+
+
+struct interop_dcp_font_test;
+struct smpte_dcp_font_test;
+struct pull_fonts_test1;
+struct pull_fonts_test2;
+struct pull_fonts_test3;
+
+
+namespace dcp {
+
+
+class TextString;
+class SubtitleImage;
+class FontNode;
+class TextNode;
+class SubtitleNode;
+class LoadFontNode;
+class ReelAsset;
+
+
+namespace order {
+       class Part;
+       struct Context;
+}
+
+
+/** @class TextAsset
+ *  @brief A parent for classes representing a file containing subtitles or closed captions
+ *
+ *  This class holds a list of Text objects which it can extract
+ *  from the appropriate part of either an Interop or SMPTE XML file.
+ *  Its subclasses InteropTextAsset and SMPTETextAsset handle the
+ *  differences between the two types.
+ */
+class TextAsset : public Asset
+{
+public:
+       TextAsset() = default;
+       explicit TextAsset(boost::filesystem::path file);
+
+       bool equals (
+               std::shared_ptr<const Asset>,
+               EqualityOptions const&,
+               NoteHandler note
+               ) const override;
+
+       std::vector<std::shared_ptr<const Text>> texts_during(Time from, Time to, bool starting) const;
+       std::vector<std::shared_ptr<const Text>> texts() const;
+
+       virtual void add(std::shared_ptr<Text>);
+       virtual void add_font (std::string id, dcp::ArrayData data) = 0;
+       void ensure_font(std::string id, dcp::ArrayData data);
+       std::map<std::string, ArrayData> font_data () const;
+       std::map<std::string, boost::filesystem::path> font_filenames () const;
+
+       virtual void write (boost::filesystem::path) const = 0;
+       virtual std::string xml_as_string () const = 0;
+
+       Time latest_text_out() const;
+
+       void fix_empty_font_ids ();
+
+       virtual std::vector<std::shared_ptr<LoadFontNode>> load_font_nodes () const = 0;
+
+       virtual int time_code_rate () const = 0;
+
+       /** @return Raw XML loaded from, or written to, an on-disk asset, or boost::none if
+        *  - this object was not created from an existing on-disk asset and has not been written to one, or
+        *  - this asset is encrypted and no key is available.
+        */
+       virtual boost::optional<std::string> raw_xml () const {
+               return _raw_xml;
+       }
+
+       virtual TextStandard text_standard() const = 0;
+
+       static std::string format_xml(xmlpp::Document const& document, boost::optional<std::pair<std::string, std::string>> xml_namespace);
+
+protected:
+       friend struct ::interop_dcp_font_test;
+       friend struct ::smpte_dcp_font_test;
+
+       struct ParseState {
+               boost::optional<std::string> font_id;
+               boost::optional<int64_t> size;
+               boost::optional<float> aspect_adjust;
+               boost::optional<bool> italic;
+               boost::optional<bool> bold;
+               boost::optional<bool> underline;
+               boost::optional<Colour> colour;
+               boost::optional<Effect> effect;
+               boost::optional<Colour> effect_colour;
+               boost::optional<float> h_position;
+               boost::optional<HAlign> h_align;
+               boost::optional<float> v_position;
+               boost::optional<VAlign> v_align;
+               boost::optional<float> z_position;
+               boost::optional<Direction> direction;
+               boost::optional<Time> in;
+               boost::optional<Time> out;
+               boost::optional<Time> fade_up_time;
+               boost::optional<Time> fade_down_time;
+               enum class Type {
+                       TEXT,
+                       IMAGE
+               };
+               boost::optional<Type> type;
+               float space_before = 0;
+       };
+
+       void parse_texts(xmlpp::Element const * node, std::vector<ParseState>& state, boost::optional<int> tcr, Standard standard);
+       ParseState font_node_state (xmlpp::Element const * node, Standard standard) const;
+       ParseState text_node_state (xmlpp::Element const * node) const;
+       ParseState image_node_state (xmlpp::Element const * node) const;
+       ParseState subtitle_node_state (xmlpp::Element const * node, boost::optional<int> tcr) const;
+       Time fade_time (xmlpp::Element const * node, std::string name, boost::optional<int> tcr) const;
+       void position_align (ParseState& ps, xmlpp::Element const * node) const;
+
+       void texts_as_xml(xmlpp::Element* root, int time_code_rate, Standard standard) const;
+
+       /** All our subtitles / closed captions, in no particular order */
+       std::vector<std::shared_ptr<Text>> _texts;
+
+       class Font
+       {
+       public:
+               Font (std::string load_id_, std::string uuid_, boost::filesystem::path file_)
+                       : load_id (load_id_)
+                       , uuid (uuid_)
+                       , data (file_)
+                       , file (file_)
+               {}
+
+               Font (std::string load_id_, std::string uuid_, ArrayData data_)
+                       : load_id (load_id_)
+                       , uuid (uuid_)
+                       , data (data_)
+               {}
+
+               std::string load_id;
+               std::string uuid;
+               ArrayData data;
+               /** .ttf file that this data was last written to, if applicable */
+               mutable boost::optional<boost::filesystem::path> file;
+       };
+
+       /** TTF font data that we need */
+       std::vector<Font> _fonts;
+
+       /** The raw XML data that we read from or wrote to our asset; useful for validation */
+       mutable boost::optional<std::string> _raw_xml;
+
+private:
+       friend struct ::pull_fonts_test1;
+       friend struct ::pull_fonts_test2;
+       friend struct ::pull_fonts_test3;
+
+       void maybe_add_text(
+               std::string text,
+               std::vector<ParseState> const & parse_state,
+               float space_before,
+               Standard standard,
+               std::vector<Ruby> const& rubies
+               );
+
+       static void pull_fonts (std::shared_ptr<order::Part> part);
+};
+
+
+}
+
+
+#endif
diff --git a/src/text_asset_internal.cc b/src/text_asset_internal.cc
new file mode 100644 (file)
index 0000000..58a2553
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text_asset_internal.cc
+ *  @brief Internal TextAsset helpers
+ */
+
+
+#include "text_asset_internal.h"
+#include "text_string.h"
+#include "compose.hpp"
+#include <cmath>
+
+
+using std::map;
+using std::shared_ptr;
+using std::string;
+using namespace dcp;
+
+
+order::Font::Font(shared_ptr<TextString> s, Standard standard)
+{
+       if (s->font()) {
+               if (standard == Standard::SMPTE) {
+                       _values["ID"] = s->font().get ();
+               } else {
+                       _values["Id"] = s->font().get ();
+               }
+       }
+       _values["Italic"] = s->italic() ? "yes" : "no";
+       _values["Color"] = s->colour().to_argb_string();
+       _values["Size"] = raw_convert<string> (s->size());
+       _values["AspectAdjust"] = raw_convert<string>(s->aspect_adjust(), 1, true);
+       _values["Effect"] = effect_to_string (s->effect());
+       _values["EffectColor"] = s->effect_colour().to_argb_string();
+       _values["Script"] = "normal";
+       if (standard == Standard::SMPTE) {
+               _values["Underline"] = s->underline() ? "yes" : "no";
+       } else {
+               _values["Underlined"] = s->underline() ? "yes" : "no";
+       }
+       _values["Weight"] = s->bold() ? "bold" : "normal";
+}
+
+
+xmlpp::Element*
+order::Font::as_xml (xmlpp::Element* parent, Context&) const
+{
+       auto e = parent->add_child("Font");
+       for (const auto& i: _values) {
+               e->set_attribute (i.first, i.second);
+       }
+       return e;
+}
+
+
+/** Modify our values so that they contain only those that are common to us and
+ *  other.
+ */
+void
+order::Font::take_intersection (Font other)
+{
+       map<string, string> inter;
+
+       for (auto const& i: other._values) {
+               auto t = _values.find (i.first);
+               if (t != _values.end() && t->second == i.second) {
+                       inter.insert (i);
+               }
+       }
+
+       _values = inter;
+}
+
+
+/** Modify our values so that it contains only those keys that are not in other */
+void
+order::Font::take_difference (Font other)
+{
+       map<string, string> diff;
+       for (auto const& i: _values) {
+               if (other._values.find (i.first) == other._values.end()) {
+                       diff.insert (i);
+               }
+       }
+
+       _values = diff;
+}
+
+
+bool
+order::Font::empty () const
+{
+       return _values.empty ();
+}
+
+
+xmlpp::Element*
+order::Part::as_xml (xmlpp::Element* parent, Context &) const
+{
+       return parent;
+}
+
+
+xmlpp::Element*
+order::String::as_xml (xmlpp::Element* parent, Context& context) const
+{
+       if (fabs(_space_before) > SPACE_BEFORE_EPSILON) {
+               auto space = parent->add_child("Space");
+               auto size = raw_convert<string>(_space_before, 2);
+               if (context.standard == Standard::INTEROP) {
+                       size += "em";
+               }
+               space->set_attribute("Size", size);
+       }
+       parent->add_child_text (_text);
+       return 0;
+}
+
+
+void
+order::Part::write_xml (xmlpp::Element* parent, order::Context& context) const
+{
+       if (!font.empty ()) {
+               parent = font.as_xml (parent, context);
+       }
+
+       parent = as_xml (parent, context);
+
+       for (auto i: children) {
+               i->write_xml (parent, context);
+       }
+}
+
+
+static void
+position_align (xmlpp::Element* e, order::Context& context, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position)
+{
+       if (h_align != HAlign::CENTER) {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Halign", halign_to_string (h_align));
+               } else {
+                       e->set_attribute ("HAlign", halign_to_string (h_align));
+               }
+       }
+
+       if (fabs(h_position) > ALIGN_EPSILON) {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Hposition", raw_convert<string> (h_position * 100, 6));
+               } else {
+                       e->set_attribute ("HPosition", raw_convert<string> (h_position * 100, 6));
+               }
+       }
+
+       if (context.standard == Standard::SMPTE) {
+               e->set_attribute ("Valign", valign_to_string (v_align));
+       } else {
+               e->set_attribute ("VAlign", valign_to_string (v_align));
+       }
+
+       if (fabs(v_position) > ALIGN_EPSILON) {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Vposition", raw_convert<string> (v_position * 100, 6));
+               } else {
+                       e->set_attribute ("VPosition", raw_convert<string> (v_position * 100, 6));
+               }
+       } else {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Vposition", "0");
+               } else {
+                       e->set_attribute ("VPosition", "0");
+               }
+       }
+
+       if (fabs(z_position) > ALIGN_EPSILON && context.standard == Standard::SMPTE) {
+               e->set_attribute("Zposition", raw_convert<string>(z_position * 100, 6));
+       }
+}
+
+
+xmlpp::Element*
+order::Text::as_xml (xmlpp::Element* parent, Context& context) const
+{
+       auto e = parent->add_child ("Text");
+
+       position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position);
+
+       /* Interop only supports "horizontal" or "vertical" for direction, so only write this
+          for SMPTE.
+       */
+       if (_direction != Direction::LTR && context.standard == Standard::SMPTE) {
+               e->set_attribute ("Direction", direction_to_string (_direction));
+       }
+
+       for (auto const& ruby: _rubies) {
+               auto xml = e->add_child("Ruby");
+               xml->add_child("Rb")->add_child_text(ruby.base);
+               auto rt = xml->add_child("Rt");
+               rt->add_child_text(ruby.annotation);
+               rt->set_attribute("Size", dcp::raw_convert<string>(ruby.size, 6));
+               rt->set_attribute("Position", ruby.position == RubyPosition::BEFORE ? "before" : "after");
+               rt->set_attribute("Offset", dcp::raw_convert<string>(ruby.offset, 6));
+               rt->set_attribute("Spacing", dcp::raw_convert<string>(ruby.spacing, 6));
+               rt->set_attribute("AspectAdjust", dcp::raw_convert<string>(ruby.aspect_adjust, 6));
+       }
+
+       return e;
+}
+
+
+xmlpp::Element*
+order::Subtitle::as_xml (xmlpp::Element* parent, Context& context) const
+{
+       auto e = parent->add_child ("Subtitle");
+       e->set_attribute ("SpotNumber", raw_convert<string> (context.spot_number++));
+       e->set_attribute ("TimeIn", _in.rebase(context.time_code_rate).as_string(context.standard));
+       e->set_attribute ("TimeOut", _out.rebase(context.time_code_rate).as_string(context.standard));
+       if (context.standard == Standard::SMPTE) {
+               e->set_attribute ("FadeUpTime", _fade_up.rebase(context.time_code_rate).as_string(context.standard));
+               e->set_attribute ("FadeDownTime", _fade_down.rebase(context.time_code_rate).as_string(context.standard));
+       } else {
+               e->set_attribute ("FadeUpTime", raw_convert<string> (_fade_up.as_editable_units_ceil(context.time_code_rate)));
+               e->set_attribute ("FadeDownTime", raw_convert<string> (_fade_down.as_editable_units_ceil(context.time_code_rate)));
+       }
+       return e;
+}
+
+
+bool
+order::Font::operator== (Font const & other) const
+{
+       return _values == other._values;
+}
+
+
+void
+order::Font::clear ()
+{
+       _values.clear ();
+}
+
+
+xmlpp::Element *
+order::Image::as_xml (xmlpp::Element* parent, Context& context) const
+{
+       auto e = parent->add_child ("Image");
+
+       position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position);
+       if (context.standard == Standard::SMPTE) {
+               e->add_child_text ("urn:uuid:" + _id);
+       } else {
+               e->add_child_text (_id + ".png");
+       }
+
+       return e;
+}
diff --git a/src/text_asset_internal.h b/src/text_asset_internal.h
new file mode 100644 (file)
index 0000000..8491517
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text_asset_internal.h
+ *  @brief Internal TextAsset helpers
+ */
+
+
+#ifndef LIBDCP_TEXT_ASSET_INTERNAL_H
+#define LIBDCP_TEXT_ASSET_INTERNAL_H
+
+
+#include "array_data.h"
+#include "dcp_time.h"
+#include "h_align.h"
+#include "raw_convert.h"
+#include "v_align.h"
+#include "warnings.h"
+LIBDCP_DISABLE_WARNINGS
+#include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+struct take_intersection_test;
+struct take_difference_test;
+struct pull_fonts_test1;
+struct pull_fonts_test2;
+struct pull_fonts_test3;
+
+
+namespace dcp {
+
+
+class Ruby;
+class TextString;
+
+
+namespace order {
+
+
+struct Context
+{
+       int time_code_rate;
+       Standard standard;
+       int spot_number;
+};
+
+
+class Font
+{
+public:
+       Font () {}
+
+       Font(std::shared_ptr<TextString> s, Standard standard);
+
+       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const;
+
+       void take_intersection (Font other);
+       void take_difference (Font other);
+       bool empty () const;
+       void clear ();
+       bool operator== (Font const & other) const;
+
+private:
+       friend struct ::take_intersection_test;
+       friend struct ::take_difference_test;
+       friend struct ::pull_fonts_test1;
+       friend struct ::pull_fonts_test2;
+       friend struct ::pull_fonts_test3;
+
+       std::map<std::string, std::string> _values;
+};
+
+
+class Part
+{
+public:
+       Part (std::shared_ptr<Part> parent_)
+               : parent (parent_)
+       {}
+
+       Part (std::shared_ptr<Part> parent_, Font font_)
+               : parent (parent_)
+               , font (font_)
+       {}
+
+       virtual ~Part () {}
+
+       virtual xmlpp::Element* as_xml (xmlpp::Element* parent, Context &) const;
+       void write_xml (xmlpp::Element* parent, order::Context& context) const;
+
+       std::shared_ptr<Part> parent;
+       Font font;
+       std::vector<std::shared_ptr<Part>> children;
+};
+
+
+class String : public Part
+{
+public:
+       String (std::shared_ptr<Part> parent, Font font, std::string text, float space_before)
+               : Part (parent, font)
+               , _text (text)
+               , _space_before (space_before)
+       {}
+
+       virtual xmlpp::Element* as_xml (xmlpp::Element* parent, Context &) const override;
+
+private:
+       std::string _text;
+       float _space_before;
+};
+
+
+class Text : public Part
+{
+public:
+       Text(std::shared_ptr<Part> parent, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position, Direction direction, std::vector<Ruby> rubies)
+               : Part (parent)
+               , _h_align (h_align)
+               , _h_position (h_position)
+               , _v_align (v_align)
+               , _v_position (v_position)
+               , _z_position(z_position)
+               , _direction (direction)
+               , _rubies(rubies)
+       {}
+
+       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
+
+private:
+       HAlign _h_align;
+       float _h_position;
+       VAlign _v_align;
+       float _v_position;
+       float _z_position;
+       Direction _direction;
+       std::vector<Ruby> _rubies;
+};
+
+
+class Subtitle : public Part
+{
+public:
+       Subtitle (std::shared_ptr<Part> parent, Time in, Time out, Time fade_up, Time fade_down)
+               : Part (parent)
+               , _in (in)
+               , _out (out)
+               , _fade_up (fade_up)
+               , _fade_down (fade_down)
+       {}
+
+       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
+
+private:
+       Time _in;
+       Time _out;
+       Time _fade_up;
+       Time _fade_down;
+};
+
+
+class Image : public Part
+{
+public:
+       Image (std::shared_ptr<Part> parent, std::string id, ArrayData png_data, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position)
+               : Part (parent)
+               , _png_data (png_data)
+               , _id (id)
+               , _h_align (h_align)
+               , _h_position (h_position)
+               , _v_align (v_align)
+               , _v_position (v_position)
+               , _z_position(z_position)
+       {}
+
+       xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
+
+private:
+       ArrayData _png_data;
+       std::string _id; ///< the ID of this image
+       HAlign _h_align;
+       float _h_position;
+       VAlign _v_align;
+       float _v_position;
+       float _z_position;
+};
+
+
+}
+}
+
+
+#endif
diff --git a/src/text_standard.cc b/src/text_standard.cc
new file mode 100644 (file)
index 0000000..5582b79
--- /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 "text_standard.h"
+
+
+using namespace dcp;
+
+
+bool
+dcp::uses_baseline(TextStandard standard)
+{
+       return standard == TextStandard::INTEROP || standard == TextStandard::SMPTE_2014;
+}
+
+
+bool
+dcp::uses_bounding_box(TextStandard 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 == TextStandard::SMPTE_2007 || standard == TextStandard::SMPTE_2010;
+}
+
+
diff --git a/src/text_standard.h b/src/text_standard.h
new file mode 100644 (file)
index 0000000..1d14603
--- /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_TEXT_STANDARD_H
+#define LIBDCP_TEXT_STANDARD_H
+
+
+namespace dcp {
+
+
+enum class TextStandard {
+       INTEROP,
+       SMPTE_2007,
+       SMPTE_2010,
+       SMPTE_2014
+};
+
+
+bool uses_baseline(TextStandard standard);
+bool uses_bounding_box(TextStandard standard);
+
+
+}
+
+#endif
+
diff --git a/src/text_string.cc b/src/text_string.cc
new file mode 100644 (file)
index 0000000..411829e
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+    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.
+*/
+
+
+/** @file  src/subtitle_string.cc
+ *  @brief SubtitleString class
+ */
+
+
+#include "compose.hpp"
+#include "text_string.h"
+#include "xml.h"
+#include <cmath>
+
+
+using std::dynamic_pointer_cast;
+using std::max;
+using std::min;
+using std::ostream;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using boost::optional;
+using namespace dcp;
+
+
+TextString::TextString(
+       optional<string> font,
+       bool italic,
+       bool bold,
+       bool underline,
+       Colour colour,
+       int size,
+       float aspect_adjust,
+       Time in,
+       Time out,
+       float h_position,
+       HAlign h_align,
+       float v_position,
+       VAlign v_align,
+       float z_position,
+       Direction direction,
+       string text,
+       Effect effect,
+       Colour effect_colour,
+       Time fade_up_time,
+       Time fade_down_time,
+       float space_before,
+       vector<Ruby> rubies
+       )
+       : Text(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
+       , _font (font)
+       , _italic (italic)
+       , _bold (bold)
+       , _underline (underline)
+       , _colour (colour)
+       , _size (size)
+       , _aspect_adjust (aspect_adjust)
+       , _direction (direction)
+       , _text (text)
+       , _effect (effect)
+       , _effect_colour (effect_colour)
+       , _space_before (space_before)
+       , _rubies(rubies)
+{
+       _aspect_adjust = max(min(_aspect_adjust, 4.0f), 0.25f);
+}
+
+
+float
+TextString::size_in_pixels (int screen_height) const
+{
+       /* Size in the subtitle file is given in points as if the screen
+          height is 11 inches, so a 72pt font would be 1/11th of the screen
+          height.
+       */
+
+       return _size * static_cast<float>(screen_height) / (11.0f * 72.0f);
+}
+
+
+bool
+dcp::operator==(TextString const & a, TextString const & b)
+{
+       return (
+               a.font() == b.font() &&
+               a.italic() == b.italic() &&
+               a.bold() == b.bold() &&
+               a.underline() == b.underline() &&
+               a.colour() == b.colour() &&
+               a.size() == b.size() &&
+               fabs (a.aspect_adjust() - b.aspect_adjust()) < ASPECT_ADJUST_EPSILON &&
+               a.in() == b.in() &&
+               a.out() == b.out() &&
+               a.h_position() == b.h_position() &&
+               a.h_align() == b.h_align() &&
+               a.v_position() == b.v_position() &&
+               a.v_align() == b.v_align() &&
+               a.z_position() == b.z_position() &&
+               a.direction() == b.direction() &&
+               a.text() == b.text() &&
+               a.effect() == b.effect() &&
+               a.effect_colour() == b.effect_colour() &&
+               a.fade_up_time() == b.fade_up_time() &&
+               a.fade_down_time() == b.fade_down_time() &&
+               fabs (a.space_before() - b.space_before()) < SPACE_BEFORE_EPSILON &&
+               a.rubies() == b.rubies()
+               );
+}
+
+
+bool
+dcp::operator!=(TextString const & a, TextString const & b)
+{
+       return !(a == b);
+}
+
+
+ostream&
+dcp::operator<<(ostream& s, TextString const & sub)
+{
+       s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n"
+         << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n"
+         << "font " << sub.font().get_value_or ("[default]") << ", ";
+
+       if (sub.italic()) {
+               s << "italic, ";
+       } else {
+               s << "non-italic, ";
+       }
+
+       if (sub.bold()) {
+               s << "bold, ";
+       } else {
+               s << "normal, ";
+       }
+
+       if (sub.underline()) {
+               s << "underlined, ";
+       }
+
+       s << "size " << sub.size() << ", aspect " << sub.aspect_adjust()
+         << ", colour (" << sub.colour().r << ", " << sub.colour().g << ", " << sub.colour().b << ")"
+         << ", vpos " << sub.v_position() << ", valign " << ((int) sub.v_align())
+         << ", hpos " << sub.h_position() << ", halign " << ((int) sub.h_align())
+         << ", zpos " << sub.z_position()
+         << ", direction " << ((int) sub.direction())
+         << ", effect " << ((int) sub.effect())
+         << ", effect colour (" << sub.effect_colour().r << ", " << sub.effect_colour().g << ", " << sub.effect_colour().b << ")"
+         << ", space before " << sub.space_before();
+
+       for (auto ruby: sub.rubies()) {
+               s << ", ruby " << ruby.base << " " << ruby.annotation;
+       }
+
+       return s;
+}
+
+
+bool
+TextString::equals(shared_ptr<const Text> other_sub, EqualityOptions const& options, NoteHandler note) const
+{
+       if (!Text::equals(other_sub, options, note)) {
+               return false;
+       }
+
+       auto other = dynamic_pointer_cast<const TextString>(other_sub);
+       if (!other) {
+               note(NoteType::ERROR, "Subtitle types differ: string vs image");
+               return false;
+       }
+
+       bool same = true;
+
+       if (_font != other->_font) {
+               note(NoteType::ERROR, String::compose("subtitle font differs: %1 vs %2", _font.get_value_or("[none]"), other->_font.get_value_or("[none]")));
+               same = false;
+       }
+
+       if (_italic != other->_italic) {
+               note(NoteType::ERROR, String::compose("subtitle italic flag differs: %1 vs %2", _italic ? "true" : "false", other->_italic ? "true" : "false"));
+               same = false;
+       }
+
+       if (_bold != other->_bold) {
+               note(NoteType::ERROR, String::compose("subtitle bold flag differs: %1 vs %2", _bold ? "true" : "false", other->_bold ? "true" : "false"));
+               same = false;
+       }
+
+       if (_underline != other->_underline) {
+               note(NoteType::ERROR, String::compose("subtitle underline flag differs: %1 vs %2", _underline ? "true" : "false", other->_underline ? "true" : "false"));
+               same = false;
+       }
+
+       if (_colour != other->_colour) {
+               note(NoteType::ERROR, String::compose("subtitle colour differs: %1 vs %2", _colour.to_rgb_string(), other->_colour.to_rgb_string()));
+               same = false;
+       }
+
+       if (_size != other->_size) {
+               note(NoteType::ERROR, String::compose("subtitle size differs: %1 vs %2", _size, other->_size));
+               same = false;
+       }
+
+       if (_aspect_adjust != other->_aspect_adjust) {
+               note(NoteType::ERROR, String::compose("subtitle aspect_adjust differs: %1 vs %2", _aspect_adjust, other->_aspect_adjust));
+               same = false;
+       }
+
+       if (_direction != other->_direction) {
+               note(NoteType::ERROR, String::compose("subtitle direction differs: %1 vs %2", direction_to_string(_direction), direction_to_string(other->_direction)));
+               same = false;
+       }
+
+       if (_text != other->_text) {
+               note(NoteType::ERROR, String::compose("subtitle text differs: %1 vs %2", _text, other->_text));
+               same = false;
+       }
+
+       if (_effect != other->_effect) {
+               note(NoteType::ERROR, String::compose("subtitle effect differs: %1 vs %2", effect_to_string(_effect), effect_to_string(other->_effect)));
+               same = false;
+       }
+
+       if (_effect_colour != other->_effect_colour) {
+               note(NoteType::ERROR, String::compose("subtitle effect colour differs: %1 vs %2", _effect_colour.to_rgb_string(), other->_effect_colour.to_rgb_string()));
+               same = false;
+       }
+
+       if (_space_before != other->_space_before) {
+               note(NoteType::ERROR, String::compose("subtitle space before differs: %1 vs %2", _space_before, other->_space_before));
+               same = false;
+       }
+
+       if (_rubies != other->_rubies) {
+               note(NoteType::ERROR, "rubies differ");
+               same = false;
+       }
+
+       return same;
+}
+
diff --git a/src/text_string.h b/src/text_string.h
new file mode 100644 (file)
index 0000000..180a8b7
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+    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.
+*/
+
+
+/** @file  src/text_string.h
+ *  @brief TextString class
+ */
+
+
+#ifndef LIBDCP_TEXT_STRING_H
+#define LIBDCP_TEXT_STRING_H
+
+
+#include "dcp_time.h"
+#include "ruby.h"
+#include "text.h"
+#include <boost/optional.hpp>
+#include <string>
+
+
+namespace dcp {
+
+
+/** @class TextString
+ *  @brief A single line of subtitle or closed caption text with all the associated attributes.
+ */
+class TextString : public Text
+{
+public:
+       /** @param font Font ID, or empty to use the default
+        *  @param italic true for italic text
+        *  @param bold true for bold text
+        *  @param underline true for underlined text
+        *  @param colour Colour of the text
+        *  @param size Size in points as if the screen height is 11 inches, so a 72pt font would be 1/11th of the screen height
+        *  @param aspect_adjust greater than 1 to stretch text to be wider, less than 1 to shrink text to be narrower (must be between 0.25 and 4)
+        *  @param in start time
+        *  @param out finish time
+        *  @param h_position Horizontal position as a fraction of the screen width (between 0 and 1) from h_align
+        *  @param h_align Horizontal alignment point
+        *  @param v_position Vertical position as a fraction of the screen height (between 0 and 1) from v_align
+        *  @param v_align Vertical alignment point
+        *  @param z_position Z position as a proportion of the primary picture width between -1 and +1;
+        *  +ve moves the image away from the viewer, -ve moves it toward the viewer, 0 is in the plane of the screen.
+        *  @param direction Direction of text
+        *  @param text The text to display
+        *  @param effect Effect to use
+        *  @param effect_colour Colour of the effect
+        *  @param fade_up_time Time to fade the text in
+        *  @param fade_down_time Time to fade the text out
+        *  @param space_before Space to add before this string, in ems (could be negative to remove space).
+        */
+       TextString(
+               boost::optional<std::string> font,
+               bool italic,
+               bool bold,
+               bool underline,
+               Colour colour,
+               int size,
+               float aspect_adjust,
+               Time in,
+               Time out,
+               float h_position,
+               HAlign h_align,
+               float v_position,
+               VAlign v_align,
+               float z_position,
+               Direction direction,
+               std::string text,
+               Effect effect,
+               Colour effect_colour,
+               Time fade_up_time,
+               Time fade_down_time,
+               float space_before,
+               std::vector<Ruby> rubies
+               );
+
+       /** @return font ID */
+       boost::optional<std::string> font () const {
+               return _font;
+       }
+
+       bool italic () const {
+               return _italic;
+       }
+
+       bool bold () const {
+               return _bold;
+       }
+
+       bool underline () const {
+               return _underline;
+       }
+
+       Colour colour () const {
+               return _colour;
+       }
+
+       std::string text () const {
+               return _text;
+       }
+
+       Direction direction () const {
+               return _direction;
+       }
+
+       Effect effect () const {
+               return _effect;
+       }
+
+       Colour effect_colour () const {
+               return _effect_colour;
+       }
+
+       int size () const {
+               return _size;
+       }
+
+       float size_in_pixels (int screen_height) const;
+
+       float space_before () const {
+               return _space_before;
+       }
+
+       /** @return Aspect ratio `adjustment' of the font size.
+        *  Values greater than 1 widen each character, values less than 1 narrow each character,
+        *  and the value must be between 0.25 and 4.
+        */
+       float aspect_adjust () const {
+               return _aspect_adjust;
+       }
+
+       std::vector<Ruby> const& rubies() const {
+               return _rubies;
+       }
+
+       void set_font (std::string id) {
+               _font = id;
+       }
+
+       void unset_font () {
+               _font = boost::optional<std::string>();
+       }
+
+       void set_size (int s) {
+               _size = s;
+       }
+
+       void set_aspect_adjust (float a) {
+               _aspect_adjust = a;
+       }
+
+       void set_text (std::string t) {
+               _text = t;
+       }
+
+       void set_colour (Colour c) {
+               _colour = c;
+       }
+
+       void set_effect (Effect e) {
+               _effect = e;
+       }
+
+       void set_effect_colour (Colour c) {
+               _effect_colour = c;
+       }
+
+       void set_rubies(std::vector<Ruby> rubies) {
+               _rubies = std::move(rubies);
+       }
+
+       bool equals(std::shared_ptr<const dcp::Text> other_sub, EqualityOptions const& options, NoteHandler node) const override;
+
+private:
+       /** font ID */
+       boost::optional<std::string> _font;
+       /** true if the text is italic */
+       bool _italic;
+       /** true if the weight is bold, false for normal */
+       bool _bold;
+       /** true to enable underlining, false otherwise */
+       bool _underline;
+       /** text colour */
+       Colour _colour;
+       /** Size in points as if the screen height is 11 inches, so a 72pt font
+        *  would be 1/11th of the screen height.
+        */
+       int _size;
+       float _aspect_adjust;
+       Direction _direction;
+       std::string _text;
+       Effect _effect;
+       Colour _effect_colour;
+       float _space_before;
+       std::vector<Ruby> _rubies;
+};
+
+bool operator==(TextString const & a, TextString const & b);
+bool operator!=(TextString const & a, TextString const & b);
+std::ostream& operator<<(std::ostream& s, TextString const & sub);
+
+
+}
+
+
+#endif
+
index 960f0438816a3c726a604ecdcf692ac79fa5f4c5..4e9c29c365cb47f715a0817630dfacf27f5e823a 100644 (file)
@@ -42,7 +42,7 @@
 #include "dcp.h"
 #include "exceptions.h"
 #include "filesystem.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "mono_picture_asset.h"
 #include "mono_picture_frame.h"
 #include "raw_convert.h"
@@ -54,7 +54,7 @@
 #include "reel_sound_asset.h"
 #include "reel_smpte_subtitle_asset.h"
 #include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "stereo_picture_asset.h"
 #include "stereo_picture_frame.h"
 #include "verify.h"
@@ -670,7 +670,7 @@ verify_closed_caption_reel (shared_ptr<const ReelClosedCaptionAsset> reel_asset,
 /** Verify stuff that is common to both subtitles and closed captions */
 void
 verify_smpte_timed_text_asset (
-       shared_ptr<const SMPTESubtitleAsset> asset,
+       shared_ptr<const SMPTETextAsset> asset,
        optional<int64_t> reel_asset_duration,
        vector<VerificationNote>& notes
        )
@@ -720,9 +720,9 @@ verify_smpte_timed_text_asset (
 
 /** Verify Interop subtitle / CCAP stuff */
 void
-verify_interop_text_asset(shared_ptr<const InteropSubtitleAsset> asset, vector<VerificationNote>& notes)
+verify_interop_text_asset(shared_ptr<const InteropTextAsset> asset, vector<VerificationNote>& notes)
 {
-       if (asset->subtitles().empty()) {
+       if (asset->texts().empty()) {
                notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_SUBTITLE, asset->id(), asset->file().get() });
        }
        auto const unresolved = asset->unresolved_fonts();
@@ -735,7 +735,7 @@ verify_interop_text_asset(shared_ptr<const InteropSubtitleAsset> asset, vector<V
 /** Verify SMPTE subtitle-only stuff */
 void
 verify_smpte_subtitle_asset (
-       shared_ptr<const SMPTESubtitleAsset> asset,
+       shared_ptr<const SMPTETextAsset> asset,
        vector<VerificationNote>& notes,
        State& state
        )
@@ -778,7 +778,7 @@ verify_smpte_subtitle_asset (
 /** Verify all subtitle stuff */
 static void
 verify_subtitle_asset (
-       shared_ptr<const SubtitleAsset> asset,
+       shared_ptr<const TextAsset> asset,
        optional<int64_t> reel_asset_duration,
        function<void (string, optional<boost::filesystem::path>)> stage,
        boost::filesystem::path xsd_dtd_directory,
@@ -787,7 +787,7 @@ verify_subtitle_asset (
        )
 {
        stage ("Checking subtitle XML", asset->file());
-       /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
+       /* Note: we must not use TextAsset::xml_as_string() here as that will mean the data on disk
         * gets passed through libdcp which may clean up and therefore hide errors.
         */
        if (asset->raw_xml()) {
@@ -796,7 +796,7 @@ verify_subtitle_asset (
                notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED});
        }
 
-       auto namespace_count = [](shared_ptr<const SubtitleAsset> asset, string root_node) {
+       auto namespace_count = [](shared_ptr<const TextAsset> asset, string root_node) {
                cxml::Document doc(root_node);
                doc.read_string(asset->raw_xml().get());
                auto root = dynamic_cast<xmlpp::Element*>(doc.node())->cobj();
@@ -807,7 +807,7 @@ verify_subtitle_asset (
                return count;
        };
 
-       auto interop = dynamic_pointer_cast<const InteropSubtitleAsset>(asset);
+       auto interop = dynamic_pointer_cast<const InteropTextAsset>(asset);
        if (interop) {
                verify_interop_text_asset(interop, notes);
                if (namespace_count(asset, "DCSubtitle") > 1) {
@@ -815,7 +815,7 @@ verify_subtitle_asset (
                }
        }
 
-       auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset);
+       auto smpte = dynamic_pointer_cast<const SMPTETextAsset>(asset);
        if (smpte) {
                verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes);
                verify_smpte_subtitle_asset (smpte, notes, state);
@@ -830,7 +830,7 @@ verify_subtitle_asset (
 /** Verify all closed caption stuff */
 static void
 verify_closed_caption_asset (
-       shared_ptr<const SubtitleAsset> asset,
+       shared_ptr<const TextAsset> asset,
        optional<int64_t> reel_asset_duration,
        function<void (string, optional<boost::filesystem::path>)> stage,
        boost::filesystem::path xsd_dtd_directory,
@@ -838,7 +838,7 @@ verify_closed_caption_asset (
        )
 {
        stage ("Checking closed caption XML", asset->file());
-       /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
+       /* Note: we must not use TextAsset::xml_as_string() here as that will mean the data on disk
         * gets passed through libdcp which may clean up and therefore hide errors.
         */
        auto raw_xml = asset->raw_xml();
@@ -851,12 +851,12 @@ verify_closed_caption_asset (
                notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED});
        }
 
-       auto interop = dynamic_pointer_cast<const InteropSubtitleAsset>(asset);
+       auto interop = dynamic_pointer_cast<const InteropTextAsset>(asset);
        if (interop) {
                verify_interop_text_asset(interop, notes);
        }
 
-       auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset);
+       auto smpte = dynamic_pointer_cast<const SMPTETextAsset>(asset);
        if (smpte) {
                verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes);
        }
@@ -1161,7 +1161,7 @@ struct LinesCharactersResult
 static
 void
 verify_text_lines_and_characters (
-       shared_ptr<SubtitleAsset> asset,
+       shared_ptr<TextAsset> asset,
        int warning_length,
        int error_length,
        LinesCharactersResult* result
@@ -1189,7 +1189,7 @@ verify_text_lines_and_characters (
 
        vector<shared_ptr<Event>> events;
 
-       auto position = [](shared_ptr<const SubtitleString> sub) {
+       auto position = [](shared_ptr<const TextString> sub) {
                switch (sub->v_align()) {
                case VAlign::TOP:
                        return lrintf(sub->v_position() * 100);
@@ -1202,8 +1202,8 @@ verify_text_lines_and_characters (
                return 0L;
        };
 
-       for (auto j: asset->subtitles()) {
-               auto text = dynamic_pointer_cast<const SubtitleString>(j);
+       for (auto j: asset->texts()) {
+               auto text = dynamic_pointer_cast<const TextString>(j);
                if (text) {
                        auto in = make_shared<Event>(text->in(), position(text), text->text().length());
                        events.push_back(in);
index 3bc8537b3724e0f5d68c60649bbe87c0b74b3777..6a2ad9c2754be7fd3dff0b1a7e65fa149b94d80e 100644 (file)
@@ -64,7 +64,7 @@ def build(bld):
              h_align.cc
              identity_transfer_function.cc
              interop_load_font_node.cc
-             interop_subtitle_asset.cc
+             interop_text_asset.cc
              j2k_transcode.cc
              key.cc
              language_tag.cc
@@ -105,19 +105,19 @@ def build(bld):
              s_gamut3_transfer_function.cc
              search.cc
              smpte_load_font_node.cc
-             smpte_subtitle_asset.cc
+             smpte_text_asset.cc
              sound_asset.cc
              sound_asset_writer.cc
              sound_frame.cc
              stereo_picture_asset.cc
              stereo_picture_asset_writer.cc
              stereo_picture_frame.cc
-             subtitle.cc
-             subtitle_asset.cc
-             subtitle_asset_internal.cc
              subtitle_image.cc
-             subtitle_standard.cc
-             subtitle_string.cc
+             text.cc
+             text_asset.cc
+             text_asset_internal.cc
+             text_standard.cc
+             text_string.cc
              transfer_function.cc
              types.cc
              utc_offset.cc
@@ -166,7 +166,7 @@ def build(bld):
               h_align.h
               identity_transfer_function.h
               interop_load_font_node.h
-              interop_subtitle_asset.h
+              interop_text_asset.h
               j2k_transcode.h
               key.h
               language_tag.h
@@ -202,15 +202,15 @@ def build(bld):
               reel_picture_asset.h
               reel_sound_asset.h
               reel_smpte_closed_caption_asset.h
-              reel_smpte_subtitle_asset.h
+              reel_smpte_text_asset.h
               reel_stereo_picture_asset.h
-              reel_subtitle_asset.h
+              reel_text_asset.h
               ref.h
               ruby.h
               s_gamut3_transfer_function.h
               search.h
               smpte_load_font_node.h
-              smpte_subtitle_asset.h
+              smpte_text_asset.h
               sound_frame.h
               sound_asset.h
               sound_asset_reader.h
@@ -219,11 +219,11 @@ def build(bld):
               stereo_picture_asset_reader.h
               stereo_picture_asset_writer.h
               stereo_picture_frame.h
-              subtitle.h
-              subtitle_asset.h
               subtitle_image.h
-              subtitle_standard.h
-              subtitle_string.h
+              text.h
+              text_asset.h
+              text_standard.h
+              text_string.h
               transfer_function.h
               types.h
               utc_offset.h
index 12cbb6e2562fe87fb858e1e81457716d72006682..b04d62f1bc76a7e8ff2141b0cf75bf8a620f87a2 100644 (file)
@@ -36,7 +36,7 @@
 #include "cpl.h"
 #include "dcp.h"
 #include "equality_options.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "reel_subtitle_asset.h"
 #include "reel_mono_picture_asset.h"
 #include "reel_sound_asset.h"
@@ -448,7 +448,7 @@ BOOST_AUTO_TEST_CASE(combine_multi_reel_subtitles)
        dcp::ArrayData data1(4096);
        memset(data1.data(), 0, data1.size());
 
-       auto subs1 = make_shared<dcp::InteropSubtitleAsset>();
+       auto subs1 = make_shared<dcp::InteropTextAsset>();
        subs1->add(simple_subtitle());
        boost::filesystem::create_directory(in / "subs1");
        subs1->add_font("afont1", data1);
@@ -457,7 +457,7 @@ BOOST_AUTO_TEST_CASE(combine_multi_reel_subtitles)
        dcp::ArrayData data2(4096);
        memset(data2.data(), 1, data1.size());
 
-       auto subs2 = make_shared<dcp::InteropSubtitleAsset>();
+       auto subs2 = make_shared<dcp::InteropTextAsset>();
        subs2->add(simple_subtitle());
        boost::filesystem::create_directory(in / "subs2");
        subs2->add_font("afont2", data2);
index 7cb55c463abeed0848ee1f2dd2242a15ac83a123..482b25260637241ca3c7ea3c969d9adef1db9ea1 100644 (file)
 #include "cpl.h"
 #include "dcp.h"
 #include "file.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "reel.h"
 #include "reel_interop_subtitle_asset.h"
 #include "reel_smpte_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "test.h"
 #include "util.h"
 #include <boost/test/unit_test.hpp>
@@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE (interop_dcp_font_test)
        boost::filesystem::remove_all(directory);
        dcp::DCP dcp (directory);
 
-       auto subs = make_shared<dcp::InteropSubtitleAsset>();
+       auto subs = make_shared<dcp::InteropTextAsset>();
        subs->add_font ("theFontId", dcp::ArrayData("test/data/dummy.ttf"));
        subs->write (directory / "frobozz.xml");
        check_file ("test/data/dummy.ttf", "build/test/interop_dcp_font_test/font_0.ttf");
@@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE (interop_dcp_font_test)
 
        dcp::DCP dcp2 (directory);
        dcp2.read ();
-       auto subs2 = dynamic_pointer_cast<dcp::SubtitleAsset> (
+       auto subs2 = dynamic_pointer_cast<dcp::TextAsset> (
                dcp2.cpls()[0]->reels()[0]->main_subtitle()->asset_ref().asset()
                );
        BOOST_REQUIRE (subs2);
@@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE (smpte_dcp_font_test)
        boost::filesystem::path directory = "build/test/smpte_dcp_font_test";
        dcp::DCP dcp (directory);
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->add_font ("theFontId", dcp::ArrayData("test/data/dummy.ttf"));
        subs->write (directory / "frobozz.mxf");
 
@@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE (smpte_dcp_font_test)
 
        dcp::DCP dcp2 (directory);
        dcp2.read ();
-       auto subs2 = dynamic_pointer_cast<dcp::SubtitleAsset> (
+       auto subs2 = dynamic_pointer_cast<dcp::TextAsset> (
                dcp2.cpls().front()->reels().front()->main_subtitle()->asset_ref().asset()
                );
        BOOST_REQUIRE (subs2);
index 8f3dbff711295205cbe7c407f5d73ba137aef108..dd91da1e8b3f4d185518eadd861ca7ad2bc5a2d6 100644 (file)
@@ -48,7 +48,7 @@
 #include "reel_sound_asset.h"
 #include "reel_smpte_subtitle_asset.h"
 #include "rgb_xyz.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "sound_asset.h"
 #include "sound_asset_writer.h"
 #include "stream_operators.h"
@@ -178,10 +178,10 @@ BOOST_AUTO_TEST_CASE (decryption_test2)
        sound_writer->write(audio.data(), 2, 48000);
        sound_writer->finalize ();
 
-       auto subs_asset = std::make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs_asset = std::make_shared<dcp::SMPTETextAsset>();
        subs_asset->set_key (key);
        subs_asset->set_context_id (context_id);
-       subs_asset->add(std::make_shared<dcp::SubtitleString>(
+       subs_asset->add(std::make_shared<dcp::TextString>(
                optional<string>(),
                false, false, false,
                dcp::Colour(255, 255, 255),
@@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE (decryption_test2)
        BOOST_REQUIRE (reel_read->main_sound());
        BOOST_CHECK (reel_read->main_sound()->asset()->key());
        BOOST_REQUIRE (reel_read->main_subtitle());
-       auto smpte_sub = dynamic_pointer_cast<dcp::SMPTESubtitleAsset>(reel_read->main_subtitle()->asset());
+       auto smpte_sub = dynamic_pointer_cast<dcp::SMPTETextAsset>(reel_read->main_subtitle()->asset());
        BOOST_REQUIRE (smpte_sub);
        BOOST_CHECK (smpte_sub->key());
 }
index 358a3fd96fc7a67c855b5c49a8114de931b906f5..ccb698588ba81ba0e6778ae31345a14d1d66debf 100644 (file)
@@ -43,7 +43,7 @@
 #include "sound_asset.h"
 #include "reel.h"
 #include "test.h"
-#include "subtitle_asset.h"
+#include "text_asset.h"
 #include "reel_mono_picture_asset.h"
 #include "reel_sound_asset.h"
 #include "encrypted_kdm.h"
index 0efca007e28e3df7cfe885fc28c3dd92cd8d6ea7..0459d056a3433ad000af279faf1ebebaf6f9a6b2 100644 (file)
 */
 
 
-#include "interop_subtitle_asset.h"
 #include "interop_load_font_node.h"
+#include "interop_text_asset.h"
 #include "reel_interop_subtitle_asset.h"
-#include "subtitle_string.h"
 #include "subtitle_image.h"
+#include "text_string.h"
 #include "test.h"
 #include <boost/test/unit_test.hpp>
 #include <iostream>
@@ -51,7 +51,7 @@ using std::vector;
 /** Load some subtitle content from Interop XML and check that it is read correctly */
 BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
 {
-       dcp::InteropSubtitleAsset subs ("test/data/subs1.xml");
+       dcp::InteropTextAsset subs("test/data/subs1.xml");
 
        BOOST_CHECK_EQUAL (subs.id(), "cab5c268-222b-41d2-88ae-6d6999441b17");
        BOOST_CHECK_EQUAL (subs.movie_title(), "Movie Title");
@@ -65,10 +65,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
        BOOST_CHECK_EQUAL (interop_lfn->id, "theFontId");
        BOOST_CHECK_EQUAL (interop_lfn->uri, "arial.ttf");
 
-       auto s = subs.subtitles_during (dcp::Time (0, 0, 6, 1, 250), dcp::Time (0, 0, 6, 2, 250), false);
+       auto s = subs.texts_during(dcp::Time (0, 0, 6, 1, 250), dcp::Time (0, 0, 6, 2, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE(dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL(*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFontId"),
                                   false,
                                   false,
@@ -92,8 +92,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFontId"),
                                   false,
                                   false,
@@ -118,10 +118,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 0, 7, 190, 250), dcp::Time (0, 0, 7, 191, 250), false);
+       s = subs.texts_during(dcp::Time (0, 0, 7, 190, 250), dcp::Time (0, 0, 7, 191, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFontId"),
                                   true,
                                   false,
@@ -145,8 +145,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFontId"),
                                   false,
                                   false,
@@ -171,10 +171,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 0, 11, 95, 250), dcp::Time (0, 0, 11, 96, 250), false);
+       s = subs.texts_during(dcp::Time (0, 0, 11, 95, 250), dcp::Time (0, 0, 11, 96, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 1U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFontId"),
                                   false,
                                   false,
@@ -199,10 +199,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 0, 14, 42, 250), dcp::Time (0, 0, 14, 43, 250), false);
+       s = subs.texts_during(dcp::Time (0, 0, 14, 42, 250), dcp::Time (0, 0, 14, 43, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 1U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFontId"),
                                   false,
                                   true,
@@ -231,12 +231,12 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test1)
 /** And similarly for another one */
 BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
 {
-       dcp::InteropSubtitleAsset subs ("test/data/subs2.xml");
+       dcp::InteropTextAsset subs("test/data/subs2.xml");
 
-       auto s = subs.subtitles_during (dcp::Time (0, 0, 42, 100, 250), dcp::Time (0, 0, 42, 101, 250), false);
+       auto s = subs.texts_during(dcp::Time (0, 0, 42, 100, 250), dcp::Time (0, 0, 42, 101, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -260,8 +260,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -286,10 +286,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 0, 50, 50, 250), dcp::Time (0, 0, 50, 51, 250), false);
+       s = subs.texts_during(dcp::Time (0, 0, 50, 50, 250), dcp::Time (0, 0, 50, 51, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -313,8 +313,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -339,10 +339,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 1, 2, 300, 250), dcp::Time (0, 1, 2, 301, 250), false);
+       s = subs.texts_during(dcp::Time (0, 1, 2, 300, 250), dcp::Time (0, 1, 2, 301, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string("theFont"),
                                   true,
                                   false,
@@ -366,8 +366,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -392,10 +392,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 1, 15, 50, 250), dcp::Time (0, 1, 15, 51, 250), false);
+       s = subs.texts_during(dcp::Time (0, 1, 15, 50, 250), dcp::Time (0, 1, 15, 51, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -419,8 +419,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -445,10 +445,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 1, 27, 200, 250), dcp::Time (0, 1, 27, 201, 250), false);
+       s = subs.texts_during(dcp::Time (0, 1, 27, 200, 250), dcp::Time (0, 1, 27, 201, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -472,8 +472,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -498,10 +498,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 1, 42, 300, 250), dcp::Time (0, 1, 42, 301, 250), false);
+       s = subs.texts_during(dcp::Time (0, 1, 42, 300, 250), dcp::Time (0, 1, 42, 301, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   false,
                                   false,
@@ -525,8 +525,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   false,
                                   false,
@@ -551,10 +551,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 1, 45, 200, 250), dcp::Time (0, 1, 45, 201, 250), false);
+       s = subs.texts_during(dcp::Time (0, 1, 45, 200, 250), dcp::Time (0, 1, 45, 201, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string("theFont"),
                                   false,
                                   false,
@@ -578,8 +578,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   false,
                                   false,
@@ -604,10 +604,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 1, 47, 249, 250), dcp::Time (0, 1, 47, 250, 250), false);
+       s = subs.texts_during(dcp::Time (0, 1, 47, 249, 250), dcp::Time (0, 1, 47, 250, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   false,
                                   false,
@@ -631,8 +631,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   false,
                                   false,
@@ -657,10 +657,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   {}
                                   ));
 
-       s = subs.subtitles_during (dcp::Time (0, 2, 6, 210, 250), dcp::Time (0, 2, 6, 211, 250), false);
+       s = subs.texts_during(dcp::Time (0, 2, 6, 210, 250), dcp::Time (0, 2, 6, 211, 250), false);
        BOOST_REQUIRE_EQUAL (s.size(), 2U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.front()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.front()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.front()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.front()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -684,8 +684,8 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
                                   0,
                                   {}
                                   ));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(s.back()));
-       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::SubtitleString>(s.back()), dcp::SubtitleString (
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(s.back()));
+       BOOST_CHECK_EQUAL (*dynamic_pointer_cast<const dcp::TextString>(s.back()), dcp::TextString (
                                   string ("theFont"),
                                   true,
                                   false,
@@ -714,10 +714,10 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test2)
 /** And one with bitmap subtitles */
 BOOST_AUTO_TEST_CASE (read_interop_subtitle_test3)
 {
-       dcp::InteropSubtitleAsset subs ("test/data/subs3.xml");
+       dcp::InteropTextAsset subs ("test/data/subs3.xml");
 
-       BOOST_REQUIRE_EQUAL (subs.subtitles().size(), 1U);
-       auto si = dynamic_pointer_cast<const dcp::SubtitleImage>(subs.subtitles().front());
+       BOOST_REQUIRE_EQUAL(subs.texts().size(), 1U);
+       auto si = dynamic_pointer_cast<const dcp::SubtitleImage>(subs.texts().front());
        BOOST_REQUIRE (si);
        BOOST_CHECK (si->png_image() == dcp::ArrayData("test/data/sub.png"));
 }
@@ -726,13 +726,13 @@ BOOST_AUTO_TEST_CASE (read_interop_subtitle_test3)
 /** Write some subtitle content as Interop XML and check that it is right */
 BOOST_AUTO_TEST_CASE (write_interop_subtitle_test)
 {
-       dcp::InteropSubtitleAsset c;
+       dcp::InteropTextAsset c;
        c.set_reel_number ("1");
        c.set_language ("EN");
        c.set_movie_title ("Test");
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Frutiger"),
                        false,
                        false,
@@ -759,7 +759,7 @@ BOOST_AUTO_TEST_CASE (write_interop_subtitle_test)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        boost::optional<string> (),
                        true,
                        true,
@@ -786,7 +786,7 @@ BOOST_AUTO_TEST_CASE (write_interop_subtitle_test)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        boost::optional<string> (),
                        true,
                        true,
@@ -841,13 +841,13 @@ BOOST_AUTO_TEST_CASE (write_interop_subtitle_test)
  */
 BOOST_AUTO_TEST_CASE (write_interop_subtitle_test2)
 {
-       dcp::InteropSubtitleAsset c;
+       dcp::InteropTextAsset c;
        c.set_reel_number ("1");
        c.set_language ("EN");
        c.set_movie_title ("Test");
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Frutiger"),
                        false,
                        false,
@@ -874,7 +874,7 @@ BOOST_AUTO_TEST_CASE (write_interop_subtitle_test2)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        boost::optional<string>(),
                        true,
                        true,
@@ -929,7 +929,7 @@ BOOST_AUTO_TEST_CASE (write_interop_subtitle_test3)
 {
        RNGFixer fix;
 
-       auto c = std::make_shared<dcp::InteropSubtitleAsset>();
+       auto c = std::make_shared<dcp::InteropTextAsset>();
        c->set_reel_number ("1");
        c->set_language ("EN");
        c->set_movie_title ("Test");
index 6f06b4c96ba41025674ff049343895accf822a8c..b5deb356f09d4ad630fc2734a93ce0e1114caa33 100644 (file)
@@ -42,7 +42,7 @@
 #include "reel_mono_picture_asset.h"
 #include "reel_sound_asset.h"
 #include "reel_smpte_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "test.h"
 #include "types.h"
 #include "util.h"
@@ -325,7 +325,7 @@ BOOST_AUTO_TEST_CASE (vf_kdm_test)
 
        /* Make VF */
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->add(simple_subtitle());
        subs->set_key(key);
 
index 0a6f4c6cc171e64e676665ede800dd7966d3d6ec..11033a5e3d40accb017ab1ad384fcfc3bbbffc13 100644 (file)
     files in the program, then also delete it here.
 */
 
-#include <iostream>
-#include "dcp.h"
+
 #include "cpl.h"
+#include "dcp.h"
+#include "exceptions.h"
 #include "reel.h"
-#include "subtitle_asset.h"
 #include "reel_subtitle_asset.h"
-#include "exceptions.h"
+#include "text_asset.h"
+#include <iostream>
+
 
 using std::cout;
 using std::cerr;
index 7ac20e1071bf04f9e74b438db15b90e8a85f7769..f858de7d9a75025a9722317501f8cae15910297f 100644 (file)
  */
 
 
-#include "interop_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
-#include "subtitle_string.h"
+#include "interop_text_asset.h"
+#include "smpte_text_asset.h"
 #include "subtitle_image.h"
-#include "subtitle_asset_internal.h"
+#include "text_string.h"
+#include "text_asset_internal.h"
 #include "reel_interop_subtitle_asset.h"
 #include "reel.h"
 #include "cpl.h"
@@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE (pull_fonts_test1)
        text1->font._values["font"] = "Inconsolata";
        text1->font._values["size"] = "42";
 
-       dcp::SubtitleAsset::pull_fonts (root);
+       dcp::TextAsset::pull_fonts(root);
 
        BOOST_REQUIRE_EQUAL (sub1->font._values.size(), 2U);
        BOOST_CHECK_EQUAL (sub1->font._values["font"], "Inconsolata");
@@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE (pull_fonts_test2)
        text2->font._values["font"] = "Inconsolata";
        text2->font._values["size"] = "48";
 
-       dcp::SubtitleAsset::pull_fonts (root);
+       dcp::TextAsset::pull_fonts(root);
 
        BOOST_REQUIRE_EQUAL (sub1->font._values.size(), 1U);
        BOOST_CHECK_EQUAL (sub1->font._values["font"], "Inconsolata");
@@ -160,7 +160,7 @@ BOOST_AUTO_TEST_CASE (pull_fonts_test3)
        auto string1 = make_shared<dcp::order::String>(text1, font, "Hello world", 0);
        text1->children.push_back (string1);
 
-       dcp::SubtitleAsset::pull_fonts (root);
+       dcp::TextAsset::pull_fonts(root);
 
        BOOST_REQUIRE_EQUAL (sub1->font._values.size(), 2U);
        BOOST_CHECK_EQUAL (sub1->font._values["font"], "Inconsolata");
@@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE (format_xml_test1)
        fred->add_child_text("Fred");
        fred->add_child("Text")->add_child_text("Jim");
        fred->add_child_text("Sheila");
-       BOOST_REQUIRE_EQUAL (dcp::SubtitleAsset::format_xml(doc, make_pair(string{}, string{"fred"})),
+       BOOST_REQUIRE_EQUAL(dcp::TextAsset::format_xml(doc, make_pair(string{}, string{"fred"})),
 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 "<Foo xmlns=\"fred\">\n"
 "  <Empty/>\n"
@@ -202,7 +202,7 @@ BOOST_AUTO_TEST_CASE (format_xml_test2)
        auto path = private_test / "DKH_UT_EN20160601def.xml";
        parser.parse_file(path.string().c_str());
        auto document = parser.get_document();
-       check_xml (dcp::file_to_string(private_test / "DKH_UT_EN20160601def.reformatted.xml"), dcp::SubtitleAsset::format_xml(*document, {}), {});
+       check_xml(dcp::file_to_string(private_test / "DKH_UT_EN20160601def.reformatted.xml"), dcp::TextAsset::format_xml(*document, {}), {});
 }
 
 
@@ -211,7 +211,7 @@ BOOST_AUTO_TEST_CASE (format_xml_entities_test)
        xmlpp::Document doc;
        auto root = doc.create_root_node("Foo");
        root->add_child("Bar")->add_child_text("Don't panic &amp; xml \"is\" 'great' & < > â€”");
-       BOOST_REQUIRE_EQUAL(dcp::SubtitleAsset::format_xml(doc, {}),
+       BOOST_REQUIRE_EQUAL(dcp::TextAsset::format_xml(doc, {}),
 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 "<Foo>\n"
 "  <Bar>Don't panic &amp;amp; xml \"is\" 'great' &amp; &lt; &gt; â€”</Bar>\n"
@@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE (format_xml_entities_test)
 
 BOOST_AUTO_TEST_CASE(ruby_round_trip_test)
 {
-       dcp::InteropSubtitleAsset asset("test/data/ruby1.xml");
+       dcp::InteropTextAsset asset("test/data/ruby1.xml");
        check_xml(dcp::file_to_string("test/data/ruby1.xml"), asset.xml_as_string(), {}, false);
 }
 
index 3bf9dc5207a2cec205ca71ab1170fd055529d38b..9b1195924892512a5b5fb471c6e685f782b55aa5 100644 (file)
@@ -33,7 +33,7 @@
 
 
 #include "smpte_load_font_node.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "stream_operators.h"
 #include "subtitle_image.h"
 #include "test.h"
@@ -51,9 +51,9 @@ using boost::optional;
 
 BOOST_AUTO_TEST_CASE (smpte_subtitle_id_test)
 {
-       dcp::SMPTESubtitleAsset subs;
+       dcp::SMPTETextAsset subs;
        subs.add(
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        optional<string>(),
                        false, false, false,
                        dcp::Colour(),
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE (smpte_subtitle_id_test)
                );
        subs.write("build/test/smpte_subtitle_id_test.mxf");
 
-       dcp::SMPTESubtitleAsset check("build/test/smpte_subtitle_id_test.mxf");
+       dcp::SMPTETextAsset check("build/test/smpte_subtitle_id_test.mxf");
        BOOST_CHECK(check.id() != check.xml_id());
 }
 
@@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE (smpte_subtitle_id_test)
 /** Check reading of a SMPTE subtitle file */
 BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test)
 {
-       dcp::SMPTESubtitleAsset sc (
+       dcp::SMPTETextAsset sc(
                private_test /
                "data" /
                "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV" /
@@ -111,59 +111,59 @@ BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test)
        BOOST_REQUIRE (smpte_lfn);
        BOOST_CHECK_EQUAL (smpte_lfn->id, "theFontId");
        BOOST_CHECK_EQUAL (smpte_lfn->urn, "9118bbce-4105-4a05-b37c-a5a6f75e1fea");
-       BOOST_REQUIRE_EQUAL (sc.subtitles().size(), 63U);
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front()));
-       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front())->text(), "Noch mal.");
-       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().front())->space_before(), 0.0f);
-       BOOST_CHECK_EQUAL (sc.subtitles().front()->in(), dcp::Time (0, 0, 25, 12, 25));
-       BOOST_CHECK_EQUAL (sc.subtitles().front()->out(), dcp::Time (0, 0, 26, 4, 25));
-       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back()));
-       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back())->text(), "Prochainement");
-       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::SubtitleString>(sc.subtitles().back())->space_before(), 0.0f);
-       BOOST_CHECK_EQUAL (sc.subtitles().back()->in(), dcp::Time (0, 1, 57, 17, 25));
-       BOOST_CHECK_EQUAL (sc.subtitles().back()->out(), dcp::Time (0, 1, 58, 12, 25));
+       BOOST_REQUIRE_EQUAL(sc.texts().size(), 63U);
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(sc.texts().front()));
+       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::TextString>(sc.texts().front())->text(), "Noch mal.");
+       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::TextString>(sc.texts().front())->space_before(), 0.0f);
+       BOOST_CHECK_EQUAL (sc.texts().front()->in(), dcp::Time (0, 0, 25, 12, 25));
+       BOOST_CHECK_EQUAL (sc.texts().front()->out(), dcp::Time (0, 0, 26, 4, 25));
+       BOOST_REQUIRE (dynamic_pointer_cast<const dcp::TextString>(sc.texts().back()));
+       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::TextString>(sc.texts().back())->text(), "Prochainement");
+       BOOST_CHECK_EQUAL (dynamic_pointer_cast<const dcp::TextString>(sc.texts().back())->space_before(), 0.0f);
+       BOOST_CHECK_EQUAL (sc.texts().back()->in(), dcp::Time (0, 1, 57, 17, 25));
+       BOOST_CHECK_EQUAL (sc.texts().back()->out(), dcp::Time (0, 1, 58, 12, 25));
 }
 
 
 /** And another one featuring <Font> within <Text> and some <Space> */
 BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test2)
 {
-       dcp::SMPTESubtitleAsset sc (private_test / "olsson.xml");
+       dcp::SMPTETextAsset sc (private_test / "olsson.xml");
 
-       auto subs = sc.subtitles();
+       auto subs = sc.texts();
        BOOST_REQUIRE_EQUAL (subs.size(), 6U);
        auto i = 0;
-       auto is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
+       auto is = dynamic_pointer_cast<const dcp::TextString>(subs[i]);
        BOOST_REQUIRE (is);
        BOOST_CHECK_EQUAL (is->text(), "Testing is ");
        BOOST_CHECK (!is->italic());
        BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
        ++i;
-       is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
+       is = dynamic_pointer_cast<const dcp::TextString>(subs[i]);
        BOOST_REQUIRE (is);
        BOOST_CHECK_EQUAL (is->text(), "really");
        BOOST_CHECK (is->italic());
        BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
        ++i;
-       is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
+       is = dynamic_pointer_cast<const dcp::TextString>(subs[i]);
        BOOST_REQUIRE (is);
        BOOST_CHECK_EQUAL (is->text(), " fun!");
        BOOST_CHECK (!is->italic());
        BOOST_CHECK_CLOSE (is->space_before(), 5, 0.1);
        ++i;
-       is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
+       is = dynamic_pointer_cast<const dcp::TextString>(subs[i]);
        BOOST_REQUIRE (is);
        BOOST_CHECK_EQUAL (is->text(), "This is the ");
        BOOST_CHECK (!is->italic());
        BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
        ++i;
-       is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
+       is = dynamic_pointer_cast<const dcp::TextString>(subs[i]);
        BOOST_REQUIRE (is);
        BOOST_CHECK_EQUAL (is->text(), "second");
        BOOST_CHECK (is->italic());
        BOOST_CHECK_CLOSE (is->space_before(), 0, 0.1);
        ++i;
-       is = dynamic_pointer_cast<const dcp::SubtitleString>(subs[i]);
+       is = dynamic_pointer_cast<const dcp::TextString>(subs[i]);
        BOOST_REQUIRE (is);
        BOOST_CHECK_EQUAL (is->text(), " line!");
        BOOST_CHECK (!is->italic());
@@ -174,14 +174,14 @@ BOOST_AUTO_TEST_CASE (read_smpte_subtitle_test2)
 /* Write some subtitle content as SMPTE XML and check that it is right */
 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test)
 {
-       dcp::SMPTESubtitleAsset c;
+       dcp::SMPTETextAsset c;
        c.set_reel_number (1);
        c.set_language (dcp::LanguageTag("en"));
        c.set_content_title_text ("Test");
        c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
 
        c.add (
-               std::make_shared<dcp::SubtitleString> (
+               std::make_shared<dcp::TextString> (
                        string ("Frutiger"),
                        false,
                        false,
@@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        boost::optional<string> (),
                        true,
                        true,
@@ -235,7 +235,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        boost::optional<string> (),
                        true,
                        true,
@@ -296,14 +296,14 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test)
 */
 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
 {
-       dcp::SMPTESubtitleAsset c;
+       dcp::SMPTETextAsset c;
        c.set_reel_number (1);
        c.set_language (dcp::LanguageTag("en"));
        c.set_content_title_text ("Test");
        c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -330,7 +330,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        true,
                        false,
@@ -357,7 +357,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -384,7 +384,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -411,7 +411,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        true,
                        false,
@@ -438,7 +438,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -493,7 +493,7 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test2)
 /* Write some subtitle content as SMPTE using bitmaps and check that it is right */
 BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test3)
 {
-       dcp::SMPTESubtitleAsset c;
+       dcp::SMPTETextAsset c;
        c.set_reel_number (1);
        c.set_language (dcp::LanguageTag("en"));
        c.set_content_title_text ("Test");
@@ -522,8 +522,8 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test3)
        boost::filesystem::create_directories (path);
        c.write (path / "subs.mxf");
 
-       dcp::SMPTESubtitleAsset read_back (path / "subs.mxf");
-       auto subs = read_back.subtitles ();
+       dcp::SMPTETextAsset read_back (path / "subs.mxf");
+       auto subs = read_back.texts();
        BOOST_REQUIRE_EQUAL (subs.size(), 1U);
        auto image = dynamic_pointer_cast<const dcp::SubtitleImage>(subs[0]);
        BOOST_REQUIRE (image);
@@ -546,14 +546,14 @@ BOOST_AUTO_TEST_CASE (write_smpte_subtitle_test3)
  */
 BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_top_alignment)
 {
-       dcp::SMPTESubtitleAsset c;
+       dcp::SMPTETextAsset c;
        c.set_reel_number (1);
        c.set_language (dcp::LanguageTag("en"));
        c.set_content_title_text ("Test");
        c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -580,7 +580,7 @@ BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_top_alignment)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -636,14 +636,14 @@ BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_top_alignment)
 /* See the test above */
 BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_bottom_alignment)
 {
-       dcp::SMPTESubtitleAsset c;
+       dcp::SMPTETextAsset c;
        c.set_reel_number (1);
        c.set_language (dcp::LanguageTag("en"));
        c.set_content_title_text ("Test");
        c.set_issue_date (dcp::LocalTime ("2016-04-01T03:52:00+00:00"));
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -670,7 +670,7 @@ BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_bottom_alignment)
                );
 
        c.add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        string ("Arial"),
                        false,
                        false,
@@ -723,49 +723,49 @@ BOOST_AUTO_TEST_CASE (write_subtitles_in_vertical_order_with_bottom_alignment)
 }
 
 
-BOOST_AUTO_TEST_CASE(smpte_subtitle_standard_written_correctly)
+BOOST_AUTO_TEST_CASE(smpte_text_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::path const out = "build/test/smpte_text_standard_written_correctly";
 
        boost::filesystem::remove_all(out);
        boost::filesystem::create_directories(out);
 
-       dcp::SMPTESubtitleAsset test_2014;
+       dcp::SMPTETextAsset test_2014;
        test_2014.set_issue_date(dcp::LocalTime("2020-01-01T14:00:00"));
        test_2014.write(out / "2014.mxf");
-       BOOST_CHECK_EQUAL(dcp::SMPTESubtitleAsset(ref / "2014.mxf").raw_xml(), dcp::SMPTESubtitleAsset(out / "2014.mxf").raw_xml());
+       BOOST_CHECK_EQUAL(dcp::SMPTETextAsset(ref / "2014.mxf").raw_xml(), dcp::SMPTETextAsset(out / "2014.mxf").raw_xml());
 
-       dcp::SMPTESubtitleAsset test_2010(dcp::SubtitleStandard::SMPTE_2010);
+       dcp::SMPTETextAsset test_2010(dcp::TextStandard::SMPTE_2010);
        test_2010.set_issue_date(dcp::LocalTime("2020-01-01T14:00:00"));
        test_2010.write(out / "2010.mxf");
-       BOOST_CHECK_EQUAL(dcp::SMPTESubtitleAsset(ref / "2010.mxf").raw_xml(), dcp::SMPTESubtitleAsset(out / "2010.mxf").raw_xml());
+       BOOST_CHECK_EQUAL(dcp::SMPTETextAsset(ref / "2010.mxf").raw_xml(), dcp::SMPTETextAsset(out / "2010.mxf").raw_xml());
 
-       dcp::SMPTESubtitleAsset test_2007(dcp::SubtitleStandard::SMPTE_2007);
+       dcp::SMPTETextAsset test_2007(dcp::TextStandard::SMPTE_2007);
        test_2007.set_issue_date(dcp::LocalTime("2020-01-01T14:00:00"));
        test_2007.write(out / "2007.mxf");
-       BOOST_CHECK_EQUAL(dcp::SMPTESubtitleAsset(ref / "2007.mxf").raw_xml(), dcp::SMPTESubtitleAsset(out / "2007.mxf").raw_xml());
+       BOOST_CHECK_EQUAL(dcp::SMPTETextAsset(ref / "2007.mxf").raw_xml(), dcp::SMPTETextAsset(out / "2007.mxf").raw_xml());
 }
 
 
-BOOST_AUTO_TEST_CASE(smpte_subtitle_standard_read_correctly)
+BOOST_AUTO_TEST_CASE(smpte_text_standard_read_correctly)
 {
-       dcp::SMPTESubtitleAsset test_2007("test/data/2007.mxf");
-       BOOST_CHECK(test_2007.subtitle_standard() == dcp::SubtitleStandard::SMPTE_2007);
+       dcp::SMPTETextAsset test_2007("test/data/2007.mxf");
+       BOOST_CHECK(test_2007.text_standard() == dcp::TextStandard::SMPTE_2007);
 
-       dcp::SMPTESubtitleAsset test_2010("test/data/2010.mxf");
-       BOOST_CHECK(test_2010.subtitle_standard() == dcp::SubtitleStandard::SMPTE_2010);
+       dcp::SMPTETextAsset test_2010("test/data/2010.mxf");
+       BOOST_CHECK(test_2010.text_standard() == dcp::TextStandard::SMPTE_2010);
 
-       dcp::SMPTESubtitleAsset test_2014("test/data/2014.mxf");
-       BOOST_CHECK(test_2014.subtitle_standard() == dcp::SubtitleStandard::SMPTE_2014);
+       dcp::SMPTETextAsset test_2014("test/data/2014.mxf");
+       BOOST_CHECK(test_2014.text_standard() == dcp::TextStandard::SMPTE_2014);
 }
 
 
 BOOST_AUTO_TEST_CASE(smpte_subtitle_intrinsic_duration_read_correctly)
 {
-       dcp::SMPTESubtitleAsset ref("test/data/verify_incorrect_closed_caption_ordering3.xml");
+       dcp::SMPTETextAsset ref("test/data/verify_incorrect_closed_caption_ordering3.xml");
 
        dcp::Key key;
        ref.set_key(key);
@@ -777,7 +777,7 @@ BOOST_AUTO_TEST_CASE(smpte_subtitle_intrinsic_duration_read_correctly)
        auto const path = boost::filesystem::path("build/test/smpte_subtitle_instrinsic_duration_read_correctly.mxf");
        ref.write(path);
 
-       auto check = dcp::SMPTESubtitleAsset(path);
+       auto check = dcp::SMPTETextAsset(path);
        check.set_key(key);
        BOOST_CHECK_EQUAL(check.intrinsic_duration(), duration);
 }
index 7ca45f3563f0fc27f796acd240bc488baee7aa0f..ec130393f5b409c13a0c978ca4b447a35027c299 100644 (file)
@@ -31,8 +31,8 @@
     files in the program, then also delete it here.
 */
 
-#include "interop_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "interop_text_asset.h"
+#include "smpte_text_asset.h"
 #include <iostream>
 
 using namespace std;
@@ -46,12 +46,12 @@ main (int argc, char* argv[])
        }
 
        try {
-               dcp::InteropSubtitleAsset sc (argv[1]);
+               dcp::InteropTextAsset sc (argv[1]);
                cout << sc.xml_as_string ();
        } catch (exception& e) {
                cerr << "Could not load as interop: " << e.what() << "\n";
                try {
-                       dcp::SMPTESubtitleAsset sc (argv[1]);
+                       dcp::SMPTETextAsset sc(argv[1]);
                        cout << sc.xml_as_string();
                } catch (exception& e) {
                        cerr << "Could not load as SMPTE (" << e.what() << ")\n";
index d384bdfa4fe8593616def05c305a15a0752119f6..12b1016ee3a1a0a98d181f9241507dc981f946ed 100644 (file)
@@ -36,7 +36,7 @@
 #include "compose.hpp"
 #include "cpl.h"
 #include "dcp.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "file.h"
 #include "j2k_transcode.h"
 #include "mono_picture_asset.h"
@@ -54,7 +54,7 @@
 #include "reel_smpte_closed_caption_asset.h"
 #include "reel_smpte_subtitle_asset.h"
 #include "reel_sound_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "sound_asset.h"
 #include "sound_asset_writer.h"
 #include "test.h"
@@ -393,10 +393,10 @@ make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard
 }
 
 
-shared_ptr<dcp::Subtitle>
+shared_ptr<dcp::Text>
 simple_subtitle ()
 {
-       return std::make_shared<dcp::SubtitleString>(
+       return std::make_shared<dcp::TextString>(
                optional<string>(),
                false,
                false,
@@ -438,7 +438,7 @@ make_simple_with_interop_subs (boost::filesystem::path path)
 {
        auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
 
-       auto subs = make_shared<dcp::InteropSubtitleAsset>();
+       auto subs = make_shared<dcp::InteropTextAsset>();
        subs->add (simple_subtitle());
 
        boost::filesystem::create_directory (path / "subs");
@@ -459,7 +459,7 @@ make_simple_with_smpte_subs (boost::filesystem::path path)
 {
        auto dcp = make_simple (path, 1, 192);
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->set_language (dcp::LanguageTag("de-DE"));
        subs->set_start_time (dcp::Time());
        subs->add (simple_subtitle());
@@ -480,7 +480,7 @@ make_simple_with_interop_ccaps (boost::filesystem::path path)
 {
        auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
 
-       auto subs = make_shared<dcp::InteropSubtitleAsset>();
+       auto subs = make_shared<dcp::InteropTextAsset>();
        subs->add (simple_subtitle());
        subs->write (path / "ccap.xml");
 
@@ -496,7 +496,7 @@ make_simple_with_smpte_ccaps (boost::filesystem::path path)
 {
        auto dcp = make_simple (path, 1, 192);
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->set_language (dcp::LanguageTag("de-DE"));
        subs->set_start_time (dcp::Time());
        subs->add (simple_subtitle());
index bacb9311db9175d7fb21ebfd1804e77a73212a8d..18806f4dcb79fada3e538229e1e6a57888bad61a 100644 (file)
 #include "dcp.h"
 #include "reel.h"
 #include "reel_subtitle_asset.h"
-#include "subtitle.h"
 #include "reel_asset.h"
+#include "text.h"
 #include <boost/filesystem.hpp>
 #include <boost/optional.hpp>
 #include <boost/test/unit_test.hpp>
 
+
 namespace xmlpp {
        class Element;
 }
@@ -60,7 +61,7 @@ extern std::shared_ptr<dcp::SoundAsset> simple_sound (
        boost::optional<dcp::Key> key = boost::optional<dcp::Key>(),
        int channels = 6
        );
-extern std::shared_ptr<dcp::Subtitle> simple_subtitle ();
+extern std::shared_ptr<dcp::Text> simple_subtitle();
 extern std::shared_ptr<dcp::ReelMarkersAsset> simple_markers (int frames = 24);
 extern std::shared_ptr<dcp::DCP> make_simple (
        boost::filesystem::path path,
index 47b5cc787908876e280787a7728b02579eb787bc..d7615ed2353f615c63c8261acc1f00c9d15f2e5a 100644 (file)
@@ -36,7 +36,7 @@
 #include "cpl.h"
 #include "dcp.h"
 #include "file.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "j2k_transcode.h"
 #include "mono_picture_asset.h"
 #include "mono_picture_asset_writer.h"
@@ -51,7 +51,7 @@
 #include "reel_stereo_picture_asset.h"
 #include "reel_smpte_closed_caption_asset.h"
 #include "reel_smpte_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "stereo_picture_asset.h"
 #include "stream_operators.h"
 #include "test.h"
@@ -262,7 +262,7 @@ check_verify_result_after_replace (string suffix, boost::function<path (string)>
 
 static
 void
-add_font(shared_ptr<dcp::SubtitleAsset> asset)
+add_font(shared_ptr<dcp::TextAsset> asset)
 {
        dcp::ArrayData fake_font(1024);
        asset->add_font("font", fake_font);
@@ -695,7 +695,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
        path const dir("build/test/verify_valid_interop_subtitles");
        prepare_directory (dir);
        copy_file ("test/data/subs1.xml", dir / "subs.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -714,7 +714,7 @@ BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
        path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
        prepare_directory(dir);
        copy_file("test/data/subs1.xml", dir / "ccap.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "ccap.xml");
        auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -735,7 +735,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
        path const dir("build/test/verify_invalid_interop_subtitles");
        prepare_directory (dir);
        copy_file ("test/data/subs1.xml", dir / "subs.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -767,7 +767,7 @@ BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
        path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
        prepare_directory(dir);
        copy_file("test/data/subs4.xml", dir / "subs.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -788,7 +788,7 @@ BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
        path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
        prepare_directory(dir);
        copy_file("test/data/subs5.xml", dir / "subs.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -808,7 +808,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
        path const dir("build/test/verify_valid_smpte_subtitles");
        prepare_directory (dir);
        copy_file ("test/data/subs.mxf", dir / "subs.mxf");
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
+       auto asset = make_shared<dcp::SMPTETextAsset>(dir / "subs.mxf");
        auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
        auto cpl = write_dcp_with_single_asset (dir, reel_asset);
 
@@ -831,7 +831,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
        prepare_directory (dir);
        /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
        copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
+       auto asset = make_shared<dcp::SMPTETextAsset>(dir / "subs.mxf");
        auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
        auto cpl = write_dcp_with_single_asset (dir, reel_asset);
 
@@ -860,7 +860,7 @@ BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
        path const dir("build/test/verify_empty_text_node_in_subtitles");
        prepare_directory (dir);
        copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
+       auto asset = make_shared<dcp::SMPTETextAsset>(dir / "subs.mxf");
        auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
        auto cpl = write_dcp_with_single_asset (dir, reel_asset);
 
@@ -884,7 +884,7 @@ BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
        path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
        prepare_directory (dir);
        copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
        auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -904,7 +904,7 @@ BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes
        path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
        prepare_directory (dir);
        copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
        auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -955,7 +955,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
        prepare_directory (dir);
 
        copy_file ("test/data/subs.mxf", dir / "subs.mxf");
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
+       auto asset = make_shared<dcp::SMPTETextAsset>(dir / "subs.mxf");
        auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
 
        auto reel = make_shared<dcp::Reel>();
@@ -1102,7 +1102,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_language1)
        path const dir("build/test/verify_invalid_language1");
        prepare_directory (dir);
        copy_file ("test/data/subs.mxf", dir / "subs.mxf");
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
+       auto asset = make_shared<dcp::SMPTETextAsset>(dir / "subs.mxf");
        asset->_language = "wrong-andbad";
        asset->write (dir / "subs.mxf");
        auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
@@ -1126,7 +1126,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_language2)
        path const dir("build/test/verify_invalid_language2");
        prepare_directory (dir);
        copy_file ("test/data/subs.mxf", dir / "subs.mxf");
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
+       auto asset = make_shared<dcp::SMPTETextAsset>(dir / "subs.mxf");
        asset->_language = "wrong-andbad";
        asset->write (dir / "subs.mxf");
        auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
@@ -1336,10 +1336,10 @@ BOOST_AUTO_TEST_CASE (verify_picture_size)
 
 static
 void
-add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello")
+add_test_subtitle(shared_ptr<dcp::TextAsset> asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello")
 {
        asset->add (
-               std::make_shared<dcp::SubtitleString>(
+               std::make_shared<dcp::TextString>(
                        optional<string>(),
                        false,
                        false,
@@ -1372,7 +1372,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
        path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
        prepare_directory (dir);
 
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>();
+       auto asset = make_shared<dcp::SMPTETextAsset>();
        for (int i = 0; i < 2048; ++i) {
                add_test_subtitle (asset, i * 24, i * 24 + 20);
        }
@@ -1400,10 +1400,10 @@ BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
 
 
 static
-shared_ptr<dcp::SMPTESubtitleAsset>
+shared_ptr<dcp::SMPTETextAsset>
 make_large_subtitle_asset (path font_file)
 {
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>();
+       auto asset = make_shared<dcp::SMPTETextAsset>();
        dcp::ArrayData big_fake_font(1024 * 1024);
        big_fake_font.write (font_file);
        for (int i = 0; i < 116; ++i) {
@@ -1478,7 +1478,7 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
        BOOST_REQUIRE (xml_file);
        xml_file.write(xml.c_str(), xml.size(), 1);
        xml_file.close();
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
+       auto subs = make_shared<dcp::SMPTETextAsset>(dir / "subs.xml");
        subs->write (dir / "subs.mxf");
 
        auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
@@ -1503,7 +1503,7 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
        auto cpl = dcp->cpls()[0];
 
        {
-               auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+               auto subs = make_shared<dcp::SMPTETextAsset>();
                subs->set_language (dcp::LanguageTag("de-DE"));
                subs->add (simple_subtitle());
                add_font(subs);
@@ -1513,7 +1513,7 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
        }
 
        {
-               auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+               auto subs = make_shared<dcp::SMPTETextAsset>();
                subs->set_language (dcp::LanguageTag("en-US"));
                subs->add (simple_subtitle());
                add_font(subs);
@@ -1543,7 +1543,7 @@ BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
        auto cpl = dcp->cpls()[0];
 
        {
-               auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
+               auto ccaps = make_shared<dcp::SMPTETextAsset>();
                ccaps->set_language (dcp::LanguageTag("de-DE"));
                ccaps->add (simple_subtitle());
                add_font(ccaps);
@@ -1553,7 +1553,7 @@ BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
        }
 
        {
-               auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
+               auto ccaps = make_shared<dcp::SMPTETextAsset>();
                ccaps->set_language (dcp::LanguageTag("en-US"));
                ccaps->add (simple_subtitle());
                add_font(ccaps);
@@ -1605,7 +1605,7 @@ BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
        BOOST_REQUIRE (xml_file);
        xml_file.write(xml.c_str(), xml.size(), 1);
        xml_file.close();
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
+       auto subs = make_shared<dcp::SMPTETextAsset>(dir / "subs.xml");
        subs->write (dir / "subs.mxf");
 
        auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
@@ -1654,7 +1654,7 @@ BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
        BOOST_REQUIRE (xml_file);
        xml_file.write(xml.c_str(), xml.size(), 1);
        xml_file.close();
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
+       auto subs = make_shared<dcp::SMPTETextAsset>(dir / "subs.xml");
        subs->write (dir / "subs.mxf");
 
        auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
@@ -1695,7 +1695,7 @@ shared_ptr<dcp::CPL>
 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
 {
        prepare_directory (dir);
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>();
+       auto asset = make_shared<dcp::SMPTETextAsset>();
        asset->set_start_time (dcp::Time());
        for (auto i: subs) {
                add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
@@ -1718,7 +1718,7 @@ shared_ptr<dcp::CPL>
 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
 {
        prepare_directory (dir);
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
+       auto asset = make_shared<dcp::SMPTETextAsset>(subs_xml);
        asset->set_start_time (dcp::Time());
        asset->set_language (dcp::LanguageTag("de-DE"));
 
@@ -1780,7 +1780,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
        auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
        prepare_directory (dir);
 
-       auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
+       auto asset1 = make_shared<dcp::SMPTETextAsset>();
        asset1->set_start_time (dcp::Time());
        /* Just late enough */
        add_test_subtitle (asset1, 4 * 24, 5 * 24);
@@ -1794,7 +1794,7 @@ BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
        markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
        reel1->add (markers1);
 
-       auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
+       auto asset2 = make_shared<dcp::SMPTETextAsset>();
        asset2->set_start_time (dcp::Time());
        add_font(asset2);
        /* This would be too early on first reel but should be OK on the second */
@@ -1878,7 +1878,7 @@ BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
 {
        auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
        prepare_directory (dir);
-       auto asset = make_shared<dcp::SMPTESubtitleAsset>();
+       auto asset = make_shared<dcp::SMPTETextAsset>();
        asset->set_start_time (dcp::Time());
        add_test_subtitle (asset, 0, 4 * 24);
        add_font(asset);
@@ -2342,7 +2342,7 @@ verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool a
 
        auto constexpr reel_length = 192;
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->set_language (dcp::LanguageTag("de-DE"));
        subs->set_start_time (dcp::Time());
        subs->add (simple_subtitle());
@@ -2427,7 +2427,7 @@ verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1,
 
        auto constexpr reel_length = 192;
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->set_language (dcp::LanguageTag("de-DE"));
        subs->set_start_time (dcp::Time());
        subs->add (simple_subtitle());
@@ -2510,7 +2510,7 @@ verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost
 
        auto constexpr reel_length = 192;
 
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       auto subs = make_shared<dcp::SMPTETextAsset>();
        subs->set_language (dcp::LanguageTag("de-DE"));
        subs->set_start_time (dcp::Time());
        subs->add (simple_subtitle());
@@ -3177,7 +3177,7 @@ BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
 
        writer.Finalize();
 
-       auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
+       auto subs_asset = make_shared<dcp::SMPTETextAsset>(subs_mxf);
        auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
 
        auto cpl = write_dcp_with_single_asset (dir, subs_reel);
@@ -3243,7 +3243,7 @@ BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
 
        writer.Finalize();
 
-       auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
+       auto subs_asset = make_shared<dcp::SMPTETextAsset>(subs_mxf);
        auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
 
        auto cpl = write_dcp_with_single_asset (dir, subs_reel);
@@ -3683,7 +3683,7 @@ BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
                Editor editor(dir / "subs.xml");
                editor.delete_first_line_containing("LoadFont");
        }
-       auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
+       auto asset = make_shared<dcp::InteropTextAsset>(dir / "subs.xml");
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
 
@@ -3729,7 +3729,7 @@ BOOST_AUTO_TEST_CASE(verify_missing_load_font)
        BOOST_REQUIRE(xml_file);
        xml_file.write(xml.c_str(), xml.size(), 1);
        xml_file.close();
-       auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
+       auto subs = make_shared<dcp::SMPTETextAsset>(dir / "subs.xml");
        subs->write(dir / "subs.mxf");
 
        auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
index fa7a170ceaa471d32400fca5fe565d8b89250eb2..a14d497802095d0ee578f84eb0fc1a9fb63cf112 100644 (file)
@@ -36,7 +36,7 @@
 #include "decrypted_kdm_key.h"
 #include "encrypted_kdm.h"
 #include "file.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "util.h"
 #include <getopt.h>
 #include <cstdlib>
@@ -107,7 +107,7 @@ main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
-       dcp::SMPTESubtitleAsset sub (argv[optind]);
+       dcp::SMPTETextAsset sub (argv[optind]);
 
        if (sub.key_id() && (!kdm_file || !private_key_file)) {
                cerr << "Subtitle MXF is encrypted so you must provide a KDM and private key.\n";
index e812afe40775161afc2c1370a951bb4b56ea2b92..648140d88689c18de7280e52a103c4bbf081a0ed 100644 (file)
 #include "encrypted_kdm.h"
 #include "exceptions.h"
 #include "filesystem.h"
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
 #include "mono_picture_asset.h"
 #include "picture_asset.h"
 #include "reel.h"
 #include "reel_picture_asset.h"
 #include "reel_sound_asset.h"
 #include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
 #include "sound_asset.h"
-#include "subtitle_asset.h"
 #include "subtitle_image.h"
-#include "subtitle_string.h"
+#include "text_asset.h"
+#include "text_string.h"
 #include <getopt.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
@@ -251,19 +251,19 @@ main_subtitle (vector<string> const& only, shared_ptr<Reel> reel, bool list_subt
        OUTPUT_SUBTITLE("      Subtitle ID: %1", ms->id());
 
        if (ms->asset_ref().resolved()) {
-               auto subs = ms->asset()->subtitles ();
+               auto subs = ms->asset()->texts();
                OUTPUT_SUBTITLE("\n      Subtitle:    %1 subtitles", subs.size());
-               shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
+               auto iop = dynamic_pointer_cast<InteropTextAsset>(ms->asset());
                if (iop) {
                        OUTPUT_SUBTITLE(" in %1\n", iop->language());
                }
-               shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (ms->asset());
+               auto smpte = dynamic_pointer_cast<SMPTETextAsset>(ms->asset());
                if (smpte && smpte->language ()) {
                        OUTPUT_SUBTITLE(" in %1\n", smpte->language().get());
                }
                if (list_subtitles) {
                        for (auto k: subs) {
-                               auto ks = dynamic_pointer_cast<const SubtitleString>(k);
+                               auto ks = dynamic_pointer_cast<const TextString>(k);
                                if (ks) {
                                        stringstream s;
                                        s << *ks;