release
[asdcplib.git] / src / ST2052_TextParser.cpp
1 /*
2 Copyright (c) 2013-2015, 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
33 #include "AS_02_internal.h"
34 #include "KM_xml.h"
35 #include <openssl/sha.h>
36
37 using namespace Kumu;
38 using namespace ASDCP;
39
40 using Kumu::DefaultLogSink;
41
42 const char* c_tt_namespace_name = "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt";
43
44
45 //------------------------------------------------------------------------------------------
46
47 //
48 int const NS_ID_LENGTH = 16;
49
50 //
51 static byte_t s_png_id_prefix[NS_ID_LENGTH] = {
52   // RFC 4122 type 5
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
56 };
57
58 //
59 static byte_t s_font_id_prefix[NS_ID_LENGTH] = {
60   // RFC 4122 type 5
61   // 2067-2 5.4.6
62   0xb6, 0xcc, 0x57, 0xa0, 0x87, 0xe7, 0x4e, 0x75,
63   0xb1, 0xc3, 0x33, 0x59, 0xf3, 0xae, 0x88, 0x17
64 };
65
66 //
67 static Kumu::UUID
68 create_4122_type5_id(const std::string& subject_name, const byte_t* ns_id)
69 {
70   SHA_CTX ctx;
71   SHA1_Init(&ctx);
72   SHA1_Update(&ctx, ns_id, NS_ID_LENGTH);
73   SHA1_Update(&ctx, (byte_t*)subject_name.c_str(), subject_name.size());
74
75   const ui32_t sha_len = 20;
76   byte_t bin_buf[sha_len];
77   SHA1_Final(bin_buf, &ctx);
78
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);
87 }
88
89 //
90 static Kumu::UUID
91 create_png_name_id(const std::string& image_name)
92 {
93   return create_4122_type5_id(image_name, s_png_id_prefix);
94 }
95
96 //
97 static Kumu::UUID
98 create_font_name_id(const std::string& font_name)
99 {
100   return create_4122_type5_id(font_name, s_font_id_prefix);
101 }
102
103 //------------------------------------------------------------------------------------------
104
105
106 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
107 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
108
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 };
112
113 //
114 Result_t
115 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
116 {
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];
122
123   if ( abs_dirname.empty() )
124     {
125       abs_dirname = ".";
126     }
127
128   Result_t result = dir_reader.Open(abs_dirname);
129
130   if ( KM_SUCCESS(result) )
131     {
132       while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
133         {
134           if ( next_item[0] == '.' ) continue; // no hidden files
135           std::string tmp_path = PathJoin(abs_dirname, next_item);
136
137           if ( ft == DET_FILE )
138             {
139               FileReader reader;
140               Result_t read_result = reader.OpenRead(tmp_path);
141
142               if ( KM_SUCCESS(read_result) )
143                 {
144                   read_result = reader.Read(read_buffer, 16);
145                 }
146
147               if ( KM_SUCCESS(read_result) )
148                 {
149                   // is it PNG?
150                   if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
151                     {
152                       UUID asset_id = create_png_name_id(next_item);
153                       m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
154                     }
155                   // is it a font?
156                   else if ( memcmp(read_buffer, OpenTypeMagic, sizeof(OpenTypeMagic)) == 0
157                             || memcmp(read_buffer, TrueTypeMagic, sizeof(TrueTypeMagic)) == 0 )
158                     {
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));
162                     }
163                 }
164             }
165         }
166     }
167
168   return result;
169 }
170
171 //
172 Result_t
173 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
174 {
175   Kumu::UUID tmp_id(uuid);
176   char buf[64];
177
178   ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
179
180   if ( i == m_ResourceMap.end() )
181     {
182       DefaultLogSink().Debug("Missing timed-text resource \"%s\"\n", tmp_id.EncodeHex(buf, 64));
183       return RESULT_NOT_FOUND;
184     }
185
186   FileReader Reader;
187
188   DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
189
190   Result_t result = Reader.OpenRead(i->second.c_str());
191
192   if ( KM_SUCCESS(result) )
193     {
194       ui32_t read_count, read_size = Reader.Size();
195       result = FrameBuf.Capacity(read_size);
196       
197       if ( KM_SUCCESS(result) )
198         result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
199       
200       if ( KM_SUCCESS(result) )
201         FrameBuf.Size(read_count);
202     }
203
204   return result;
205 }
206
207 //------------------------------------------------------------------------------------------
208
209 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
210
211 class AS_02::TimedText::ST2052_TextParser::h__TextParser
212 {
213   XMLElement  m_Root;
214   ResourceTypeMap_t m_ResourceTypes;
215   Result_t OpenRead();
216
217   ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
218
219 public:
220   std::string m_Filename;
221   std::string m_XMLDoc;
222   TimedTextDescriptor  m_TDesc;
223   ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
224
225   h__TextParser() : m_Root("**ParserRoot**")
226   {
227     memset(&m_TDesc.AssetID, 0, UUIDlen);
228   }
229
230   ~h__TextParser() {}
231
232   ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
233   {
234     if ( m_DefaultResolver.empty() )
235       {
236         AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
237         resolver->OpenRead(PathDirname(m_Filename));
238         m_DefaultResolver = resolver;
239       }
240     
241     return m_DefaultResolver;
242   }
243
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;
248 };
249
250 //
251 Result_t
252 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename)
253 {
254   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
255
256   if ( KM_SUCCESS(result) )
257     {
258       m_Filename = filename;
259       result = OpenRead();
260     }
261
262   return result;
263 }
264
265 //
266 Result_t
267 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename)
268 {
269   m_XMLDoc = xml_doc;
270   m_Filename = filename;
271   return OpenRead();
272 }
273
274 //
275 template <class VisitorType>
276 bool
277 apply_visitor(const XMLElement& element, VisitorType& visitor)
278 {
279   const ElementList& l = element.GetChildren();
280   ElementList::const_iterator i;
281
282   for ( i = l.begin(); i != l.end(); ++i )
283     {
284       if ( ! visitor.Element(**i) )
285         {
286           return false;
287         }
288
289       if ( ! apply_visitor(**i, visitor) )
290         {
291           return false;
292         }
293     }
294
295   return true;
296 }
297
298 //
299 class AttributeVisitor
300 {
301   std::string attr_name;
302
303 public:
304   AttributeVisitor(const std::string& n) : attr_name(n) {}
305   std::set<std::string> value_list;
306
307   bool Element(const XMLElement& e)
308   {
309     const AttributeList& l = e.GetAttributes();
310     AttributeList::const_iterator i;
311  
312     for ( i = l.begin(); i != l.end(); ++i )
313       {
314         if ( i->name == attr_name )
315           {
316             value_list.insert(i->value);
317           }
318       }
319
320     return true;
321   }
322 };
323
324 //
325 Result_t
326 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
327 {
328   if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
329     {
330       return RESULT_FORMAT;
331     }
332
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();
337
338   if ( ns == 0 )
339     {
340       DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_tt_namespace_name);
341       m_TDesc.NamespaceName = c_tt_namespace_name;
342     }
343   else
344     {
345       m_TDesc.NamespaceName = ns->Name();
346     }
347
348   AttributeVisitor png_visitor("backgroundImage");
349   apply_visitor(m_Root, png_visitor);
350   std::set<std::string>::const_iterator i;
351
352   for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
353     {
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));
361     }
362
363   AttributeVisitor font_visitor("fontFamily");
364   apply_visitor(m_Root, font_visitor);
365
366   for ( i = font_visitor.value_list.begin(); i != font_visitor.value_list.end(); ++i )
367     {
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));
375     }
376
377   return RESULT_OK;
378 }
379
380 //
381 Result_t
382 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
383                                                                           const ASDCP::TimedText::IResourceResolver& Resolver) const
384 {
385   FrameBuf.AssetID(uuid);
386   UUID TmpID(uuid);
387   char buf[64];
388
389   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
390
391   if ( rmi == m_ResourceTypes.end() )
392     {
393       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
394       return RESULT_RANGE;
395     }
396
397   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
398
399   if ( KM_SUCCESS(result) )
400     {
401       if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
402         {
403           FrameBuf.MIMEType("image/png");
404         }    
405       else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
406         {
407           FrameBuf.MIMEType("application/x-font-opentype");
408         }
409       else
410         {
411           FrameBuf.MIMEType("application/octet-stream");
412         }
413     }
414
415   return result;
416 }
417
418
419
420 //------------------------------------------------------------------------------------------
421
422 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
423 {
424 }
425
426 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
427 {
428 }
429
430 // Opens the stream for reading, parses enough data to provide a complete
431 // set of stream metadata for the MXFWriter below.
432 ASDCP::Result_t
433 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
434 {
435   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
436
437   Result_t result = m_Parser->OpenRead(filename);
438
439   if ( ASDCP_FAILURE(result) )
440     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
441
442   return result;
443 }
444
445 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
446 Result_t
447 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
448 {
449   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
450
451   Result_t result = m_Parser->OpenRead(xml_doc, filename);
452
453   if ( ASDCP_FAILURE(result) )
454     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
455
456   return result;
457 }
458
459 //
460 ASDCP::Result_t
461 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
462 {
463   if ( m_Parser.empty() )
464     return RESULT_INIT;
465
466   TDesc = m_Parser->m_TDesc;
467   return RESULT_OK;
468 }
469
470 // Reads the complete Timed Text Resource into the given string.
471 ASDCP::Result_t
472 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
473 {
474   if ( m_Parser.empty() )
475     return RESULT_INIT;
476
477   s = m_Parser->m_XMLDoc;
478   return RESULT_OK;
479 }
480
481 //
482 ASDCP::Result_t
483 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
484                                                            const ASDCP::TimedText::IResourceResolver* Resolver) const
485 {
486   if ( m_Parser.empty() )
487     return RESULT_INIT;
488
489   if ( Resolver == 0 )
490     Resolver = m_Parser->GetDefaultResolver();
491
492   return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
493 }
494
495
496 //
497 // end ST2052_TextParser.cpp
498 //