From e4b8bed37b4fcfb932e2b899003f2a95df908ba0 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 9 Jun 2015 14:02:20 +0100 Subject: [PATCH] Handle storing/recovery of fonts in SMPTE MXF files. --- run/tools/dcpinfo | 2 +- src/interop_subtitle_asset.cc | 4 +- src/smpte_load_font_node.cc | 10 +++- src/smpte_load_font_node.h | 1 + src/smpte_subtitle_asset.cc | 84 ++++++++++++++++++++++++++++++---- src/subtitle_asset.cc | 2 +- src/subtitle_asset.h | 14 +++--- test/data/dummy.mxf | Bin 0 -> 17714 bytes test/dcp_font_test.cc | 39 ++++++++++++++++ 9 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 test/data/dummy.mxf diff --git a/run/tools/dcpinfo b/run/tools/dcpinfo index 54e3c659..c0d13ba4 100755 --- a/run/tools/dcpinfo +++ b/run/tools/dcpinfo @@ -1,6 +1,6 @@ #!/bin/bash -export LD_LIBRARY_PATH=build/src:build/asdcplib/src +export LD_LIBRARY_PATH=build/src:build/asdcplib/src:$LD_LIBRARY_PATH if [ "$1" == "--debug" ]; then shift gdb --args build/tools/dcpinfo "$@" diff --git a/src/interop_subtitle_asset.cc b/src/interop_subtitle_asset.cc index 9565b12d..5be4c53c 100644 --- a/src/interop_subtitle_asset.cc +++ b/src/interop_subtitle_asset.cc @@ -36,6 +36,7 @@ using std::cout; using std::cerr; using std::map; using boost::shared_ptr; +using boost::shared_array; using boost::optional; using boost::dynamic_pointer_cast; using namespace dcp; @@ -192,6 +193,7 @@ void InteropSubtitleAsset::add_font_assets (list >& assets) { for (map::const_iterator i = _fonts.begin(); i != _fonts.end(); ++i) { - assets.push_back (shared_ptr (new Font (i->second.file))); + DCP_ASSERT (i->second.file); + assets.push_back (shared_ptr (new Font (i->second.file.get ()))); } } diff --git a/src/smpte_load_font_node.cc b/src/smpte_load_font_node.cc index 11f7d5a4..28a020df 100644 --- a/src/smpte_load_font_node.cc +++ b/src/smpte_load_font_node.cc @@ -24,8 +24,16 @@ using std::string; using boost::shared_ptr; using namespace dcp; +SMPTELoadFontNode::SMPTELoadFontNode (string id, string urn_) + : LoadFontNode (id) + , urn (urn_) +{ + +} + SMPTELoadFontNode::SMPTELoadFontNode (shared_ptr node) : LoadFontNode (node->string_attribute ("ID")) + , urn (node->content().substr (9)) { - urn = node->content().substr (9); + } diff --git a/src/smpte_load_font_node.h b/src/smpte_load_font_node.h index a150d9be..e6b87f30 100644 --- a/src/smpte_load_font_node.h +++ b/src/smpte_load_font_node.h @@ -38,6 +38,7 @@ class SMPTELoadFontNode : public LoadFontNode { public: SMPTELoadFontNode () {} + SMPTELoadFontNode (std::string id, std::string urn); SMPTELoadFontNode (boost::shared_ptr node); std::string urn; diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index 9878ea1f..026e9f63 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -26,9 +26,11 @@ #include "font_node.h" #include "exceptions.h" #include "xml.h" +#include "raw_convert.h" +#include "dcp_assert.h" +#include "util.h" #include "AS_DCP.h" #include "KM_util.h" -#include "raw_convert.h" #include #include #include @@ -38,13 +40,16 @@ using std::list; using std::stringstream; using std::cout; using std::vector; +using std::map; using boost::shared_ptr; using boost::split; using boost::is_any_of; +using boost::shared_array; using namespace dcp; SMPTESubtitleAsset::SMPTESubtitleAsset () - : _time_code_rate (0) + : _edit_rate (24, 1) + , _time_code_rate (24) { } @@ -55,24 +60,25 @@ SMPTESubtitleAsset::SMPTESubtitleAsset () SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) : SubtitleAsset (file) { - shared_ptr xml (new cxml::Document ("SubtitleReel")); - ASDCP::TimedText::MXFReader reader; Kumu::Result_t r = reader.OpenRead (file.string().c_str ()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r)); } + + /* Read the subtitle XML */ string s; reader.ReadTimedTextResource (s, 0, 0); stringstream t; t << s; + shared_ptr xml (new cxml::Document ("SubtitleReel")); xml->read_stream (t); ASDCP::WriterInfo info; reader.FillWriterInfo (info); _id = read_writer_info (info); - + _load_font_nodes = type_children (xml, "LoadFont"); _content_title_text = xml->string_child ("ContentTitleText"); @@ -107,6 +113,42 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) } parse_subtitles (xml, font_nodes); + + /* Read fonts */ + + ASDCP::TimedText::TimedTextDescriptor text_descriptor; + reader.FillTimedTextDescriptor (text_descriptor); + for ( + ASDCP::TimedText::ResourceList_t::const_iterator i = text_descriptor.ResourceList.begin(); + i != text_descriptor.ResourceList.end(); + ++i) { + + if (i->Type == ASDCP::TimedText::MT_OPENTYPE) { + ASDCP::TimedText::FrameBuffer buffer; + buffer.Capacity (10 * 1024 * 1024); + reader.ReadAncillaryResource (i->ResourceID, buffer); + + char id[64]; + Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id)); + + shared_array data (new uint8_t[buffer.Size()]); + memcpy (data.get(), buffer.RoData(), buffer.Size()); + + /* The IDs in the MXF have a 9 character prefix of unknown origin and meaning... */ + string check_id = string (id).substr (9); + + list >::const_iterator j = _load_font_nodes.begin (); + while (j != _load_font_nodes.end() && (*j)->urn != check_id) { + ++j; + } + + if (j != _load_font_nodes.end ()) { + _fonts[(*j)->id] = FontData (data, buffer.Size ()); + } + } + } + + } list > @@ -172,13 +214,23 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const ASDCP::TimedText::TimedTextDescriptor descriptor; descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator); descriptor.EncodingName = "UTF-8"; - descriptor.ResourceList.clear (); + + BOOST_FOREACH (shared_ptr i, _load_font_nodes) { + map::const_iterator j = _fonts.find (i->id); + 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); + } + } + descriptor.NamespaceName = "dcst"; memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen); descriptor.ContainerDuration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); - /* XXX: should write fonts into the file somehow */ - ASDCP::TimedText::MXFWriter writer; ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor); if (ASDCP_FAILURE (r)) { @@ -191,6 +243,19 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r)); } + BOOST_FOREACH (shared_ptr i, _load_font_nodes) { + map::const_iterator j = _fonts.find (i->id); + if (j != _fonts.end ()) { + ASDCP::TimedText::FrameBuffer buffer; + buffer.SetData (j->second.data.get(), j->second.size); + buffer.Size (j->second.size); + r = writer.WriteAncillaryResource (buffer); + if (ASDCP_FAILURE (r)) { + boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r)); + } + } + } + writer.Finalize (); _file = p; @@ -206,5 +271,6 @@ SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions void SMPTESubtitleAsset::add_font (string id, boost::filesystem::path file) { - /* XXX */ + add_font_data (id, file); + _load_font_nodes.push_back (shared_ptr (new SMPTELoadFontNode (id, make_uuid ()))); } diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 70d7a955..085e5959 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -324,5 +324,5 @@ SubtitleAsset::add_font_data (string id, boost::filesystem::path file) throw FileError ("could not read font file", file, -1); } - _fonts[id] = FontData (data, size, file); + _fonts[id] = FontData (data, size); } diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h index 481556aa..4cbbff0d 100644 --- a/src/subtitle_asset.h +++ b/src/subtitle_asset.h @@ -31,6 +31,7 @@ namespace xmlpp { } struct interop_dcp_font_test; +struct smpte_dcp_font_test; namespace dcp { @@ -78,6 +79,7 @@ public: protected: friend struct ::interop_dcp_font_test; + friend struct ::smpte_dcp_font_test; void parse_subtitles (boost::shared_ptr xml, std::list > font_nodes); void subtitles_as_xml (xmlpp::Element* root, int time_code_rate, std::string xmlns) const; @@ -94,20 +96,16 @@ protected: : data (data_) , size (size_) {} - - FontData (boost::shared_array data_, boost::uintmax_t size_, boost::filesystem::path file_) - : data (data_) - , size (size_) - , file (file_) - {} boost::shared_array data; boost::uintmax_t size; - mutable boost::filesystem::path file; + /** .ttf file that this data was last written to */ + mutable boost::optional file; }; /** Font data, keyed by a subclass-dependent identifier. - * For Interop fonts, the string is the font ID from the subtitle file. + * For Interop, the string is the font ID from the subtitle file. + * For SMPTE, the string is the font's URN from the subtitle file. */ std::map _fonts; diff --git a/test/data/dummy.mxf b/test/data/dummy.mxf new file mode 100644 index 0000000000000000000000000000000000000000..3c6045480853f760a5900187de136124f2ce13ea GIT binary patch literal 17714 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 literal 0 HcmV?d00001 diff --git a/test/dcp_font_test.cc b/test/dcp_font_test.cc index e50219a0..a21552de 100644 --- a/test/dcp_font_test.cc +++ b/test/dcp_font_test.cc @@ -18,6 +18,7 @@ */ #include "interop_subtitle_asset.h" +#include "smpte_subtitle_asset.h" #include "dcp.h" #include "cpl.h" #include "test.h" @@ -70,3 +71,41 @@ BOOST_AUTO_TEST_CASE (interop_dcp_font_test) BOOST_CHECK_EQUAL (memcmp (subs2->_fonts["theFontId"].data.get(), ref.get(), size), 0); } + +/** Create a DCP with SMPTE subtitles and check that the font is written and read back correctly */ +BOOST_AUTO_TEST_CASE (smpte_dcp_font_test) +{ + boost::filesystem::path directory = "build/test/smpte_dcp_font_test"; + dcp::DCP dcp (directory); + + shared_ptr subs (new dcp::SMPTESubtitleAsset ()); + subs->add_font ("theFontId", "test/data/dummy.ttf"); + subs->write (directory / "frobozz.mxf"); + + shared_ptr reel (new dcp::Reel ()); + reel->add (shared_ptr (new dcp::ReelSubtitleAsset (subs, dcp::Fraction (24, 1), 24, 0))); + + shared_ptr cpl (new dcp::CPL ("", dcp::TRAILER)); + cpl->add (reel); + + dcp.add (cpl); + dcp.write_xml (dcp::SMPTE); + + dcp::DCP dcp2 (directory); + dcp2.read (); + shared_ptr subs2 = dynamic_pointer_cast ( + dcp2.cpls().front()->reels().front()->main_subtitle()->asset_ref().object() + ); + BOOST_REQUIRE (subs2); + BOOST_REQUIRE_EQUAL (subs2->_fonts.size(), 1); + + boost::uintmax_t const size = boost::filesystem::file_size ("test/data/dummy.ttf"); + FILE* f = dcp::fopen_boost ("test/data/dummy.ttf", "r"); + BOOST_REQUIRE (f); + shared_array ref (new uint8_t[size]); + fread (ref.get(), 1, size, f); + fclose (f); + + BOOST_REQUIRE (subs2->_fonts["theFontId"].data); + BOOST_CHECK_EQUAL (memcmp (subs2->_fonts["theFontId"].data.get(), ref.get(), size), 0); +} -- 2.30.2