compile fixes
[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 //
49 static byte_t s_id_prefix[16] = {
50   // RFC 4122 type 5
51   // 2067-2 5.4.5
52   0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
53   0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
54 };
55
56 //
57 void
58 gen_png_name_id(const std::string& image_name, UUID& asset_id)
59 {
60   SHA_CTX ctx;
61   SHA1_Init(&ctx);
62   SHA1_Update(&ctx, s_id_prefix, 16);
63   SHA1_Update(&ctx, (byte_t*)image_name.c_str(), image_name.length());
64
65   const ui32_t sha_len = 20;
66   byte_t bin_buf[sha_len];
67   SHA1_Final(bin_buf, &ctx);
68
69   // Derive the asset ID from the digest. Make it a type-5 UUID
70   byte_t buf[UUID_Length];
71   memcpy(buf, bin_buf, UUID_Length);
72   buf[6] &= 0x0f; // clear bits 4-7
73   buf[6] |= 0x50; // set UUID version 'digest'
74   buf[8] &= 0x3f; // clear bits 6&7
75   buf[8] |= 0x80; // set bit 7
76   asset_id.Set(buf);
77 }
78
79 //------------------------------------------------------------------------------------------
80
81
82 AS_02::TimedText::Type5UUIDFilenameResolver::Type5UUIDFilenameResolver() {}
83 AS_02::TimedText::Type5UUIDFilenameResolver::~Type5UUIDFilenameResolver() {}
84
85 const byte_t PNGMagic[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
86 const byte_t OpenTypeMagic[5] = { 0x4f, 0x54, 0x54, 0x4f, 0x00 };
87 const byte_t TrueTypeMagic[5] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
88
89 //
90 Result_t
91 AS_02::TimedText::Type5UUIDFilenameResolver::OpenRead(const std::string& dirname)
92 {
93   DirScannerEx dir_reader;
94   DirectoryEntryType_t ft;
95   std::string next_item;
96   std::string abs_dirname = PathMakeCanonical(dirname);
97   byte_t read_buffer[16];
98
99   if ( abs_dirname.empty() )
100     {
101       abs_dirname = ".";
102     }
103
104   if ( KM_SUCCESS(dir_reader.Open(abs_dirname.c_str())) )
105     {
106       while ( KM_SUCCESS(dir_reader.GetNext(next_item, ft)) )
107         {
108           if ( next_item[0] == '.' ) continue; // no hidden files
109           std::string tmp_path = PathJoin(abs_dirname, next_item);
110
111           if ( ft == DET_FILE )
112             {
113               FileReader reader;
114               Result_t result = reader.OpenRead(tmp_path);
115
116               if ( KM_SUCCESS(result) )
117                 {
118                   result = reader.Read(read_buffer, 16);
119                 }
120
121               if ( KM_SUCCESS(result) )
122                 {
123                   // is it PNG?
124                   if ( memcmp(read_buffer, PNGMagic, sizeof(PNGMagic)) == 0 )
125                     {
126                       UUID asset_id;
127                       gen_png_name_id(next_item, asset_id);
128                       m_ResourceMap.insert(ResourceMap::value_type(asset_id, next_item));
129                     }
130                 }
131             }
132         }
133     }
134 }
135
136 //
137 Result_t
138 AS_02::TimedText::Type5UUIDFilenameResolver::ResolveRID(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf) const
139 {
140   Kumu::UUID tmp_id(uuid);
141   char buf[64];
142
143   ResourceMap::const_iterator i = m_ResourceMap.find(tmp_id);
144
145   if ( i == m_ResourceMap.end() )
146     {
147       return RESULT_NOT_FOUND;
148     }
149
150   FileReader Reader;
151
152   DefaultLogSink().Debug("Retrieving resource %s from file %s\n", tmp_id.EncodeHex(buf, 64), i->second.c_str());
153
154   Result_t result = Reader.OpenRead(i->second.c_str());
155
156   if ( KM_SUCCESS(result) )
157     {
158       ui32_t read_count, read_size = Reader.Size();
159       result = FrameBuf.Capacity(read_size);
160       
161       if ( KM_SUCCESS(result) )
162         result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
163       
164       if ( KM_SUCCESS(result) )
165         FrameBuf.Size(read_count);
166     }
167
168   return result;
169 }
170
171 //------------------------------------------------------------------------------------------
172
173 typedef std::map<Kumu::UUID, ASDCP::TimedText::MIMEType_t> ResourceTypeMap_t;
174
175 class AS_02::TimedText::ST2052_TextParser::h__TextParser
176 {
177   XMLElement  m_Root;
178   ResourceTypeMap_t m_ResourceTypes;
179   Result_t OpenRead();
180
181   ASDCP_NO_COPY_CONSTRUCT(h__TextParser);
182
183 public:
184   std::string m_Filename;
185   std::string m_XMLDoc;
186   TimedTextDescriptor  m_TDesc;
187   ASDCP::mem_ptr<ASDCP::TimedText::IResourceResolver> m_DefaultResolver;
188
189   h__TextParser() : m_Root("**ParserRoot**")
190   {
191     memset(&m_TDesc.AssetID, 0, UUIDlen);
192   }
193
194   ~h__TextParser() {}
195
196   ASDCP::TimedText::IResourceResolver* GetDefaultResolver()
197   {
198     if ( m_DefaultResolver.empty() )
199       {
200         AS_02::TimedText::Type5UUIDFilenameResolver *resolver = new AS_02::TimedText::Type5UUIDFilenameResolver;
201         resolver->OpenRead(PathDirname(m_Filename));
202         m_DefaultResolver = resolver;
203       }
204     
205     return m_DefaultResolver;
206   }
207
208   Result_t OpenRead(const std::string& filename);
209   Result_t OpenRead(const std::string& xml_doc, const std::string& filename);
210   Result_t ReadAncillaryResource(const byte_t *uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
211                                  const ASDCP::TimedText::IResourceResolver& Resolver) const;
212 };
213
214 //
215 Result_t
216 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& filename)
217 {
218   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
219
220   if ( KM_SUCCESS(result) )
221     {
222       m_Filename = filename;
223       result = OpenRead();
224     }
225
226   return result;
227 }
228
229 //
230 Result_t
231 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead(const std::string& xml_doc, const std::string& filename)
232 {
233   m_XMLDoc = xml_doc;
234   m_Filename = filename;
235   return OpenRead();
236 }
237
238 //
239 template <class VisitorType>
240 bool
241 apply_visitor(const XMLElement& element, VisitorType& visitor)
242 {
243   const ElementList& l = element.GetChildren();
244   ElementList::const_iterator i;
245
246   for ( i = l.begin(); i != l.end(); ++i )
247     {
248       if ( ! visitor.Element(**i) )
249         {
250           return false;
251         }
252
253       if ( ! apply_visitor(**i, visitor) )
254         {
255           return false;
256         }
257     }
258
259   return true;
260 }
261
262 //
263 class AttributeVisitor
264 {
265   std::string attr_name;
266
267 public:
268   AttributeVisitor(const std::string& n) : attr_name(n) {}
269   std::set<std::string> value_list;
270
271   bool Element(const XMLElement& e)
272   {
273     const AttributeList& l = e.GetAttributes();
274     AttributeList::const_iterator i;
275  
276     for ( i = l.begin(); i != l.end(); ++i )
277       {
278         if ( i->name == attr_name )
279           {
280             value_list.insert(i->value);
281           }
282       }
283
284     return true;
285   }
286 };
287
288 //
289 Result_t
290 AS_02::TimedText::ST2052_TextParser::h__TextParser::OpenRead()
291 {
292   if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
293     {
294       return RESULT_FORMAT;
295     }
296
297   m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
298   m_TDesc.ResourceList.clear();
299   m_TDesc.ContainerDuration = 0;
300   const XMLNamespace* ns = m_Root.Namespace();
301
302   if ( ns == 0 )
303     {
304       DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_tt_namespace_name);
305       m_TDesc.NamespaceName = c_tt_namespace_name;
306     }
307   else
308     {
309       m_TDesc.NamespaceName = ns->Name();
310     }
311
312   AttributeVisitor png_visitor("backgroundImage");
313   apply_visitor(m_Root, png_visitor);
314   std::set<std::string>::const_iterator i;
315
316   for ( i = png_visitor.value_list.begin(); i != png_visitor.value_list.end(); ++i )
317     {
318       UUID asset_id;
319       gen_png_name_id(*i, asset_id);
320       TimedTextResourceDescriptor TmpResource;
321       memcpy(TmpResource.ResourceID, asset_id.Value(), UUIDlen);
322       TmpResource.Type = ASDCP::TimedText::MT_PNG;
323       m_TDesc.ResourceList.push_back(TmpResource);
324       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), ASDCP::TimedText::MT_PNG));
325     }
326
327   return RESULT_OK;
328 }
329
330 //
331 Result_t
332 AS_02::TimedText::ST2052_TextParser::h__TextParser::ReadAncillaryResource(const byte_t* uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
333                                                                           const ASDCP::TimedText::IResourceResolver& Resolver) const
334 {
335   FrameBuf.AssetID(uuid);
336   UUID TmpID(uuid);
337   char buf[64];
338
339   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
340
341   if ( rmi == m_ResourceTypes.end() )
342     {
343       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
344       return RESULT_RANGE;
345     }
346
347   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
348
349   if ( KM_SUCCESS(result) )
350     {
351       if ( (*rmi).second == ASDCP::TimedText::MT_PNG )
352         {
353           FrameBuf.MIMEType("image/png");
354         }    
355       else if ( (*rmi).second == ASDCP::TimedText::MT_OPENTYPE )
356         {
357           FrameBuf.MIMEType("application/x-font-opentype");
358         }
359       else
360         {
361           FrameBuf.MIMEType("application/octet-stream");
362         }
363     }
364
365   return result;
366 }
367
368
369
370 //------------------------------------------------------------------------------------------
371
372 AS_02::TimedText::ST2052_TextParser::ST2052_TextParser()
373 {
374 }
375
376 AS_02::TimedText::ST2052_TextParser::~ST2052_TextParser()
377 {
378 }
379
380 // Opens the stream for reading, parses enough data to provide a complete
381 // set of stream metadata for the MXFWriter below.
382 ASDCP::Result_t
383 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& filename) const
384 {
385   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
386
387   Result_t result = m_Parser->OpenRead(filename);
388
389   if ( ASDCP_FAILURE(result) )
390     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
391
392   return result;
393 }
394
395 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
396 Result_t
397 AS_02::TimedText::ST2052_TextParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
398 {
399   const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = new h__TextParser;
400
401   Result_t result = m_Parser->OpenRead(xml_doc, filename);
402
403   if ( ASDCP_FAILURE(result) )
404     const_cast<AS_02::TimedText::ST2052_TextParser*>(this)->m_Parser = 0;
405
406   return result;
407 }
408
409 //
410 ASDCP::Result_t
411 AS_02::TimedText::ST2052_TextParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
412 {
413   if ( m_Parser.empty() )
414     return RESULT_INIT;
415
416   TDesc = m_Parser->m_TDesc;
417   return RESULT_OK;
418 }
419
420 // Reads the complete Timed Text Resource into the given string.
421 ASDCP::Result_t
422 AS_02::TimedText::ST2052_TextParser::ReadTimedTextResource(std::string& s) const
423 {
424   if ( m_Parser.empty() )
425     return RESULT_INIT;
426
427   s = m_Parser->m_XMLDoc;
428   return RESULT_OK;
429 }
430
431 //
432 ASDCP::Result_t
433 AS_02::TimedText::ST2052_TextParser::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
434                                                            const ASDCP::TimedText::IResourceResolver* Resolver) const
435 {
436   if ( m_Parser.empty() )
437     return RESULT_INIT;
438
439   if ( Resolver == 0 )
440     Resolver = m_Parser->GetDefaultResolver();
441
442   return m_Parser->ReadAncillaryResource(uuid.Value(), FrameBuf, *Resolver);
443 }
444
445
446 //
447 // end ST2052_TextParser.cpp
448 //