Handle storing/recovery of fonts in SMPTE MXF files.
authorCarl Hetherington <cth@carlh.net>
Tue, 9 Jun 2015 13:02:20 +0000 (14:02 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 9 Jun 2015 13:02:20 +0000 (14:02 +0100)
run/tools/dcpinfo
src/interop_subtitle_asset.cc
src/smpte_load_font_node.cc
src/smpte_load_font_node.h
src/smpte_subtitle_asset.cc
src/subtitle_asset.cc
src/subtitle_asset.h
test/data/dummy.mxf [new file with mode: 0644]
test/dcp_font_test.cc

index 54e3c659fc66ca33f562e48c1b5ddb82862f523c..c0d13ba42b87069c6e7139bf16a088732919e018 100755 (executable)
@@ -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 "$@"
index 9565b12d5e2832e2925f831583999bc1b8ca8b14..5be4c53c8e834b04ae085bdab3570cfe6e40c965 100644 (file)
@@ -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<shared_ptr<Asset> >& assets)
 {
        for (map<string, FontData>::const_iterator i = _fonts.begin(); i != _fonts.end(); ++i) {
-               assets.push_back (shared_ptr<Font> (new Font (i->second.file)));
+               DCP_ASSERT (i->second.file);
+               assets.push_back (shared_ptr<Font> (new Font (i->second.file.get ())));
        }
 }
index 11f7d5a422985871d60b094615880eb70b4f525f..28a020dfb9306117fd6a96148ca5c78c14ef606c 100644 (file)
@@ -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<const cxml::Node> node)
        : LoadFontNode (node->string_attribute ("ID"))
+       , urn (node->content().substr (9))
 {
-       urn = node->content().substr (9);
+       
 }
index a150d9bec3a245537d66201e69b846fae2cf3e17..e6b87f30117b4c18ceb4f25da72610c8ece5508d 100644 (file)
@@ -38,6 +38,7 @@ class SMPTELoadFontNode : public LoadFontNode
 {
 public:
        SMPTELoadFontNode () {}
+       SMPTELoadFontNode (std::string id, std::string urn);
        SMPTELoadFontNode (boost::shared_ptr<const cxml::Node> node);
 
        std::string urn;
index 9878ea1f8cb2890ebf09fd65db5948d8214a9dcb..026e9f631d0ee2dfa1f0547b9b377a8a090370ad 100644 (file)
 #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 <libxml++/libxml++.h>
 #include <boost/foreach.hpp>
 #include <boost/algorithm/string.hpp>
@@ -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<cxml::Document> 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<cxml::Document> 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<dcp::SMPTELoadFontNode> (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<uint8_t> 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<shared_ptr<SMPTELoadFontNode> >::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<shared_ptr<LoadFontNode> >
@@ -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<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
+               map<string, FontData>::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<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
+               map<string, FontData>::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<const Asset> 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<SMPTELoadFontNode> (new SMPTELoadFontNode (id, make_uuid ())));
 }
index 70d7a9554a2eb12fdba9bd9eaca466a1344708d7..085e59590ccd7735e7a0ad04c462fb1bdb774037 100644 (file)
@@ -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);
 }
index 481556aae3eccf83a5ef6ae560f851c6de829752..4cbbff0d5ebc1646a4d4b1147ebd0235d88be14e 100644 (file)
@@ -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<cxml::Document> xml, std::list<boost::shared_ptr<FontNode> > 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<uint8_t> data_, boost::uintmax_t size_, boost::filesystem::path file_)
-                       : data (data_)
-                       , size (size_)
-                       , file (file_)
-               {}
                
                boost::shared_array<uint8_t> data;
                boost::uintmax_t size;
-               mutable boost::filesystem::path file;
+               /** .ttf file that this data was last written to */
+               mutable boost::optional<boost::filesystem::path> 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<std::string, FontData> _fonts;
        
diff --git a/test/data/dummy.mxf b/test/data/dummy.mxf
new file mode 100644 (file)
index 0000000..3c60454
Binary files /dev/null and b/test/data/dummy.mxf differ
index e50219a056c2afcc31fdf06c67c272e8cc3cd3a8..a21552de74a363b3677528f56bbc3ce502f248c1 100644 (file)
@@ -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<dcp::SMPTESubtitleAsset> subs (new dcp::SMPTESubtitleAsset ());
+       subs->add_font ("theFontId", "test/data/dummy.ttf");
+       subs->write (directory / "frobozz.mxf");
+
+       shared_ptr<dcp::Reel> reel (new dcp::Reel ());
+       reel->add (shared_ptr<dcp::ReelAsset> (new dcp::ReelSubtitleAsset (subs, dcp::Fraction (24, 1), 24, 0)));
+
+       shared_ptr<dcp::CPL> 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<dcp::SubtitleAsset> subs2 = dynamic_pointer_cast<dcp::SubtitleAsset> (
+               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<uint8_t> 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);
+}