Modified to enforce execution order of the predicates
[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();
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);
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;
266 };
267
268 //
269 Result_t
270 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename)
271 {
272   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
273
274   if ( KM_SUCCESS(result) )
275     {
276       m_Filename = filename;
277       result = OpenRead();
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 {
287   m_XMLDoc = xml_doc;
288   m_Filename = filename;
289   return OpenRead();
290 }
291
292
293
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";
296
297 //
298 Result_t
299 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
300 {
301   setup_default_font_family_list();
302
303   if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
304     {
305       DefaultLogSink(). Error("ST 2052-1 document is not well-formed.\n");
306       return RESULT_FORMAT;
307     }
308
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;
313
314   // Attempt to set the profile from <conformsToStandard>
315   if ( m_TDesc.NamespaceName.empty() )
316     {
317       ElementVisitor conforms_visitor("conformsToStandard");
318       apply_visitor(m_Root, conforms_visitor);
319
320       for ( i = conforms_visitor.value_list.begin(); i != conforms_visitor.value_list.end(); ++i )
321         {
322           if ( *i == IMSC1_imageProfile || *i == IMSC1_textProfile )
323             {
324               m_TDesc.NamespaceName = *i;
325               break;
326             }
327         }
328     }
329
330   // Attempt to set the profile from the use of attribute "profile"
331   if ( m_TDesc.NamespaceName.empty() )
332     {
333       AttributeVisitor profile_visitor("profile");
334       apply_visitor(m_Root, profile_visitor);
335
336       for ( i = profile_visitor.value_list.begin(); i != profile_visitor.value_list.end(); ++i )
337         {
338           if ( *i == IMSC1_imageProfile || *i == IMSC1_textProfile )
339             {
340               m_TDesc.NamespaceName = *i;
341               break;
342             }
343         }
344     }
345
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);
350
351   for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
352     {
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));
360
361       if ( m_TDesc.NamespaceName.empty() )
362         {
363           m_TDesc.NamespaceName = IMSC1_imageProfile;
364         }
365     }
366
367   // If images are present and profile is "text" make sure to say something.
368   if ( ! m_ResourceTypes.empty() && m_TDesc.NamespaceName == IMSC1_textProfile )
369     {
370       DefaultLogSink().Warn("Unexpected IMSC-1 text profile; document contains images.\n ");
371     }
372   
373   // If all else fails set the profile to "text".
374   if ( m_TDesc.NamespaceName.empty() )
375     {
376       DefaultLogSink().Warn("Using default IMSC-1 text profile.\n ");
377       m_TDesc.NamespaceName = IMSC1_textProfile;
378     }
379
380   // Find font resources for later packaging as GS partitions.
381   AttributeVisitor font_visitor("fontFamily");
382   apply_visitor(m_Root, font_visitor);
383   char buf[64];
384
385   for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
386     {
387       UUID font_id = CreateFontNameId(PathBasename(*i));
388
389       if ( PathIsFile(font_id.EncodeHex(buf, 64))
390            || PathIsFile(*i+".ttf")
391            || PathIsFile(*i+".otf") )
392         {
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));
399         }
400       else
401         {
402           AutoMutex l(sg_default_font_family_list_lock);
403           if ( sg_default_font_family_list.find(*i) == sg_default_font_family_list.end() )
404             {
405               DefaultLogSink(). Error("Unable to locate external font resource \"%s\".\n", i->c_str());
406               return RESULT_FORMAT;
407             }
408         }
409     }
410
411   return RESULT_OK;
412 }
413
414 //
415 Result_t
416 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
417                                                                           const ASDCP::TimedText::IResourceResolver& Resolver) const
418 {
419   FrameBuf.AssetID(uuid);
420   UUID TmpID(uuid);
421   char buf[64];
422
423   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
424
425   if ( rmi == m_ResourceTypes.end() )
426     {
427       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
428       return RESULT_RANGE;
429     }
430
431   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
432
433   if ( KM_SUCCESS(result) )
434     {
435       if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
436         {
437           FrameBuf.MIMEType("image/png");
438         }    
439       else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
440         {
441           FrameBuf.MIMEType("application/x-font-opentype");
442         }
443       else
444         {
445           FrameBuf.MIMEType("application/octet-stream");
446         }
447     }
448
449   return result;
450 }
451
452
453
454 //------------------------------------------------------------------------------------------
455
456 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
457 {
458 }
459
460 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
461 {
462 }
463
464 // Opens the stream for reading, parses enough data to provide a complete
465 // set of stream metadata for the MXFWriter below.
466 ASDCP::Result_t
467 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
468 {
469   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
470
471   Result_t result = m_Parser->OpenRead(filename);
472
473   if ( ASDCP_FAILURE(result) )
474     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
475
476   return result;
477 }
478
479 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
480 Result_t
481 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
482 {
483   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
484
485   Result_t result = m_Parser->OpenRead(xml_doc, filename);
486
487   if ( ASDCP_FAILURE(result) )
488     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
489
490   return result;
491 }
492
493 //
494 ASDCP::Result_t
495 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
496 {
497   if ( m_Parser.empty() )
498     return RESULT_INIT;
499
500   TDesc = m_Parser->m_TDesc;
501   return RESULT_OK;
502 }
503
504 // Reads the complete Timed Text Resource into the given string.
505 ASDCP::Result_t
506 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
507 {
508   if ( m_Parser.empty() )
509     return RESULT_INIT;
510
511   s = m_Parser->m_XMLDoc;
512   return RESULT_OK;
513 }
514
515 //
516 ASDCP::Result_t
517 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
518                                                            const ASDCP::TimedText::IResourceResolver* Resolver) const
519 {
520   if ( m_Parser.empty() )
521     return RESULT_INIT;
522
523   if ( Resolver == 0 )
524     Resolver = m_Parser->GetDefaultResolver();
525
526   return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
527 }
528
529
530 //
531 // end ST2052_TextParser.cpp
532 //