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 create_png_name_id(const std::string& image_name)
92 return create_4122_type5_id(image_name, s_png_id_prefix);
97 create_font_name_id(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 sg_default_font_family_list.insert("default");
110 sg_default_font_family_list.insert("monospace");
111 sg_default_font_family_list.insert("sansSerif");
112 sg_default_font_family_list.insert("serif");
113 sg_default_font_family_list.insert("monospaceSansSerif");
114 sg_default_font_family_list.insert("monospaceSerif");
115 sg_default_font_family_list.insert("proportionalSansSerif");
116 sg_default_font_family_list.insert("proportionalSerif");
120 //------------------------------------------------------------------------------------------
123 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
124 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
126 const byte_t PNGMagic[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
127 const byte_t OpenTypeMagic[5] = { 0x4f, 0x54, 0x54, 0x4f, 0x00 };
128 const byte_t TrueTypeMagic[5] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
132 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
134 DirScannerEx dir_reader;
135 DirectoryEntryType_t ft;
136 std::string next_item;
137 std::string abs_dirname = PathMakeCanonical(dirname);
138 byte_t read_buffer[16];
140 if ( abs_dirname.empty() )
145 Result_t result = dir_reader.Open(abs_dirname);
147 if ( KM_SUCCESS(result) )
149 while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
151 if ( next_item[0] == '.' ) continue; // no hidden files
152 std::string tmp_path = PathJoin(abs_dirname, next_item);
154 if ( ft == DET_FILE )
157 Result_t read_result = reader.OpenRead(tmp_path);
159 if ( KM_SUCCESS(read_result) )
161 read_result = reader.Read(read_buffer, 16);
164 if ( KM_SUCCESS(read_result) )
167 if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
169 UUID asset_id = create_png_name_id(next_item);
170 m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
173 else if ( memcmp(read_buffer, OpenTypeMagic, sizeof(OpenTypeMagic)) == 0
174 || memcmp(read_buffer, TrueTypeMagic, sizeof(TrueTypeMagic)) == 0 )
176 std::string font_root_name = PathSetExtension(next_item, "");
177 UUID asset_id = create_font_name_id(font_root_name);
178 m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
190 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
192 Kumu::UUID tmp_id(uuid);
195 ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
197 if ( i == m_ResourceMap.end() )
199 DefaultLogSink().Debug("Missing timed-text resource \"%s\"\n", tmp_id.EncodeHex(buf, 64));
200 return RESULT_NOT_FOUND;
205 DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
207 Result_t result = Reader.OpenRead(i->second.c_str());
209 if ( KM_SUCCESS(result) )
211 ui32_t read_count, read_size = Reader.Size();
212 result = FrameBuf.Capacity(read_size);
214 if ( KM_SUCCESS(result) )
215 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
217 if ( KM_SUCCESS(result) )
218 FrameBuf.Size(read_count);
224 //------------------------------------------------------------------------------------------
226 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
228 class AS_02::TimedText::ST2052_TextParser::h__TextParser
231 ResourceTypeMap_t m_ResourceTypes;
234 ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
237 std::string m_Filename;
238 std::string m_XMLDoc;
239 TimedTextDescriptor m_TDesc;
240 ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
242 h__TextParser() : m_Root("**ParserRoot**")
244 memset(&m_TDesc.AssetID, 0, UUIDlen);
249 ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
251 if ( m_DefaultResolver.empty() )
253 AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
254 resolver->OpenRead(PathDirname(m_Filename));
255 m_DefaultResolver = resolver;
258 return m_DefaultResolver;
261 Result_t OpenRead(const std::string& filename);
262 Result_t OpenRead(const std::string& xml_doc, const std::string& filename);
263 Result_t ReadAncillaryResource(const byte_t *uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
264 const ASDCP::TimedText::IResourceResolver& Resolver) const;
269 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename)
271 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
273 if ( KM_SUCCESS(result) )
275 m_Filename = filename;
284 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename)
287 m_Filename = filename;
292 template <class VisitorType>
294 apply_visitor(const XMLElement& element, VisitorType& visitor)
296 const ElementList& l = element.GetChildren();
297 ElementList::const_iterator i;
299 for ( i = l.begin(); i != l.end(); ++i )
301 if ( ! visitor.Element(**i) )
306 if ( ! apply_visitor(**i, visitor) )
316 class AttributeVisitor
318 std::string attr_name;
321 AttributeVisitor(const std::string& n) : attr_name(n) {}
322 std::set<std::string> value_list;
324 bool Element(const XMLElement& e)
326 const AttributeList& l = e.GetAttributes();
327 AttributeList::const_iterator i;
329 for ( i = l.begin(); i != l.end(); ++i )
331 if ( i->name == attr_name )
333 value_list.insert(i->value);
343 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
345 setup_default_font_family_list();
347 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
349 DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
350 return RESULT_FORMAT;
353 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
354 m_TDesc.ResourceList.clear();
355 m_TDesc.ContainerDuration = 0;
356 const XMLNamespace* ns = m_Root.Namespace();
360 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_tt_namespace_name);
361 m_TDesc.NamespaceName = c_tt_namespace_name;
365 m_TDesc.NamespaceName = ns->Name();
368 AttributeVisitor png_visitor("backgroundImage");
369 apply_visitor(m_Root, png_visitor);
370 std::set<std::string>::const_iterator i;
372 for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
374 UUID asset_id = create_png_name_id(*i);
375 TimedTextResourceDescriptor png_resource;
376 memcpy(png_resource.ResourceID, asset_id.Value(), UUIDlen);
377 png_resource.Type = ASDCP::TimedText::MT_PNG;
378 m_TDesc.ResourceList.push_back(png_resource);
379 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(png_resource.ResourceID),
380 ASDCP::TimedText::MT_PNG));
383 AttributeVisitor font_visitor("fontFamily");
384 apply_visitor(m_Root, font_visitor);
387 for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
389 UUID font_id = create_font_name_id(*i);
391 if ( PathIsFile(font_id.EncodeHex(buf, 64))
392 || PathIsFile(*i+".ttf")
393 || PathIsFile(*i+".otf") )
395 TimedTextResourceDescriptor font_resource;
396 memcpy(font_resource.ResourceID, font_id.Value(), UUIDlen);
397 font_resource.Type = ASDCP::TimedText::MT_OPENTYPE;
398 m_TDesc.ResourceList.push_back(font_resource);
399 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(font_resource.ResourceID),
400 ASDCP::TimedText::MT_OPENTYPE));
404 AutoMutex l(sg_default_font_family_list_lock);
405 if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
407 DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
408 return RESULT_FORMAT;
418 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
419 const ASDCP::TimedText::IResourceResolver& Resolver) const
421 FrameBuf.AssetID(uuid);
425 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
427 if ( rmi == m_ResourceTypes.end() )
429 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
433 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
435 if ( KM_SUCCESS(result) )
437 if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
439 FrameBuf.MIMEType("image/png");
441 else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
443 FrameBuf.MIMEType("application/x-font-opentype");
447 FrameBuf.MIMEType("application/octet-stream");
456 //------------------------------------------------------------------------------------------
458 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
462 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
466 // Opens the stream for reading, parses enough data to provide a complete
467 // set of stream metadata for the MXFWriter below.
469 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
471 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
473 Result_t result = m_Parser->OpenRead(filename);
475 if ( ASDCP_FAILURE(result) )
476 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
481 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
483 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
485 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
487 Result_t result = m_Parser->OpenRead(xml_doc, filename);
489 if ( ASDCP_FAILURE(result) )
490 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
497 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
499 if ( m_Parser.empty() )
502 TDesc = m_Parser->m_TDesc;
506 // Reads the complete Timed Text Resource into the given string.
508 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
510 if ( m_Parser.empty() )
513 s = m_Parser->m_XMLDoc;
519 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
520 const ASDCP::TimedText::IResourceResolver* Resolver) const
522 if ( m_Parser.empty() )
526 Resolver = m_Parser->GetDefaultResolver();
528 return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
533 // end ST2052_TextParser.cpp