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;
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);
263 Result_t OpenRead(const std::string& xml_doc, const std::string& filename);
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)
272 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
274 if ( KM_SUCCESS(result) )
276 m_Filename = filename;
285 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename)
288 m_Filename = filename;
294 std::string const IMSC1_imageProfile = "http://www.w3.org/ns/ttml/profile/imsc1/image";
295 std::string const IMSC1_textProfile = "http://www.w3.org/ns/ttml/profile/imsc1/text";
299 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
301 setup_default_font_family_list();
303 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
305 DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
306 return RESULT_FORMAT;
309 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
310 m_TDesc.ResourceList.clear();
311 m_TDesc.ContainerDuration = 0;
312 std::set<std::string>::const_iterator i;
314 // Attempt to set the profile from <conformsToStandard>
315 if ( m_TDesc.NamespaceName.empty() )
317 ElementVisitor conforms_visitor("conformsToStandard");
318 apply_visitor(m_Root, conforms_visitor);
320 for ( i = conforms_visitor.value_list.begin(); i != conforms_visitor.value_list.end(); ++i )
322 if ( *i == IMSC1_imageProfile || *i == IMSC1_textProfile )
324 m_TDesc.NamespaceName = *i;
330 // Attempt to set the profile from the use of attribute "profile"
331 if ( m_TDesc.NamespaceName.empty() )
333 AttributeVisitor profile_visitor("profile");
334 apply_visitor(m_Root, profile_visitor);
336 for ( i = profile_visitor.value_list.begin(); i != profile_visitor.value_list.end(); ++i )
338 if ( *i == IMSC1_imageProfile || *i == IMSC1_textProfile )
340 m_TDesc.NamespaceName = *i;
346 // Find image resources for later packaging as GS partitions.
347 // Attempt to set the profile; infer from use of images.
348 AttributeVisitor png_visitor("backgroundImage");
349 apply_visitor(m_Root, png_visitor);
351 for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
353 UUID asset_id = CreatePNGNameId(PathBasename(*i));
354 TimedTextResourceDescriptor png_resource;
355 memcpy(png_resource.ResourceID, asset_id.Value(), UUIDlen);
356 png_resource.Type = ASDCP::TimedText::MT_PNG;
357 m_TDesc.ResourceList.push_back(png_resource);
358 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(png_resource.ResourceID),
359 ASDCP::TimedText::MT_PNG));
361 if ( m_TDesc.NamespaceName.empty() )
363 m_TDesc.NamespaceName = IMSC1_imageProfile;
367 // If images are present and profile is "text" make sure to say something.
368 if ( ! m_ResourceTypes.empty() && m_TDesc.NamespaceName == IMSC1_textProfile )
370 DefaultLogSink().Warn("Unexpected IMSC-1 text profile; document contains images.\n ");
373 // If all else fails set the profile to "text".
374 if ( m_TDesc.NamespaceName.empty() )
376 DefaultLogSink().Warn("Using default IMSC-1 text profile.\n ");
377 m_TDesc.NamespaceName = IMSC1_textProfile;
380 // Find font resources for later packaging as GS partitions.
381 AttributeVisitor font_visitor("fontFamily");
382 apply_visitor(m_Root, font_visitor);
385 for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
387 UUID font_id = CreateFontNameId(PathBasename(*i));
389 if ( PathIsFile(font_id.EncodeHex(buf, 64))
390 || PathIsFile(*i+".ttf")
391 || PathIsFile(*i+".otf") )
393 TimedTextResourceDescriptor font_resource;
394 memcpy(font_resource.ResourceID, font_id.Value(), UUIDlen);
395 font_resource.Type = ASDCP::TimedText::MT_OPENTYPE;
396 m_TDesc.ResourceList.push_back(font_resource);
397 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(font_resource.ResourceID),
398 ASDCP::TimedText::MT_OPENTYPE));
402 AutoMutex l(sg_default_font_family_list_lock);
403 if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
405 DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
406 return RESULT_FORMAT;
416 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
417 const ASDCP::TimedText::IResourceResolver& Resolver) const
419 FrameBuf.AssetID(uuid);
423 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
425 if ( rmi == m_ResourceTypes.end() )
427 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
431 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
433 if ( KM_SUCCESS(result) )
435 if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
437 FrameBuf.MIMEType("image/png");
439 else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
441 FrameBuf.MIMEType("application/x-font-opentype");
445 FrameBuf.MIMEType("application/octet-stream");
454 //------------------------------------------------------------------------------------------
456 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
460 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
464 // Opens the stream for reading, parses enough data to provide a complete
465 // set of stream metadata for the MXFWriter below.
467 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
469 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
471 Result_t result = m_Parser->OpenRead(filename);
473 if ( ASDCP_FAILURE(result) )
474 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
479 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
481 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
483 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
485 Result_t result = m_Parser->OpenRead(xml_doc, filename);
487 if ( ASDCP_FAILURE(result) )
488 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
495 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
497 if ( m_Parser.empty() )
500 TDesc = m_Parser->m_TDesc;
504 // Reads the complete Timed Text Resource into the given string.
506 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
508 if ( m_Parser.empty() )
511 s = m_Parser->m_XMLDoc;
517 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
518 const ASDCP::TimedText::IResourceResolver* Resolver) const
520 if ( m_Parser.empty() )
524 Resolver = m_Parser->GetDefaultResolver();
526 return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
531 // end ST2052_TextParser.cpp