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