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;
232 Result_t OpenRead(const std::string& profile_name);
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, const std::string& profile_name);
262 Result_t OpenRead(const std::string& xml_doc, const std::string& filename, const std::string& profile_name);
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, const std::string& profile_name)
271 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
273 if ( KM_SUCCESS(result) )
275 m_Filename = filename;
276 result = OpenRead(profile_name);
284 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename,
285 const std::string& profile_name)
288 m_Filename = filename;
289 return OpenRead(profile_name);
293 template <class VisitorType>
295 apply_visitor(const XMLElement& element, VisitorType& visitor)
297 const ElementList& l = element.GetChildren();
298 ElementList::const_iterator i;
300 for ( i = l.begin(); i != l.end(); ++i )
302 if ( ! visitor.Element(**i) )
307 if ( ! apply_visitor(**i, visitor) )
317 class AttributeVisitor
319 std::string attr_name;
322 AttributeVisitor(const std::string& n) : attr_name(n) {}
323 std::set<std::string> value_list;
325 bool Element(const XMLElement& e)
327 const AttributeList& l = e.GetAttributes();
328 AttributeList::const_iterator i;
330 for ( i = l.begin(); i != l.end(); ++i )
332 if ( i->name == attr_name )
334 value_list.insert(i->value);
344 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& profile_name)
346 setup_default_font_family_list();
348 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
350 DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
351 return RESULT_FORMAT;
354 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
355 m_TDesc.ResourceList.clear();
356 m_TDesc.ContainerDuration = 0;
357 m_TDesc.NamespaceName = profile_name;
359 AttributeVisitor png_visitor("backgroundImage");
360 apply_visitor(m_Root, png_visitor);
361 std::set<std::string>::const_iterator i;
363 for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
365 UUID asset_id = create_png_name_id(*i);
366 TimedTextResourceDescriptor png_resource;
367 memcpy(png_resource.ResourceID, asset_id.Value(), UUIDlen);
368 png_resource.Type = ASDCP::TimedText::MT_PNG;
369 m_TDesc.ResourceList.push_back(png_resource);
370 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(png_resource.ResourceID),
371 ASDCP::TimedText::MT_PNG));
374 AttributeVisitor font_visitor("fontFamily");
375 apply_visitor(m_Root, font_visitor);
378 for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
380 UUID font_id = create_font_name_id(*i);
382 if ( PathIsFile(font_id.EncodeHex(buf, 64))
383 || PathIsFile(*i+".ttf")
384 || PathIsFile(*i+".otf") )
386 TimedTextResourceDescriptor font_resource;
387 memcpy(font_resource.ResourceID, font_id.Value(), UUIDlen);
388 font_resource.Type = ASDCP::TimedText::MT_OPENTYPE;
389 m_TDesc.ResourceList.push_back(font_resource);
390 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(font_resource.ResourceID),
391 ASDCP::TimedText::MT_OPENTYPE));
395 AutoMutex l(sg_default_font_family_list_lock);
396 if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
398 DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
399 return RESULT_FORMAT;
409 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
410 const ASDCP::TimedText::IResourceResolver& Resolver) const
412 FrameBuf.AssetID(uuid);
416 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
418 if ( rmi == m_ResourceTypes.end() )
420 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
424 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
426 if ( KM_SUCCESS(result) )
428 if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
430 FrameBuf.MIMEType("image/png");
432 else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
434 FrameBuf.MIMEType("application/x-font-opentype");
438 FrameBuf.MIMEType("application/octet-stream");
447 //------------------------------------------------------------------------------------------
449 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
453 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
457 // Opens the stream for reading, parses enough data to provide a complete
458 // set of stream metadata for the MXFWriter below.
460 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename, const std::string& profile_name) const
462 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
464 Result_t result = m_Parser->OpenRead(filename, profile_name);
466 if ( ASDCP_FAILURE(result) )
467 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
472 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
474 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename,
475 const std::string& profile_name) const
477 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
479 Result_t result = m_Parser->OpenRead(xml_doc, filename, profile_name);
481 if ( ASDCP_FAILURE(result) )
482 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
489 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
491 if ( m_Parser.empty() )
494 TDesc = m_Parser->m_TDesc;
498 // Reads the complete Timed Text Resource into the given string.
500 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
502 if ( m_Parser.empty() )
505 s = m_Parser->m_XMLDoc;
511 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
512 const ASDCP::TimedText::IResourceResolver* Resolver) const
514 if ( m_Parser.empty() )
518 Resolver = m_Parser->GetDefaultResolver();
520 return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
525 // end ST2052_TextParser.cpp