#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"
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";
}
#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"
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:
#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>
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,
#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"
#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"
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());
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;
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());
}
+++ /dev/null
-/*
- 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;
-}
-
+++ /dev/null
-/*
- 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
-
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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
+
#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>
/* 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);
}
/* 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);
}
#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>
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,
#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;
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;
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)
{
#define LIBDCP_REEL_INTEROP_CLOSED_CAPTION_ASSET_H
-#include "interop_subtitle_asset.h"
+#include "interop_text_asset.h"
#include "reel_closed_caption_asset.h"
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;
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)
{
*/
-/** @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"
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>();
}
};
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)
{
#include "reel_file_asset.h"
#include "reel_closed_caption_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
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;
#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>
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)
{
#include "reel_subtitle_asset.h"
-#include "smpte_subtitle_asset.h"
+#include "smpte_text_asset.h"
namespace dcp {
-class SMPTESubtitleAsset;
+class SMPTETextAsset;
/** @class ReelSMPTESubtitleAsset
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:
#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>
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,
#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;
namespace dcp {
-class SubtitleAsset;
+class TextAsset;
/** @class ReelSubtitleAsset
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;
+++ /dev/null
-/*
- 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);
-}
+++ /dev/null
-/*
- 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
--- /dev/null
+/*
+ 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);
+}
--- /dev/null
+/*
+ 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
+++ /dev/null
-/*
- 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;
-}
+++ /dev/null
-/*
- 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
+++ /dev/null
-/*
- 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, "&", "&");
- boost::replace_all(content, "<", "<");
- boost::replace_all(content, ">", ">");
- 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);
- }
-}
-
+++ /dev/null
-/*
- 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
+++ /dev/null
-/*
- 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;
-}
+++ /dev/null
-/*
- 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
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 ())
{
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)
{
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;
}
#include "array_data.h"
-#include "subtitle.h"
#include "dcp_time.h"
+#include "text.h"
#include <boost/optional.hpp>
#include <string>
/** @class SubtitleImage
* @brief A bitmap subtitle with all the associated attributes
*/
-class SubtitleImage : public Subtitle
+class SubtitleImage : public Text
{
public:
SubtitleImage (
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;
+++ /dev/null
-/*
- 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;
-}
-
-
+++ /dev/null
-/*
- 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
-
+++ /dev/null
-/*
- 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;
-}
-
+++ /dev/null
-/*
- 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
-
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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, "&", "&");
+ boost::replace_all(content, "<", "<");
+ boost::replace_all(content, ">", ">");
+ 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);
+ }
+}
+
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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;
+}
+
+
--- /dev/null
+/*
+ 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
+
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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
+
#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"
#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"
/** 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
)
/** 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();
/** 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
)
/** 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,
)
{
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()) {
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();
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) {
}
}
- 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);
/** 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,
)
{
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();
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);
}
static
void
verify_text_lines_and_characters (
- shared_ptr<SubtitleAsset> asset,
+ shared_ptr<TextAsset> asset,
int warning_length,
int error_length,
LinesCharactersResult* result
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);
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);
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
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
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
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
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
#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"
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);
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);
#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>
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");
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);
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");
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);
#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"
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),
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());
}
#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"
*/
-#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>
/** 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");
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,
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,
{}
));
- 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,
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,
{}
));
- 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,
{}
));
- 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,
/** 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
{}
));
- 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,
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,
/** 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"));
}
/** 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,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
boost::optional<string> (),
true,
true,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
boost::optional<string> (),
true,
true,
*/
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,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
boost::optional<string>(),
true,
true,
{
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");
#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"
/* Make VF */
- auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+ auto subs = make_shared<dcp::SMPTETextAsset>();
subs->add(simple_subtitle());
subs->set_key(key);
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;
*/
-#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"
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");
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");
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");
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"
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, {}), {});
}
xmlpp::Document doc;
auto root = doc.create_root_node("Foo");
root->add_child("Bar")->add_child_text("Don't panic & 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; xml \"is\" 'great' & < > —</Bar>\n"
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);
}
#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"
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(),
);
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());
}
/** 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" /
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());
/* 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,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
boost::optional<string> (),
true,
true,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
boost::optional<string> (),
true,
true,
*/
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,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
true,
false,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
false,
false,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
false,
false,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
true,
false,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
false,
false,
/* 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");
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);
*/
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,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
false,
false,
/* 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,
);
c.add (
- std::make_shared<dcp::SubtitleString>(
+ std::make_shared<dcp::TextString>(
string ("Arial"),
false,
false,
}
-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);
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);
}
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;
}
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";
#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"
#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"
}
-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,
{
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");
{
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());
{
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");
{
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());
#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;
}
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,
#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"
#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"
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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>();
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);
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);
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,
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);
}
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) {
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);
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);
}
{
- 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);
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);
}
{
- 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);
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);
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);
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);
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"));
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);
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 */
{
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);
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());
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());
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());
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);
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);
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);
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);
#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>
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";
#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>
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;