2 Copyright (c) 2013-2016, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14 derived from this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /*! \file ST2052_TimedText.cpp
29 \brief AS-DCP library, PCM essence reader and writer implementation
32 #include "AS_02_internal.h"
34 #include <openssl/sha.h>
37 using namespace ASDCP;
39 using Kumu::DefaultLogSink;
41 const char* c_tt_namespace_name = "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt";
44 //------------------------------------------------------------------------------------------
47 int const NS_ID_LENGTH = 16;
50 static byte_t s_png_id_prefix[NS_ID_LENGTH] = {
52 // 2067-2 5.4.5 / RFC4122 Appendix C
53 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
54 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
58 static byte_t s_font_id_prefix[NS_ID_LENGTH] = {
61 0xb6, 0xcc, 0x57, 0xa0, 0x87, 0xe7, 0x4e, 0x75,
62 0xb1, 0xc3, 0x33, 0x59, 0xf3, 0xae, 0x88, 0x17
67 create_4122_type5_id(const std::string& subject_name, const byte_t* ns_id)
71 SHA1_Update(&ctx, ns_id, NS_ID_LENGTH);
72 SHA1_Update(&ctx, (byte_t*)subject_name.c_str(), subject_name.size());
74 const ui32_t sha_len = 20;
75 byte_t bin_buf[sha_len];
76 SHA1_Final(bin_buf, &ctx);
78 // Derive the asset ID from the digest. Make it a type-5 UUID
79 byte_t buf[UUID_Length];
80 memcpy(buf, bin_buf, UUID_Length);
81 buf[6] &= 0x0f; // clear bits 4-7
82 buf[6] |= 0x50; // set UUID version 'digest'
83 buf[8] &= 0x3f; // clear bits 6&7
84 buf[8] |= 0x80; // set bit 7
85 return Kumu::UUID(buf);
90 AS_02::TimedText::CreatePNGNameId(const std::string& image_name)
92 return create_4122_type5_id(image_name, s_png_id_prefix);
97 AS_02::TimedText::CreateFontNameId(const std::string& font_name)
99 return create_4122_type5_id(font_name, s_font_id_prefix);
103 static Kumu::Mutex sg_default_font_family_list_lock;
104 static std::set<std::string> sg_default_font_family_list;
107 setup_default_font_family_list()
109 AutoMutex l(sg_default_font_family_list_lock);
110 sg_default_font_family_list.insert("default");
111 sg_default_font_family_list.insert("monospace");
112 sg_default_font_family_list.insert("sansSerif");
113 sg_default_font_family_list.insert("serif");
114 sg_default_font_family_list.insert("monospaceSansSerif");
115 sg_default_font_family_list.insert("monospaceSerif");
116 sg_default_font_family_list.insert("proportionalSansSerif");
117 sg_default_font_family_list.insert("proportionalSerif");
121 //------------------------------------------------------------------------------------------
124 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
125 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
127 const byte_t PNGMagic[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
128 const byte_t OpenTypeMagic[5] = { 0x4f, 0x54, 0x54, 0x4f, 0x00 };
129 const byte_t TrueTypeMagic[5] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
133 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
135 DirScannerEx dir_reader;
136 DirectoryEntryType_t ft;
137 std::string next_item;
138 std::string abs_dirname = PathMakeCanonical(dirname);
139 byte_t read_buffer[16];
141 if ( abs_dirname.empty() )
146 Result_t result = dir_reader.Open(abs_dirname);
148 if ( KM_SUCCESS(result) )
150 while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
152 if ( next_item[0] == '.' ) continue; // no hidden files
153 std::string tmp_path = PathJoin(abs_dirname, next_item);
155 if ( ft == DET_FILE )
158 Result_t read_result = reader.OpenRead(tmp_path);
160 if ( KM_SUCCESS(read_result) )
162 read_result = reader.Read(read_buffer, 16);
165 if ( KM_SUCCESS(read_result) )
168 if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
170 UUID asset_id = CreatePNGNameId(PathBasename(next_item));
171 m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
174 else if ( memcmp(read_buffer, OpenTypeMagic, sizeof(OpenTypeMagic)) == 0
175 || memcmp(read_buffer, TrueTypeMagic, sizeof(TrueTypeMagic)) == 0 )
177 std::string font_root_name = PathSetExtension(next_item, "");
178 UUID asset_id = CreateFontNameId(PathBasename(font_root_name));
179 m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
191 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
193 Kumu::UUID tmp_id(uuid);
196 ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
198 if ( i == m_ResourceMap.end() )
200 DefaultLogSink().Debug("Missing timed-text resource \"%s\"\n", tmp_id.EncodeHex(buf, 64));
201 return RESULT_NOT_FOUND;
206 DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
208 Result_t result = Reader.OpenRead(i->second.c_str());
210 if ( KM_SUCCESS(result) )
212 ui32_t read_count, read_size = Reader.Size();
213 result = FrameBuf.Capacity(read_size);
215 if ( KM_SUCCESS(result) )
216 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
218 if ( KM_SUCCESS(result) )
219 FrameBuf.Size(read_count);
225 //------------------------------------------------------------------------------------------
227 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
229 class AS_02::TimedText::ST2052_TextParser::h__TextParser
232 ResourceTypeMap_t m_ResourceTypes;
233 Result_t OpenRead(const std::string& profile_name);
235 ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
238 std::string m_Filename;
239 std::string m_XMLDoc;
240 TimedTextDescriptor m_TDesc;
241 ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
243 h__TextParser() : m_Root("**ParserRoot**")
245 memset(&m_TDesc.AssetID, 0, UUIDlen);
250 ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
252 if ( m_DefaultResolver.empty() )
254 AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
255 resolver->OpenRead(PathDirname(m_Filename));
256 m_DefaultResolver = resolver;
259 return m_DefaultResolver;
262 Result_t OpenRead(const std::string& filename, const std::string& profile_name);
263 Result_t OpenRead(const std::string& xml_doc, const std::string& filename, const std::string& profile_name);
264 Result_t ReadAncillaryResource(const byte_t *uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
265 const ASDCP::TimedText::IResourceResolver& Resolver) const;
270 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename, const std::string& profile_name)
272 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
274 if ( KM_SUCCESS(result) )
276 m_Filename = filename;
277 result = OpenRead(profile_name);
285 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename,
286 const std::string& profile_name)
289 m_Filename = filename;
290 return OpenRead(profile_name);
294 template <class VisitorType>
296 apply_visitor(const XMLElement& element, VisitorType& visitor)
298 const ElementList& l = element.GetChildren();
299 ElementList::const_iterator i;
301 for ( i = l.begin(); i != l.end(); ++i )
303 if ( ! visitor.Element(**i) )
308 if ( ! apply_visitor(**i, visitor) )
318 class AttributeVisitor
320 std::string attr_name;
323 AttributeVisitor(const std::string& n) : attr_name(n) {}
324 std::set<std::string> value_list;
326 bool Element(const XMLElement& e)
328 const AttributeList& l = e.GetAttributes();
329 AttributeList::const_iterator i;
331 for ( i = l.begin(); i != l.end(); ++i )
333 if ( i->name == attr_name )
335 value_list.insert(i->value);
345 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& profile_name)
347 setup_default_font_family_list();
349 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
351 DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
352 return RESULT_FORMAT;
355 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
356 m_TDesc.ResourceList.clear();
357 m_TDesc.ContainerDuration = 0;
358 m_TDesc.NamespaceName = profile_name;
360 AttributeVisitor png_visitor("backgroundImage");
361 apply_visitor(m_Root, png_visitor);
362 std::set<std::string>::const_iterator i;
364 for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
366 UUID asset_id = CreatePNGNameId(PathBasename(*i));
367 TimedTextResourceDescriptor png_resource;
368 memcpy(png_resource.ResourceID, asset_id.Value(), UUIDlen);
369 png_resource.Type = ASDCP::TimedText::MT_PNG;
370 m_TDesc.ResourceList.push_back(png_resource);
371 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(png_resource.ResourceID),
372 ASDCP::TimedText::MT_PNG));
375 AttributeVisitor font_visitor("fontFamily");
376 apply_visitor(m_Root, font_visitor);
379 for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
381 UUID font_id = CreateFontNameId(PathBasename(*i));
383 if ( PathIsFile(font_id.EncodeHex(buf, 64))
384 || PathIsFile(*i+".ttf")
385 || PathIsFile(*i+".otf") )
387 TimedTextResourceDescriptor font_resource;
388 memcpy(font_resource.ResourceID, font_id.Value(), UUIDlen);
389 font_resource.Type = ASDCP::TimedText::MT_OPENTYPE;
390 m_TDesc.ResourceList.push_back(font_resource);
391 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(font_resource.ResourceID),
392 ASDCP::TimedText::MT_OPENTYPE));
396 AutoMutex l(sg_default_font_family_list_lock);
397 if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
399 DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
400 return RESULT_FORMAT;
410 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
411 const ASDCP::TimedText::IResourceResolver& Resolver) const
413 FrameBuf.AssetID(uuid);
417 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
419 if ( rmi == m_ResourceTypes.end() )
421 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
425 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
427 if ( KM_SUCCESS(result) )
429 if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
431 FrameBuf.MIMEType("image/png");
433 else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
435 FrameBuf.MIMEType("application/x-font-opentype");
439 FrameBuf.MIMEType("application/octet-stream");
448 //------------------------------------------------------------------------------------------
450 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
454 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
458 // Opens the stream for reading, parses enough data to provide a complete
459 // set of stream metadata for the MXFWriter below.
461 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename, const std::string& profile_name) const
463 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
465 Result_t result = m_Parser->OpenRead(filename, profile_name);
467 if ( ASDCP_FAILURE(result) )
468 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
473 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
475 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename,
476 const std::string& profile_name) const
478 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
480 Result_t result = m_Parser->OpenRead(xml_doc, filename, profile_name);
482 if ( ASDCP_FAILURE(result) )
483 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
490 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
492 if ( m_Parser.empty() )
495 TDesc = m_Parser->m_TDesc;
499 // Reads the complete Timed Text Resource into the given string.
501 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
503 if ( m_Parser.empty() )
506 s = m_Parser->m_XMLDoc;
512 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
513 const ASDCP::TimedText::IResourceResolver* Resolver) const
515 if ( m_Parser.empty() )
519 Resolver = m_Parser->GetDefaultResolver();
521 return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
526 // end ST2052_TextParser.cpp