diff options
| author | Carl Hetherington <cth@carlh.net> | 2015-06-09 11:13:02 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2015-06-09 11:13:02 +0100 |
| commit | 5c1b079a28941781c9cfa46d57e0c20b55681b32 (patch) | |
| tree | 35c0d2aac409ca1b52699e974f299a64aa5ffc1f | |
| parent | 6425742f95a4c2e539061b29016a4a5bd10ac9ae (diff) | |
Basically-working interop subtitle font handling.
| -rw-r--r-- | src/asset.cc | 8 | ||||
| -rw-r--r-- | src/dcp.cc | 18 | ||||
| -rw-r--r-- | src/font.h | 2 | ||||
| -rw-r--r-- | src/interop_subtitle_asset.cc | 50 | ||||
| -rw-r--r-- | src/interop_subtitle_asset.h | 4 | ||||
| -rw-r--r-- | src/reel.cc | 9 | ||||
| -rw-r--r-- | src/smpte_subtitle_asset.cc | 5 | ||||
| -rw-r--r-- | src/smpte_subtitle_asset.h | 1 | ||||
| -rw-r--r-- | src/subtitle_asset.cc | 23 | ||||
| -rw-r--r-- | src/subtitle_asset.h | 33 | ||||
| -rw-r--r-- | test/data/dummy.ttf | 1 | ||||
| -rw-r--r-- | test/dcp_font_test.cc | 72 | ||||
| -rw-r--r-- | test/test.cc | 42 | ||||
| -rw-r--r-- | test/test.h | 1 | ||||
| -rw-r--r-- | test/wscript | 1 |
15 files changed, 258 insertions, 12 deletions
diff --git a/src/asset.cc b/src/asset.cc index 24fc5919..5760c905 100644 --- a/src/asset.cc +++ b/src/asset.cc @@ -71,10 +71,16 @@ Asset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const asset->add_child("Id")->add_child_text ("urn:uuid:" + _id); xmlpp::Node* chunk_list = asset->add_child ("ChunkList"); xmlpp::Node* chunk = chunk_list->add_child ("Chunk"); - optional<boost::filesystem::path> path = relative_to_root (root, _file); + + optional<boost::filesystem::path> path = relative_to_root ( + boost::filesystem::canonical (root), + boost::filesystem::canonical (_file) + ); + if (!path) { throw MiscError (String::compose ("Asset %1 is not within the directory %2", _file, root)); } + chunk->add_child("Path")->add_child_text (path.get().string ()); chunk->add_child("VolumeIndex")->add_child_text ("1"); chunk->add_child("Offset")->add_child_text ("0"); @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,6 +29,7 @@ #include "smpte_subtitle_asset.h" #include "mono_picture_asset.h" #include "stereo_picture_asset.h" +#include "reel_subtitle_asset.h" #include "util.h" #include "metadata.h" #include "exceptions.h" @@ -40,6 +41,7 @@ #include "decrypted_kdm_key.h" #include "dcp_assert.h" #include "reel_asset.h" +#include "font.h" #include <xmlsec/xmldsig.h> #include <xmlsec/app.h> #include <libxml++/libxml++.h> @@ -55,8 +57,10 @@ using std::ostream; using std::make_pair; using std::map; using std::cout; +using std::cerr; using std::exception; using boost::shared_ptr; +using boost::dynamic_pointer_cast; using boost::algorithm::starts_with; using namespace dcp; @@ -117,7 +121,7 @@ DCP::read (bool keep_going, ReadErrors* errors) */ /* Make a list of non-CPL assets so that we can resolve the references - from the CPL. + from the CPLs. */ list<shared_ptr<Asset> > other_assets; @@ -171,6 +175,8 @@ DCP::read (bool keep_going, ReadErrors* errors) default: throw DCPReadError ("Unknown MXF essence type"); } + } else if (boost::filesystem::extension (path) == ".ttf") { + other_assets.push_back (shared_ptr<Font> (new Font (path))); } } @@ -423,7 +429,13 @@ DCP::assets () const BOOST_FOREACH (shared_ptr<CPL> i, cpls ()) { assets.push_back (i); BOOST_FOREACH (shared_ptr<const ReelAsset> j, i->reel_assets ()) { - assets.push_back (j->asset_ref().object ()); + shared_ptr<Asset> o = j->asset_ref().object (); + assets.push_back (o); + /* More Interop special-casing */ + shared_ptr<InteropSubtitleAsset> sub = dynamic_pointer_cast<InteropSubtitleAsset> (o); + if (sub) { + sub->add_font_assets (assets); + } } } @@ -26,7 +26,7 @@ namespace dcp { /** @class Font - * @brief A (truetype) font asset for subtitles in a DCP. + * @brief A (truetype) font asset for subtitles in an Interop DCP. */ class Font : public Asset { diff --git a/src/interop_subtitle_asset.cc b/src/interop_subtitle_asset.cc index 231dda16..9565b12d 100644 --- a/src/interop_subtitle_asset.cc +++ b/src/interop_subtitle_asset.cc @@ -23,6 +23,8 @@ #include "raw_convert.h" #include "font_node.h" #include "util.h" +#include "font.h" +#include "dcp_assert.h" #include <libxml++/libxml++.h> #include <boost/foreach.hpp> #include <cmath> @@ -31,6 +33,8 @@ using std::list; using std::string; using std::cout; +using std::cerr; +using std::map; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; @@ -85,9 +89,10 @@ InteropSubtitleAsset::xml_as_string () const } void -InteropSubtitleAsset::add_font (string id, string uri) +InteropSubtitleAsset::add_font (string id, boost::filesystem::path file) { - _load_font_nodes.push_back (shared_ptr<InteropLoadFontNode> (new InteropLoadFontNode (id, uri))); + add_font_data (id, file); + _load_font_nodes.push_back (shared_ptr<InteropLoadFontNode> (new InteropLoadFontNode (id, file.leaf().string ()))); } bool @@ -136,7 +141,7 @@ InteropSubtitleAsset::load_font_nodes () const return lf; } -/** Write this content to an XML file */ +/** Write this content to an XML file with its fonts alongside */ void InteropSubtitleAsset::write (boost::filesystem::path p) const { @@ -150,4 +155,43 @@ InteropSubtitleAsset::write (boost::filesystem::path p) const fclose (f); _file = p; + + BOOST_FOREACH (shared_ptr<InteropLoadFontNode> i, _load_font_nodes) { + boost::filesystem::path file = p.parent_path() / i->uri; + FILE* f = fopen_boost (file, "w"); + if (!f) { + throw FileError ("could not open font file for writing", file, errno); + } + map<string, FontData>::const_iterator j = _fonts.find (i->id); + if (j != _fonts.end ()) { + fwrite (j->second.data.get(), 1, j->second.size, f); + j->second.file = file; + } + fclose (f); + } +} + +void +InteropSubtitleAsset::resolve_fonts (list<shared_ptr<Object> > objects) +{ + BOOST_FOREACH (shared_ptr<Object> i, objects) { + shared_ptr<Font> font = dynamic_pointer_cast<Font> (i); + if (!font) { + continue; + } + + BOOST_FOREACH (shared_ptr<InteropLoadFontNode> j, _load_font_nodes) { + if (j->uri == font->file().leaf().string ()) { + add_font_data (j->id, font->file ()); + } + } + } +} + +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))); + } } diff --git a/src/interop_subtitle_asset.h b/src/interop_subtitle_asset.h index 378bacc7..4d2c2893 100644 --- a/src/interop_subtitle_asset.h +++ b/src/interop_subtitle_asset.h @@ -47,10 +47,12 @@ public: std::list<boost::shared_ptr<LoadFontNode> > load_font_nodes () const; - void add_font (std::string id, std::string uri); + void add_font (std::string id, boost::filesystem::path file); Glib::ustring xml_as_string () const; void write (boost::filesystem::path path) const; + void resolve_fonts (std::list<boost::shared_ptr<Object> > objects); + void add_font_assets (std::list<boost::shared_ptr<Asset> >& assets); /** Set the reel number or sub-element identifier * of these subtitles. diff --git a/src/reel.cc b/src/reel.cc index 106fbf40..4d92d345 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ #include "reel_subtitle_asset.h" #include "decrypted_kdm_key.h" #include "decrypted_kdm.h" +#include "interop_subtitle_asset.h" #include <libxml++/nodes/element.h> using std::string; @@ -176,5 +177,11 @@ Reel::resolve_refs (list<shared_ptr<Object> > objects) if (_main_subtitle) { _main_subtitle->asset_ref().resolve (objects); + + /* Interop subtitle handling is all special cases */ + shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (_main_subtitle->asset_ref().object ()); + if (iop) { + iop->resolve_fonts (objects); + } } } diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index 69e2712f..28d8b261 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -209,3 +209,8 @@ SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions return false; } +void +SMPTESubtitleAsset::add_font (string id, boost::filesystem::path file) +{ + /* XXX */ +} diff --git a/src/smpte_subtitle_asset.h b/src/smpte_subtitle_asset.h index 3fb2571a..37f58c6f 100644 --- a/src/smpte_subtitle_asset.h +++ b/src/smpte_subtitle_asset.h @@ -53,6 +53,7 @@ public: Glib::ustring xml_as_string () const; void write (boost::filesystem::path path) const; + void add_font (std::string id, boost::filesystem::path file); void set_content_title_text (std::string t) { _content_title_text = t; diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 45177b95..70d7a955 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -29,6 +29,7 @@ #include "KM_util.h" #include <libxml++/nodes/element.h> #include <boost/algorithm/string.hpp> +#include <boost/shared_array.hpp> #include <fstream> using std::string; @@ -37,7 +38,9 @@ using std::ostream; using std::ofstream; using std::stringstream; using std::cout; +using std::cerr; using boost::shared_ptr; +using boost::shared_array; using boost::optional; using boost::dynamic_pointer_cast; using namespace dcp; @@ -304,4 +307,22 @@ SubtitleAsset::subtitles_as_xml (xmlpp::Element* root, int time_code_rate, strin } } - +void +SubtitleAsset::add_font_data (string id, boost::filesystem::path file) +{ + boost::uintmax_t size = boost::filesystem::file_size (file); + FILE* f = fopen_boost (file, "r"); + if (!f) { + throw FileError ("could not open font file for reading", file, errno); + } + + shared_array<uint8_t> data (new uint8_t[size]); + size_t const read = fread (data.get(), 1, size, f); + fclose (f); + + if (read != size) { + throw FileError ("could not read font file", file, -1); + } + + _fonts[id] = FontData (data, size, file); +} diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h index 5fdb5f7a..481556aa 100644 --- a/src/subtitle_asset.h +++ b/src/subtitle_asset.h @@ -24,11 +24,14 @@ #include "dcp_time.h" #include "subtitle_string.h" #include <libcxml/cxml.h> +#include <boost/shared_array.hpp> namespace xmlpp { class Element; } +struct interop_dcp_font_test; + namespace dcp { @@ -64,6 +67,7 @@ public: } void add (SubtitleString); + virtual void add_font (std::string id, boost::filesystem::path file) = 0; virtual void write (boost::filesystem::path) const = 0; virtual Glib::ustring xml_as_string () const = 0; @@ -73,13 +77,40 @@ public: virtual std::list<boost::shared_ptr<LoadFontNode> > load_font_nodes () const = 0; protected: + friend struct ::interop_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; + void add_font_data (std::string id, boost::filesystem::path file); /** All our subtitles, in no particular order */ std::list<SubtitleString> _subtitles; + class FontData { + public: + FontData () {} + + FontData (boost::shared_array<uint8_t> data_, boost::uintmax_t size_) + : 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; + }; + + /** Font data, keyed by a subclass-dependent identifier. + * For Interop fonts, the string is the font ID from the subtitle file. + */ + std::map<std::string, FontData> _fonts; + private: /** @struct ParseState * @brief A struct to hold state when parsing a subtitle XML file. diff --git a/test/data/dummy.ttf b/test/data/dummy.ttf new file mode 100644 index 00000000..1353512a --- /dev/null +++ b/test/data/dummy.ttf @@ -0,0 +1 @@ +Uap[~"ײB&ccOFJXPx{(TI{8tO'ـr"@7pNͷ-4'4BVlhy<yg
\ No newline at end of file diff --git a/test/dcp_font_test.cc b/test/dcp_font_test.cc new file mode 100644 index 00000000..e50219a0 --- /dev/null +++ b/test/dcp_font_test.cc @@ -0,0 +1,72 @@ +/* + Copyright (C) 2015 Carl Hetherington <cth@carlh.net> + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "interop_subtitle_asset.h" +#include "dcp.h" +#include "cpl.h" +#include "test.h" +#include "reel.h" +#include "util.h" +#include "reel_subtitle_asset.h" +#include <boost/test/unit_test.hpp> +#include <cstdio> + +using std::list; +using std::string; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; +using boost::shared_array; + +/** Create a DCP with interop subtitles and check that the font is written and read back correctly */ +BOOST_AUTO_TEST_CASE (interop_dcp_font_test) +{ + boost::filesystem::path directory = "build/test/interop_dcp_font_test"; + dcp::DCP dcp (directory); + + shared_ptr<dcp::InteropSubtitleAsset> subs (new dcp::InteropSubtitleAsset ()); + subs->add_font ("theFontId", "test/data/dummy.ttf"); + subs->write (directory / "frobozz.xml"); + check_file ("test/data/dummy.ttf", "build/test/interop_dcp_font_test/dummy.ttf"); + + 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::INTEROP); + + 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_CHECK_EQUAL (memcmp (subs2->_fonts["theFontId"].data.get(), ref.get(), size), 0); +} diff --git a/test/test.cc b/test/test.cc index 0c3d2c5d..c83c1048 100644 --- a/test/test.cc +++ b/test/test.cc @@ -23,8 +23,11 @@ #include "test.h" #include <libxml++/libxml++.h> #include <boost/test/unit_test.hpp> +#include <cstdio> using std::string; +using std::min; +using std::stringstream; using std::list; boost::filesystem::path private_test; @@ -106,4 +109,43 @@ check_xml (string ref, string test, list<string> ignore) check_xml (ref_root, test_root, ignore); } +void +check_file (boost::filesystem::path ref, boost::filesystem::path check) +{ + uintmax_t N = boost::filesystem::file_size (ref); + BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check)); + FILE* ref_file = dcp::fopen_boost (ref, "rb"); + BOOST_CHECK (ref_file); + FILE* check_file = dcp::fopen_boost (check, "rb"); + BOOST_CHECK (check_file); + + int const buffer_size = 65536; + uint8_t* ref_buffer = new uint8_t[buffer_size]; + uint8_t* check_buffer = new uint8_t[buffer_size]; + + stringstream error; + error << "File " << check.string() << " differs from reference " << ref.string(); + + while (N) { + uintmax_t this_time = min (uintmax_t (buffer_size), N); + size_t r = fread (ref_buffer, 1, this_time, ref_file); + BOOST_CHECK_EQUAL (r, this_time); + r = fread (check_buffer, 1, this_time, check_file); + BOOST_CHECK_EQUAL (r, this_time); + + BOOST_CHECK_MESSAGE (memcmp (ref_buffer, check_buffer, this_time) == 0, error.str ()); + if (memcmp (ref_buffer, check_buffer, this_time)) { + break; + } + + N -= this_time; + } + + delete[] ref_buffer; + delete[] check_buffer; + + fclose (ref_file); + fclose (check_file); +} + BOOST_GLOBAL_FIXTURE (TestConfig); diff --git a/test/test.h b/test/test.h index 4c941023..62a4c942 100644 --- a/test/test.h +++ b/test/test.h @@ -24,3 +24,4 @@ namespace xmlpp { extern boost::filesystem::path private_test; extern void check_xml (xmlpp::Element* ref, xmlpp::Element* test, std::list<std::string> ignore); extern void check_xml (std::string ref, std::string test, std::list<std::string> ignore); +extern void check_file (boost::filesystem::path ref, boost::filesystem::path check); diff --git a/test/wscript b/test/wscript index f640f6fc..6cf0d958 100644 --- a/test/wscript +++ b/test/wscript @@ -29,6 +29,7 @@ def build(bld): colour_test.cc colour_conversion_test.cc cpl_sar_test.cc + dcp_font_test.cc dcp_test.cc dcp_time_test.cc decryption_test.cc |
