hmac pad fix
[asdcplib.git] / src / TimedText_Parser.cpp
1 /*
2 Copyright (c) 2007-2009, 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    AS_DCP_TimedText.cpp
28     \version $Id$       
29     \brief   AS-DCP library, PCM essence reader and writer implementation
30 */
31
32
33 #include "AS_DCP_internal.h"
34 #include "S12MTimecode.h"
35 #include "KM_xml.h"
36
37 using namespace Kumu;
38 using namespace ASDCP;
39
40 using Kumu::DefaultLogSink;
41
42 const char* c_dcst_namespace_name = "http://www.smpte-ra.org/schemas/428-7/2007/DCST";
43
44 //------------------------------------------------------------------------------------------
45
46
47
48 class FilenameResolver : public ASDCP::TimedText::IResourceResolver
49 {
50   std::string m_Dirname;
51
52   FilenameResolver();
53   bool operator==(const FilenameResolver&);
54
55 public:
56   FilenameResolver(const std::string& dirname)
57   {
58     if ( PathIsDirectory(dirname) )
59       {
60         m_Dirname = dirname;
61         return;
62       }
63
64     DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
65     m_Dirname = ".";
66   }
67
68   //
69   Result_t ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
70   {
71     FileReader Reader;
72     char buf[64];
73     UUID RID(uuid);
74     std::string filename = m_Dirname + "/" + RID.EncodeHex(buf, 64);
75     DefaultLogSink().Debug("retrieving resource %s from file %s\n", buf, filename.c_str());
76
77     Result_t result = Reader.OpenRead(filename.c_str());
78
79     if ( KM_SUCCESS(result) )
80       {
81         ui32_t read_count = 0;
82         result = Reader.Read(FrameBuf.Data(), FrameBuf.Capacity(), &read_count);
83
84         if ( KM_SUCCESS(result) )
85           FrameBuf.Size(read_count);
86       }
87
88     return result;
89   }
90 };
91
92 //------------------------------------------------------------------------------------------
93
94 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
95
96 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
97 {
98   XMLElement  m_Root;
99   ResourceTypeMap_t m_ResourceTypes;
100   Result_t OpenRead();
101
102   ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
103
104 public:
105   std::string m_Filename;
106   std::string m_XMLDoc;
107   TimedTextDescriptor  m_TDesc;
108   mem_ptr<FilenameResolver> m_DefaultResolver;
109
110   h__SubtitleParser() : m_Root("**ParserRoot**")
111   {
112     memset(&m_TDesc.AssetID, 0, UUIDlen);
113   }
114
115   ~h__SubtitleParser() {}
116
117   TimedText::IResourceResolver* GetDefaultResolver()
118   {
119     if ( m_DefaultResolver.empty() )
120       m_DefaultResolver = new FilenameResolver(PathDirname(m_Filename));
121     
122     return m_DefaultResolver;
123   }
124
125   Result_t OpenRead(const char* filename);
126   Result_t OpenRead(const std::string& xml_doc, const char* filename);
127   Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
128 };
129
130 //
131 bool
132 get_UUID_from_element(XMLElement* Element, UUID& ID)
133 {
134   assert(Element);
135   const char* p = Element->GetBody().c_str();
136   if ( strncmp(p, "urn:uuid:", 9) == 0 )    p += 9;
137   return ID.DecodeHex(p);
138 }
139
140 //
141 bool
142 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
143 {
144   assert(name); assert(Parent);
145   XMLElement* Child = Parent->GetChildWithName(name);
146   if ( Child == 0 )    return false;
147   return get_UUID_from_element(Child, outID);
148 }
149
150 //
151 static ASDCP::Rational
152 decode_rational(const char* str_rat)
153 {
154   assert(str_rat);
155   ui32_t Num = atoi(str_rat);
156   ui32_t Den = 0;
157
158   const char* den_str = strrchr(str_rat, ' ');
159   if ( den_str != 0 )
160     Den = atoi(den_str+1);
161
162   return ASDCP::Rational(Num, Den);
163 }
164
165 //
166 Result_t
167 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const char* filename)
168 {
169   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
170
171   if ( KM_SUCCESS(result) )
172     result = OpenRead();
173
174   m_Filename = filename;
175   return result;
176 }
177
178 //
179 Result_t
180 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const char* filename)
181 {
182   m_XMLDoc = xml_doc;
183
184   if ( filename != 0 )
185     m_Filename = filename;
186   else
187     m_Filename = "<string>";
188
189   return OpenRead();
190 }
191
192 //
193 Result_t
194 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
195 {
196   if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
197     return RESULT_FORMAT;
198
199   m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
200   m_TDesc.ResourceList.clear();
201   m_TDesc.ContainerDuration = 0;
202   const XMLNamespace* ns = m_Root.Namespace();
203
204   if ( ns == 0 )
205     {
206       DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
207       m_TDesc.NamespaceName = c_dcst_namespace_name;
208     }
209   else
210     {
211       m_TDesc.NamespaceName = ns->Name();
212     }
213
214   UUID DocID;
215   if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
216     {
217       DefaultLogSink(). Error("Id element missing from input document\n");
218       return RESULT_FORMAT;
219     }
220
221   memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
222   XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
223
224   if ( EditRate == 0 )
225     {
226       DefaultLogSink(). Error("EditRate element missing from input document\n");
227       return RESULT_FORMAT;
228     }
229
230   m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
231
232   if ( m_TDesc.EditRate != EditRate_24 && m_TDesc.EditRate != EditRate_48 )
233     {
234       DefaultLogSink(). Error("EditRate must be 24/1 or 48/1\n");
235       return RESULT_FORMAT;
236     }
237
238   // list of fonts
239   ElementList FontList;
240   m_Root.GetChildrenWithName("LoadFont", FontList);
241
242   for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
243     {
244       UUID AssetID;
245       if ( ! get_UUID_from_element(*i, AssetID) )
246         {
247           DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
248           return RESULT_FORMAT;
249         }
250
251       TimedTextResourceDescriptor TmpResource;
252       memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
253       TmpResource.Type = MT_OPENTYPE;
254       m_TDesc.ResourceList.push_back(TmpResource);
255       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
256     }
257
258   // list of images
259   ElementList ImageList;
260   m_Root.GetChildrenWithName("Image", ImageList);
261
262   for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
263     {
264       UUID AssetID;
265       if ( ! get_UUID_from_element(*i, AssetID) )
266         {
267           DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
268           return RESULT_FORMAT;
269         }
270
271       TimedTextResourceDescriptor TmpResource;
272       memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
273       TmpResource.Type = MT_PNG;
274       m_TDesc.ResourceList.push_back(TmpResource);
275       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
276     }
277
278   // Calculate the timeline duration.
279   // This is a little ugly because the last element in the file is not necessarily
280   // the last instance to be displayed, e.g., element n and element n-1 may have the
281   // same start time but n-1 may have a greater duration making it the last to be seen.
282   // We must scan the list to accumulate the latest TimeOut value.
283   ElementList InstanceList;
284   ElementList::const_iterator ei;
285   ui32_t end_count = 0;
286   
287   m_Root.GetChildrenWithName("Subtitle", InstanceList);
288
289   if ( InstanceList.empty() )
290     {
291       DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
292       return RESULT_FORMAT;
293     }
294
295   // assumes 24/1 or 48/1 as constrained above
296   assert(m_TDesc.EditRate.Denominator == 1);
297
298   S12MTimecode beginTC;
299   beginTC.SetFPS(m_TDesc.EditRate.Numerator);
300   XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
301
302   if ( StartTime != 0 )
303     beginTC.DecodeString(StartTime->GetBody());
304
305   for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
306     {
307       S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), m_TDesc.EditRate.Numerator);
308       if ( end_count < tmpTC.GetFrames() )
309         end_count = tmpTC.GetFrames();
310     }
311
312   if ( end_count <= beginTC.GetFrames() )
313     {
314       DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
315       return RESULT_FORMAT;
316     }
317
318   m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
319
320   return RESULT_OK;
321 }
322
323
324 //
325 Result_t
326 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
327                                                                              const IResourceResolver& Resolver) const
328 {
329   FrameBuf.AssetID(uuid);
330   UUID TmpID(uuid);
331   char buf[64];
332
333   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
334
335   if ( rmi == m_ResourceTypes.end() )
336     {
337       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
338       return RESULT_RANGE;
339     }
340
341   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
342
343   if ( KM_SUCCESS(result) )
344     {
345       if ( (*rmi).second == MT_PNG )
346         FrameBuf.MIMEType("image/png");
347               
348       else if ( (*rmi).second == MT_OPENTYPE )
349         FrameBuf.MIMEType("application/x-font-opentype");
350
351       else
352         FrameBuf.MIMEType("application/octet-stream");
353     }
354
355   return result;
356 }
357
358 //------------------------------------------------------------------------------------------
359
360 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
361 {
362 }
363
364 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
365 {
366 }
367
368 // Opens the stream for reading, parses enough data to provide a complete
369 // set of stream metadata for the MXFWriter below.
370 ASDCP::Result_t
371 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
372 {
373   const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
374
375   Result_t result = m_Parser->OpenRead(filename);
376
377   if ( ASDCP_FAILURE(result) )
378     const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
379
380   return result;
381 }
382
383 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
384 Result_t
385 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const char* filename) const
386 {
387   const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
388
389   Result_t result = m_Parser->OpenRead(xml_doc, filename);
390
391   if ( ASDCP_FAILURE(result) )
392     const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
393
394   return result;
395 }
396
397 //
398 ASDCP::Result_t
399 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
400 {
401   if ( m_Parser.empty() )
402     return RESULT_INIT;
403
404   TDesc = m_Parser->m_TDesc;
405   return RESULT_OK;
406 }
407
408 // Reads the complete Timed Text Resource into the given string.
409 ASDCP::Result_t
410 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
411 {
412   if ( m_Parser.empty() )
413     return RESULT_INIT;
414
415   s = m_Parser->m_XMLDoc;
416   return RESULT_OK;
417 }
418
419 //
420 ASDCP::Result_t
421 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
422                                                           const IResourceResolver* Resolver) const
423 {
424   if ( m_Parser.empty() )
425     return RESULT_INIT;
426
427   if ( Resolver == 0 )
428     Resolver = m_Parser->GetDefaultResolver();
429
430   return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
431 }
432
433
434 //
435 // end AS_DCP_timedText.cpp
436 //