o Fixed a bug in the index reader that allowed an out-of-bounds vector index to be...
[asdcplib.git] / src / ST2052_TextParser.cpp
1 /*
2 Copyright (c) 2013-2016, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
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.
15
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.
26 */
27 /*! \file    ST2052_TimedText.cpp
28     \version $Id$       
29     \brief   AS-DCP library, PCM essence reader and writer implementation
30 */
31
32 #include "AS_02_internal.h"
33 #include "KM_xml.h"
34 #include <openssl/sha.h>
35
36 using namespace Kumu;
37 using namespace ASDCP;
38
39 using Kumu::DefaultLogSink;
40
41 const char* c_tt_namespace_name = "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt";
42
43
44 //------------------------------------------------------------------------------------------
45
46 //
47 int const NS_ID_LENGTH = 16;
48
49 //
50 static byte_t s_png_id_prefix[NS_ID_LENGTH] = {
51   // RFC 4122 type 5
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
55 };
56
57 //
58 static byte_t s_font_id_prefix[NS_ID_LENGTH] = {
59   // RFC 4122 type 5
60   // 2067-2 5.4.6
61   0xb6, 0xcc, 0x57, 0xa0, 0x87, 0xe7, 0x4e, 0x75,
62   0xb1, 0xc3, 0x33, 0x59, 0xf3, 0xae, 0x88, 0x17
63 };
64
65 //
66 static Kumu::UUID
67 create_4122_type5_id(const std::string& subject_name, const byte_t* ns_id)
68 {
69   SHA_CTX ctx;
70   SHA1_Init(&ctx);
71   SHA1_Update(&ctx, ns_id, NS_ID_LENGTH);
72   SHA1_Update(&ctx, (byte_t*)subject_name.c_str(), subject_name.size());
73
74   const ui32_t sha_len = 20;
75   byte_t bin_buf[sha_len];
76   SHA1_Final(bin_buf, &ctx);
77
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);
86 }
87
88 //
89 static Kumu::UUID
90 create_png_name_id(const std::string& image_name)
91 {
92   return create_4122_type5_id(image_name, s_png_id_prefix);
93 }
94
95 //
96 static Kumu::UUID
97 create_font_name_id(const std::string& font_name)
98 {
99   return create_4122_type5_id(font_name, s_font_id_prefix);
100 }
101
102 //
103 static Kumu::Mutex sg_default_font_family_list_lock;
104 static std::set<std::string> sg_default_font_family_list;
105
106 static void
107 setup_default_font_family_list()
108 {
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");
117 }
118
119
120 //------------------------------------------------------------------------------------------
121
122
123 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
124 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
125
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 };
129
130 //
131 Result_t
132 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
133 {
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];
139
140   if ( abs_dirname.empty() )
141     {
142       abs_dirname = ".";
143     }
144
145   Result_t result = dir_reader.Open(abs_dirname);
146
147   if ( KM_SUCCESS(result) )
148     {
149       while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
150         {
151           if ( next_item[0] == '.' ) continue; // no hidden files
152           std::string tmp_path = PathJoin(abs_dirname, next_item);
153
154           if ( ft == DET_FILE )
155             {
156               FileReader reader;
157               Result_t read_result = reader.OpenRead(tmp_path);
158
159               if ( KM_SUCCESS(read_result) )
160                 {
161                   read_result = reader.Read(read_buffer, 16);
162                 }
163
164               if ( KM_SUCCESS(read_result) )
165                 {
166                   // is it PNG?
167                   if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
168                     {
169                       UUID asset_id = create_png_name_id(next_item);
170                       m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
171                     }
172                   // is it a font?
173                   else if ( memcmp(read_buffer, OpenTypeMagic, sizeof(OpenTypeMagic)) == 0
174                             || memcmp(read_buffer, TrueTypeMagic, sizeof(TrueTypeMagic)) == 0 )
175                     {
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));
179                     }
180                 }
181             }
182         }
183     }
184
185   return result;
186 }
187
188 //
189 Result_t
190 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
191 {
192   Kumu::UUID tmp_id(uuid);
193   char buf[64];
194
195   ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
196
197   if ( i == m_ResourceMap.end() )
198     {
199       DefaultLogSink().Debug("Missing timed-text resource \"%s\"\n", tmp_id.EncodeHex(buf, 64));
200       return RESULT_NOT_FOUND;
201     }
202
203   FileReader Reader;
204
205   DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
206
207   Result_t result = Reader.OpenRead(i->second.c_str());
208
209   if ( KM_SUCCESS(result) )
210     {
211       ui32_t read_count, read_size = Reader.Size();
212       result = FrameBuf.Capacity(read_size);
213       
214       if ( KM_SUCCESS(result) )
215         result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
216       
217       if ( KM_SUCCESS(result) )
218         FrameBuf.Size(read_count);
219     }
220
221   return result;
222 }
223
224 //------------------------------------------------------------------------------------------
225
226 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
227
228 class AS_02::TimedText::ST2052_TextParser::h__TextParser
229 {
230   XMLElement  m_Root;
231   ResourceTypeMap_t m_ResourceTypes;
232   Result_t OpenRead();
233
234   ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
235
236 public:
237   std::string m_Filename;
238   std::string m_XMLDoc;
239   TimedTextDescriptor  m_TDesc;
240   ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
241
242   h__TextParser() : m_Root("**ParserRoot**")
243   {
244     memset(&m_TDesc.AssetID, 0, UUIDlen);
245   }
246
247   ~h__TextParser() {}
248
249   ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
250   {
251     if ( m_DefaultResolver.empty() )
252       {
253         AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
254         resolver->OpenRead(PathDirname(m_Filename));
255         m_DefaultResolver = resolver;
256       }
257     
258     return m_DefaultResolver;
259   }
260
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;
265 };
266
267 //
268 Result_t
269 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename)
270 {
271   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
272
273   if ( KM_SUCCESS(result) )
274     {
275       m_Filename = filename;
276       result = OpenRead();
277     }
278
279   return result;
280 }
281
282 //
283 Result_t
284 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename)
285 {
286   m_XMLDoc = xml_doc;
287   m_Filename = filename;
288   return OpenRead();
289 }
290
291 //
292 template <class VisitorType>
293 bool
294 apply_visitor(const XMLElement& element, VisitorType& visitor)
295 {
296   const ElementList& l = element.GetChildren();
297   ElementList::const_iterator i;
298
299   for ( i = l.begin(); i != l.end(); ++i )
300     {
301       if ( ! visitor.Element(**i) )
302         {
303           return false;
304         }
305
306       if ( ! apply_visitor(**i, visitor) )
307         {
308           return false;
309         }
310     }
311
312   return true;
313 }
314
315 //
316 class AttributeVisitor
317 {
318   std::string attr_name;
319
320 public:
321   AttributeVisitor(const std::string& n) : attr_name(n) {}
322   std::set<std::string> value_list;
323
324   bool Element(const XMLElement& e)
325   {
326     const AttributeList& l = e.GetAttributes();
327     AttributeList::const_iterator i;
328  
329     for ( i = l.begin(); i != l.end(); ++i )
330       {
331         if ( i->name == attr_name )
332           {
333             value_list.insert(i->value);
334           }
335       }
336
337     return true;
338   }
339 };
340
341 //
342 Result_t
343 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
344 {
345   setup_default_font_family_list();
346
347   if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
348     {
349       DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
350       return RESULT_FORMAT;
351     }
352
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();
357
358   if ( ns == 0 )
359     {
360       DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_tt_namespace_name);
361       m_TDesc.NamespaceName = c_tt_namespace_name;
362     }
363   else
364     {
365       m_TDesc.NamespaceName = ns->Name();
366     }
367
368   AttributeVisitor png_visitor("backgroundImage");
369   apply_visitor(m_Root, png_visitor);
370   std::set<std::string>::const_iterator i;
371
372   for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
373     {
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));
381     }
382
383   AttributeVisitor font_visitor("fontFamily");
384   apply_visitor(m_Root, font_visitor);
385   char buf[64];
386
387   for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
388     {
389       UUID font_id = create_font_name_id(*i);
390
391       if ( PathIsFile(font_id.EncodeHex(buf, 64))
392            || PathIsFile(*i+".ttf")
393            || PathIsFile(*i+".otf") )
394         {
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));
401         }
402       else
403         {
404           AutoMutex l(sg_default_font_family_list_lock);
405           if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
406             {
407               DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
408               return RESULT_FORMAT;
409             }
410         }
411     }
412
413   return RESULT_OK;
414 }
415
416 //
417 Result_t
418 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
419                                                                           const ASDCP::TimedText::IResourceResolver& Resolver) const
420 {
421   FrameBuf.AssetID(uuid);
422   UUID TmpID(uuid);
423   char buf[64];
424
425   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
426
427   if ( rmi == m_ResourceTypes.end() )
428     {
429       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
430       return RESULT_RANGE;
431     }
432
433   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
434
435   if ( KM_SUCCESS(result) )
436     {
437       if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
438         {
439           FrameBuf.MIMEType("image/png");
440         }    
441       else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
442         {
443           FrameBuf.MIMEType("application/x-font-opentype");
444         }
445       else
446         {
447           FrameBuf.MIMEType("application/octet-stream");
448         }
449     }
450
451   return result;
452 }
453
454
455
456 //------------------------------------------------------------------------------------------
457
458 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
459 {
460 }
461
462 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
463 {
464 }
465
466 // Opens the stream for reading, parses enough data to provide a complete
467 // set of stream metadata for the MXFWriter below.
468 ASDCP::Result_t
469 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
470 {
471   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
472
473   Result_t result = m_Parser->OpenRead(filename);
474
475   if ( ASDCP_FAILURE(result) )
476     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
477
478   return result;
479 }
480
481 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
482 Result_t
483 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
484 {
485   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
486
487   Result_t result = m_Parser->OpenRead(xml_doc, filename);
488
489   if ( ASDCP_FAILURE(result) )
490     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
491
492   return result;
493 }
494
495 //
496 ASDCP::Result_t
497 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
498 {
499   if ( m_Parser.empty() )
500     return RESULT_INIT;
501
502   TDesc = m_Parser->m_TDesc;
503   return RESULT_OK;
504 }
505
506 // Reads the complete Timed Text Resource into the given string.
507 ASDCP::Result_t
508 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
509 {
510   if ( m_Parser.empty() )
511     return RESULT_INIT;
512
513   s = m_Parser->m_XMLDoc;
514   return RESULT_OK;
515 }
516
517 //
518 ASDCP::Result_t
519 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
520                                                            const ASDCP::TimedText::IResourceResolver* Resolver) const
521 {
522   if ( m_Parser.empty() )
523     return RESULT_INIT;
524
525   if ( Resolver == 0 )
526     Resolver = m_Parser->GetDefaultResolver();
527
528   return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
529 }
530
531
532 //
533 // end ST2052_TextParser.cpp
534 //