Permit 64-bit builds.
[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 ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
170     return RESULT_FORMAT;
171
172   m_Filename = filename;
173   m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
174   m_TDesc.ResourceList.clear();
175   m_TDesc.ContainerDuration = 0;
176   const XMLNamespace* ns = m_Root.Namespace();
177
178   if ( ns == 0 )
179     {
180       DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
181       m_TDesc.NamespaceName = c_dcst_namespace_name;
182     }
183   else
184     m_TDesc.NamespaceName = ns->Name();
185
186   UUID DocID;
187   if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
188     {
189       DefaultLogSink(). Error("Id element missing from input document\n");
190       return RESULT_FORMAT;
191     }
192
193   memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
194   XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
195
196   if ( EditRate == 0 )
197     {
198       DefaultLogSink(). Error("EditRate element missing from input document\n");
199       return RESULT_FORMAT;
200     }
201
202   m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
203
204   if ( m_TDesc.EditRate != EditRate_24 && m_TDesc.EditRate != EditRate_48 )
205     {
206       DefaultLogSink(). Error("EditRate must be 24/1 or 48/1\n");
207       return RESULT_FORMAT;
208     }
209
210   // list of fonts
211   ElementList FontList;
212   m_Root.GetChildrenWithName("LoadFont", FontList);
213
214   for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
215     {
216       UUID AssetID;
217       if ( ! get_UUID_from_element(*i, AssetID) )
218         {
219           DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
220           return RESULT_FORMAT;
221         }
222
223       TimedTextResourceDescriptor TmpResource;
224       memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
225       TmpResource.Type = MT_OPENTYPE;
226       m_TDesc.ResourceList.push_back(TmpResource);
227       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
228     }
229
230   // list of images
231   ElementList ImageList;
232   m_Root.GetChildrenWithName("Image", ImageList);
233
234   for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
235     {
236       UUID AssetID;
237       if ( ! get_UUID_from_element(*i, AssetID) )
238         {
239           DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
240           return RESULT_FORMAT;
241         }
242
243       TimedTextResourceDescriptor TmpResource;
244       memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
245       TmpResource.Type = MT_PNG;
246       m_TDesc.ResourceList.push_back(TmpResource);
247       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
248     }
249
250   // Calculate the timeline duration.
251   // This is a little ugly because the last element in the file is not necessarily
252   // the last instance to be displayed, e.g., element n and element n-1 may have the
253   // same start time but n-1 may have a greater duration making it the last to be seen.
254   // We must scan the list to accumulate the latest TimeOut value.
255   ElementList InstanceList;
256   ElementList::const_iterator ei;
257   ui32_t end_count = 0;
258   
259   m_Root.GetChildrenWithName("Subtitle", InstanceList);
260
261   if ( InstanceList.empty() )
262     {
263       DefaultLogSink(). Error("XML document contains no Subtitle elements!\n");
264       return RESULT_FORMAT;
265     }
266
267   // assumes 24/1 or 48/1 as constrained above
268   S12MTimecode beginTC(InstanceList.front()->GetAttrWithName("TimeIn"), m_TDesc.EditRate.Numerator);
269
270   for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
271     {
272       S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), m_TDesc.EditRate.Numerator);
273       if ( end_count < tmpTC.GetFrames() )
274         end_count = tmpTC.GetFrames();
275     }
276
277   assert( end_count > beginTC.GetFrames() );
278   m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
279
280   return RESULT_OK;
281 }
282
283
284 //
285 Result_t
286 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
287                                                                              const IResourceResolver& Resolver) const
288 {
289   FrameBuf.AssetID(uuid);
290   UUID TmpID(uuid);
291   char buf[64];
292
293   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
294
295   if ( rmi == m_ResourceTypes.end() )
296     {
297       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
298       return RESULT_RANGE;
299     }
300
301   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
302
303   if ( KM_SUCCESS(result) )
304     {
305       if ( (*rmi).second == MT_PNG )
306         FrameBuf.MIMEType("image/png");
307               
308       else if ( (*rmi).second == MT_OPENTYPE )
309         FrameBuf.MIMEType("application/x-font-opentype");
310
311       else
312         FrameBuf.MIMEType("application/octet-stream");
313     }
314
315   return result;
316 }
317
318 //------------------------------------------------------------------------------------------
319
320 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
321 {
322 }
323
324 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
325 {
326 }
327
328 // Opens the stream for reading, parses enough data to provide a complete
329 // set of stream metadata for the MXFWriter below.
330 ASDCP::Result_t
331 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
332 {
333   const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
334
335   Result_t result = m_Parser->OpenRead(filename);
336
337   if ( ASDCP_FAILURE(result) )
338     const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
339
340   return result;
341 }
342
343 //
344 ASDCP::Result_t
345 ASDCP::TimedText::DCSubtitleParser::FillDescriptor(TimedTextDescriptor& TDesc) const
346 {
347   if ( m_Parser.empty() )
348     return RESULT_INIT;
349
350   TDesc = m_Parser->m_TDesc;
351   return RESULT_OK;
352 }
353
354 // Reads the complete Timed Text Resource into the given string.
355 ASDCP::Result_t
356 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
357 {
358   if ( m_Parser.empty() )
359     return RESULT_INIT;
360
361   s = m_Parser->m_XMLDoc;
362   return RESULT_OK;
363 }
364
365 //
366 ASDCP::Result_t
367 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
368                                                           const IResourceResolver* Resolver) const
369 {
370   if ( m_Parser.empty() )
371     return RESULT_INIT;
372
373   if ( Resolver == 0 )
374     Resolver = m_Parser->GetDefaultResolver();
375
376   return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
377 }
378
379
380 //
381 // end AS_DCP_timedText.cpp
382 //