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