2 Copyright (c) 2013-2015, 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
33 #include "AS_02_internal.h"
35 #include <openssl/sha.h>
38 using namespace ASDCP;
40 using Kumu::DefaultLogSink;
42 const char* c_tt_namespace_name = "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt";
45 //------------------------------------------------------------------------------------------
48 int const NS_ID_LENGTH = 16;
51 static byte_t s_png_id_prefix[NS_ID_LENGTH] = {
53 // 2067-2 5.4.5 / RFC4122 Appendix C
54 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
55 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
59 static byte_t s_font_id_prefix[NS_ID_LENGTH] = {
62 0xb6, 0xcc, 0x57, 0xa0, 0x87, 0xe7, 0x4e, 0x75,
63 0xb1, 0xc3, 0x33, 0x59, 0xf3, 0xae, 0x88, 0x17
68 create_4122_type5_id(const std::string& subject_name, const byte_t* ns_id)
72 SHA1_Update(&ctx, ns_id, NS_ID_LENGTH);
73 SHA1_Update(&ctx, (byte_t*)subject_name.c_str(), subject_name.size());
75 const ui32_t sha_len = 20;
76 byte_t bin_buf[sha_len];
77 SHA1_Final(bin_buf, &ctx);
79 // Derive the asset ID from the digest. Make it a type-5 UUID
80 byte_t buf[UUID_Length];
81 memcpy(buf, bin_buf, UUID_Length);
82 buf[6] &= 0x0f; // clear bits 4-7
83 buf[6] |= 0x50; // set UUID version 'digest'
84 buf[8] &= 0x3f; // clear bits 6&7
85 buf[8] |= 0x80; // set bit 7
86 return Kumu::UUID(buf);
91 create_png_name_id(const std::string& image_name)
93 return create_4122_type5_id(image_name, s_png_id_prefix);
98 create_font_name_id(const std::string& font_name)
100 return create_4122_type5_id(font_name, s_font_id_prefix);
103 //------------------------------------------------------------------------------------------
106 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
107 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
109 const byte_t PNGMagic[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
110 const byte_t OpenTypeMagic[5] = { 0x4f, 0x54, 0x54, 0x4f, 0x00 };
111 const byte_t TrueTypeMagic[5] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
115 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
117 DirScannerEx dir_reader;
118 DirectoryEntryType_t ft;
119 std::string next_item;
120 std::string abs_dirname = PathMakeCanonical(dirname);
121 byte_t read_buffer[16];
123 if ( abs_dirname.empty() )
128 Result_t result = dir_reader.Open(abs_dirname);
130 if ( KM_SUCCESS(result) )
132 while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
134 if ( next_item[0] == '.' ) continue; // no hidden files
135 std::string tmp_path = PathJoin(abs_dirname, next_item);
137 if ( ft == DET_FILE )
140 Result_t read_result = reader.OpenRead(tmp_path);
142 if ( KM_SUCCESS(read_result) )
144 read_result = reader.Read(read_buffer, 16);
147 if ( KM_SUCCESS(read_result) )
150 if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
152 UUID asset_id = create_png_name_id(next_item);
153 m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
156 else if ( memcmp(read_buffer, OpenTypeMagic, sizeof(OpenTypeMagic)) == 0
157 || memcmp(read_buffer, TrueTypeMagic, sizeof(TrueTypeMagic)) == 0 )
159 fprintf(stderr, "wrap font!\n");
160 UUID asset_id = create_font_name_id(next_item);
161 m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
173 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
175 Kumu::UUID tmp_id(uuid);
178 ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
180 if ( i == m_ResourceMap.end() )
182 DefaultLogSink().Debug("Missing timed-text resource \"%s\"\n", tmp_id.EncodeHex(buf, 64));
183 return RESULT_NOT_FOUND;
188 DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
190 Result_t result = Reader.OpenRead(i->second.c_str());
192 if ( KM_SUCCESS(result) )
194 ui32_t read_count, read_size = Reader.Size();
195 result = FrameBuf.Capacity(read_size);
197 if ( KM_SUCCESS(result) )
198 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
200 if ( KM_SUCCESS(result) )
201 FrameBuf.Size(read_count);
207 //------------------------------------------------------------------------------------------
209 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
211 class AS_02::TimedText::ST2052_TextParser::h__TextParser
214 ResourceTypeMap_t m_ResourceTypes;
217 ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
220 std::string m_Filename;
221 std::string m_XMLDoc;
222 TimedTextDescriptor m_TDesc;
223 ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
225 h__TextParser() : m_Root("**ParserRoot**")
227 memset(&m_TDesc.AssetID, 0, UUIDlen);
232 ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
234 if ( m_DefaultResolver.empty() )
236 AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
237 resolver->OpenRead(PathDirname(m_Filename));
238 m_DefaultResolver = resolver;
241 return m_DefaultResolver;
244 Result_t OpenRead(const std::string& filename);
245 Result_t OpenRead(const std::string& xml_doc, const std::string& filename);
246 Result_t ReadAncillaryResource(const byte_t *uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
247 const ASDCP::TimedText::IResourceResolver& Resolver) const;
252 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename)
254 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
256 if ( KM_SUCCESS(result) )
258 m_Filename = filename;
267 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename)
270 m_Filename = filename;
275 template <class VisitorType>
277 apply_visitor(const XMLElement& element, VisitorType& visitor)
279 const ElementList& l = element.GetChildren();
280 ElementList::const_iterator i;
282 for ( i = l.begin(); i != l.end(); ++i )
284 if ( ! visitor.Element(**i) )
289 if ( ! apply_visitor(**i, visitor) )
299 class AttributeVisitor
301 std::string attr_name;
304 AttributeVisitor(const std::string& n) : attr_name(n) {}
305 std::set<std::string> value_list;
307 bool Element(const XMLElement& e)
309 const AttributeList& l = e.GetAttributes();
310 AttributeList::const_iterator i;
312 for ( i = l.begin(); i != l.end(); ++i )
314 if ( i->name == attr_name )
316 value_list.insert(i->value);
326 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
328 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
330 return RESULT_FORMAT;
333 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
334 m_TDesc.ResourceList.clear();
335 m_TDesc.ContainerDuration = 0;
336 const XMLNamespace* ns = m_Root.Namespace();
340 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_tt_namespace_name);
341 m_TDesc.NamespaceName = c_tt_namespace_name;
345 m_TDesc.NamespaceName = ns->Name();
348 AttributeVisitor png_visitor("backgroundImage");
349 apply_visitor(m_Root, png_visitor);
350 std::set<std::string>::const_iterator i;
352 for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
354 UUID asset_id = create_png_name_id(*i);
355 TimedTextResourceDescriptor png_resource;
356 memcpy(png_resource.ResourceID, asset_id.Value(), UUIDlen);
357 png_resource.Type = ASDCP::TimedText::MT_PNG;
358 m_TDesc.ResourceList.push_back(png_resource);
359 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(png_resource.ResourceID),
360 ASDCP::TimedText::MT_PNG));
363 AttributeVisitor font_visitor("fontFamily");
364 apply_visitor(m_Root, font_visitor);
366 for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
368 UUID font_id = create_font_name_id(*i);
369 TimedTextResourceDescriptor font_resource;
370 memcpy(font_resource.ResourceID, font_id.Value(), UUIDlen);
371 font_resource.Type = ASDCP::TimedText::MT_OPENTYPE;
372 m_TDesc.ResourceList.push_back(font_resource);
373 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(font_resource.ResourceID),
374 ASDCP::TimedText::MT_OPENTYPE));
382 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
383 const ASDCP::TimedText::IResourceResolver& Resolver) const
385 FrameBuf.AssetID(uuid);
389 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
391 if ( rmi == m_ResourceTypes.end() )
393 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
397 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
399 if ( KM_SUCCESS(result) )
401 if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
403 FrameBuf.MIMEType("image/png");
405 else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
407 FrameBuf.MIMEType("application/x-font-opentype");
411 FrameBuf.MIMEType("application/octet-stream");
420 //------------------------------------------------------------------------------------------
422 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
426 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
430 // Opens the stream for reading, parses enough data to provide a complete
431 // set of stream metadata for the MXFWriter below.
433 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
435 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
437 Result_t result = m_Parser->OpenRead(filename);
439 if ( ASDCP_FAILURE(result) )
440 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
445 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
447 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
449 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
451 Result_t result = m_Parser->OpenRead(xml_doc, filename);
453 if ( ASDCP_FAILURE(result) )
454 const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
461 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
463 if ( m_Parser.empty() )
466 TDesc = m_Parser->m_TDesc;
470 // Reads the complete Timed Text Resource into the given string.
472 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
474 if ( m_Parser.empty() )
477 s = m_Parser->m_XMLDoc;
483 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
484 const ASDCP::TimedText::IResourceResolver* Resolver) const
486 if ( m_Parser.empty() )
490 Resolver = m_Parser->GetDefaultResolver();
492 return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
497 // end ST2052_TextParser.cpp