o Replaced WIN32 directory scanner with dirent_win.h
[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 Kumu::UUID
90 AS_02::TimedText::CreatePNGNameId(const std::string& image_name)
91 {
92   return create_4122_type5_id(image_name, s_png_id_prefix);
93 }
94
95 //
96 Kumu::UUID
97 AS_02::TimedText::CreateFontNameId(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   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");
118 }
119
120
121 //------------------------------------------------------------------------------------------
122
123
124 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
125 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
126
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 };
130
131 //
132 Result_t
133 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
134 {
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];
140
141   if ( abs_dirname.empty() )
142     {
143       abs_dirname = ".";
144     }
145
146   Result_t result = dir_reader.Open(abs_dirname);
147
148   if ( KM_SUCCESS(result) )
149     {
150       while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
151         {
152           if ( next_item[0] == '.' ) continue; // no hidden files
153           std::string tmp_path = PathJoin(abs_dirname, next_item);
154
155           if ( ft == DET_FILE )
156             {
157               FileReader reader;
158               Result_t read_result = reader.OpenRead(tmp_path);
159
160               if ( KM_SUCCESS(read_result) )
161                 {
162                   read_result = reader.Read(read_buffer, 16);
163                 }
164
165               if ( KM_SUCCESS(read_result) )
166                 {
167                   // is it PNG?
168                   if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
169                     {
170                       UUID asset_id = CreatePNGNameId(PathBasename(next_item));
171                       m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
172                     }
173                   // is it a font?
174                   else if ( memcmp(read_buffer, OpenTypeMagic, sizeof(OpenTypeMagic)) == 0
175                             || memcmp(read_buffer, TrueTypeMagic, sizeof(TrueTypeMagic)) == 0 )
176                     {
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));
180                     }
181                 }
182             }
183         }
184     }
185
186   return result;
187 }
188
189 //
190 Result_t
191 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
192 {
193   Kumu::UUID tmp_id(uuid);
194   char buf[64];
195
196   ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
197
198   if ( i == m_ResourceMap.end() )
199     {
200       DefaultLogSink().Debug("Missing timed-text resource \"%s\"\n", tmp_id.EncodeHex(buf, 64));
201       return RESULT_NOT_FOUND;
202     }
203
204   FileReader Reader;
205
206   DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
207
208   Result_t result = Reader.OpenRead(i->second.c_str());
209
210   if ( KM_SUCCESS(result) )
211     {
212       ui32_t read_count, read_size = Reader.Size();
213       result = FrameBuf.Capacity(read_size);
214       
215       if ( KM_SUCCESS(result) )
216         result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
217       
218       if ( KM_SUCCESS(result) )
219         FrameBuf.Size(read_count);
220     }
221
222   return result;
223 }
224
225 //------------------------------------------------------------------------------------------
226
227 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
228
229 class AS_02::TimedText::ST2052_TextParser::h__TextParser
230 {
231   XMLElement  m_Root;
232   ResourceTypeMap_t m_ResourceTypes;
233   Result_t OpenRead(const std::string& profile_name);
234
235   ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
236
237 public:
238   std::string m_Filename;
239   std::string m_XMLDoc;
240   TimedTextDescriptor  m_TDesc;
241   ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
242
243   h__TextParser() : m_Root("**ParserRoot**")
244   {
245     memset(&m_TDesc.AssetID, 0, UUIDlen);
246   }
247
248   ~h__TextParser() {}
249
250   ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
251   {
252     if ( m_DefaultResolver.empty() )
253       {
254         AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
255         resolver->OpenRead(PathDirname(m_Filename));
256         m_DefaultResolver = resolver;
257       }
258     
259     return m_DefaultResolver;
260   }
261
262   Result_t OpenRead(const std::string& filename, const std::string& profile_name);
263   Result_t OpenRead(const std::string& xml_doc, const std::string& filename, const std::string& profile_name);
264   Result_t ReadAncillaryResource(const byte_t *uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
265                                  const ASDCP::TimedText::IResourceResolver& Resolver) const;
266 };
267
268 //
269 Result_t
270 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename, const std::string& profile_name)
271 {
272   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
273
274   if ( KM_SUCCESS(result) )
275     {
276       m_Filename = filename;
277       result = OpenRead(profile_name);
278     }
279
280   return result;
281 }
282
283 //
284 Result_t
285 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename,
286                                                              const std::string& profile_name)
287 {
288   m_XMLDoc = xml_doc;
289   m_Filename = filename;
290   return OpenRead(profile_name);
291 }
292
293 //
294 template <class VisitorType>
295 bool
296 apply_visitor(const XMLElement& element, VisitorType& visitor)
297 {
298   const ElementList& l = element.GetChildren();
299   ElementList::const_iterator i;
300
301   for ( i = l.begin(); i != l.end(); ++i )
302     {
303       if ( ! visitor.Element(**i) )
304         {
305           return false;
306         }
307
308       if ( ! apply_visitor(**i, visitor) )
309         {
310           return false;
311         }
312     }
313
314   return true;
315 }
316
317 //
318 class AttributeVisitor
319 {
320   std::string attr_name;
321
322 public:
323   AttributeVisitor(const std::string& n) : attr_name(n) {}
324   std::set<std::string> value_list;
325
326   bool Element(const XMLElement& e)
327   {
328     const AttributeList& l = e.GetAttributes();
329     AttributeList::const_iterator i;
330  
331     for ( i = l.begin(); i != l.end(); ++i )
332       {
333         if ( i->name == attr_name )
334           {
335             value_list.insert(i->value);
336           }
337       }
338
339     return true;
340   }
341 };
342
343 //
344 Result_t
345 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& profile_name)
346 {
347   setup_default_font_family_list();
348
349   if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
350     {
351       DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
352       return RESULT_FORMAT;
353     }
354
355   m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
356   m_TDesc.ResourceList.clear();
357   m_TDesc.ContainerDuration = 0;
358   m_TDesc.NamespaceName = profile_name;
359
360   AttributeVisitor png_visitor("backgroundImage");
361   apply_visitor(m_Root, png_visitor);
362   std::set<std::string>::const_iterator i;
363
364   for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
365     {
366       UUID asset_id = CreatePNGNameId(PathBasename(*i));
367       TimedTextResourceDescriptor png_resource;
368       memcpy(png_resource.ResourceID, asset_id.Value(), UUIDlen);
369       png_resource.Type = ASDCP::TimedText::MT_PNG;
370       m_TDesc.ResourceList.push_back(png_resource);
371       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(png_resource.ResourceID),
372                                                            ASDCP::TimedText::MT_PNG));
373     }
374
375   AttributeVisitor font_visitor("fontFamily");
376   apply_visitor(m_Root, font_visitor);
377   char buf[64];
378
379   for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
380     {
381       UUID font_id = CreateFontNameId(PathBasename(*i));
382
383       if ( PathIsFile(font_id.EncodeHex(buf, 64))
384            || PathIsFile(*i+".ttf")
385            || PathIsFile(*i+".otf") )
386         {
387           TimedTextResourceDescriptor font_resource;
388           memcpy(font_resource.ResourceID, font_id.Value(), UUIDlen);
389           font_resource.Type = ASDCP::TimedText::MT_OPENTYPE;
390           m_TDesc.ResourceList.push_back(font_resource);
391           m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(font_resource.ResourceID),
392                                                                ASDCP::TimedText::MT_OPENTYPE));
393         }
394       else
395         {
396           AutoMutex l(sg_default_font_family_list_lock);
397           if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
398             {
399               DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
400               return RESULT_FORMAT;
401             }
402         }
403     }
404
405   return RESULT_OK;
406 }
407
408 //
409 Result_t
410 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
411                                                                           const ASDCP::TimedText::IResourceResolver& Resolver) const
412 {
413   FrameBuf.AssetID(uuid);
414   UUID TmpID(uuid);
415   char buf[64];
416
417   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
418
419   if ( rmi == m_ResourceTypes.end() )
420     {
421       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
422       return RESULT_RANGE;
423     }
424
425   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
426
427   if ( KM_SUCCESS(result) )
428     {
429       if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
430         {
431           FrameBuf.MIMEType("image/png");
432         }    
433       else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
434         {
435           FrameBuf.MIMEType("application/x-font-opentype");
436         }
437       else
438         {
439           FrameBuf.MIMEType("application/octet-stream");
440         }
441     }
442
443   return result;
444 }
445
446
447
448 //------------------------------------------------------------------------------------------
449
450 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
451 {
452 }
453
454 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
455 {
456 }
457
458 // Opens the stream for reading, parses enough data to provide a complete
459 // set of stream metadata for the MXFWriter below.
460 ASDCP::Result_t
461 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename, const std::string& profile_name) const
462 {
463   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
464
465   Result_t result = m_Parser->OpenRead(filename, profile_name);
466
467   if ( ASDCP_FAILURE(result) )
468     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
469
470   return result;
471 }
472
473 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
474 Result_t
475 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename,
476                                               const std::string& profile_name) const
477 {
478   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
479
480   Result_t result = m_Parser->OpenRead(xml_doc, filename, profile_name);
481
482   if ( ASDCP_FAILURE(result) )
483     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
484
485   return result;
486 }
487
488 //
489 ASDCP::Result_t
490 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
491 {
492   if ( m_Parser.empty() )
493     return RESULT_INIT;
494
495   TDesc = m_Parser->m_TDesc;
496   return RESULT_OK;
497 }
498
499 // Reads the complete Timed Text Resource into the given string.
500 ASDCP::Result_t
501 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
502 {
503   if ( m_Parser.empty() )
504     return RESULT_INIT;
505
506   s = m_Parser->m_XMLDoc;
507   return RESULT_OK;
508 }
509
510 //
511 ASDCP::Result_t
512 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
513                                                            const ASDCP::TimedText::IResourceResolver* Resolver) const
514 {
515   if ( m_Parser.empty() )
516     return RESULT_INIT;
517
518   if ( Resolver == 0 )
519     Resolver = m_Parser->GetDefaultResolver();
520
521   return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
522 }
523
524
525 //
526 // end ST2052_TextParser.cpp
527 //