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