Update copyright dates.
[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
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   assert(m_TDesc.EditRate.Denominator == 1);
272
273   S12MTimecode beginTC;
274   beginTC.SetFPS(m_TDesc.EditRate.Numerator);
275   XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
276
277   if ( StartTime != 0 )
278     beginTC.DecodeString(StartTime->GetBody());
279
280   for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
281     {
282       S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), m_TDesc.EditRate.Numerator);
283       if ( end_count < tmpTC.GetFrames() )
284         end_count = tmpTC.GetFrames();
285     }
286
287   if ( end_count <= beginTC.GetFrames() )
288     {
289       DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
290       return RESULT_FORMAT;
291     }
292
293   m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
294
295   return RESULT_OK;
296 }
297
298
299 //
300 Result_t
301 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
302                                                                              const IResourceResolver& Resolver) const
303 {
304   FrameBuf.AssetID(uuid);
305   UUID TmpID(uuid);
306   char buf[64];
307
308   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
309
310   if ( rmi == m_ResourceTypes.end() )
311     {
312       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
313       return RESULT_RANGE;
314     }
315
316   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
317
318   if ( KM_SUCCESS(result) )
319     {
320       if ( (*rmi).second == MT_PNG )
321         FrameBuf.MIMEType("image/png");
322               
323       else if ( (*rmi).second == MT_OPENTYPE )
324         FrameBuf.MIMEType("application/x-font-opentype");
325
326       else
327         FrameBuf.MIMEType("application/octet-stream");
328     }
329
330   return result;
331 }
332
333 //------------------------------------------------------------------------------------------
334
335 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
336 {
337 }
338
339 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
340 {
341 }
342
343 // Opens the stream for reading, parses enough data to provide a complete
344 // set of stream metadata for the MXFWriter below.
345 ASDCP::Result_t
346 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
347 {
348   const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
349
350   Result_t result = m_Parser->OpenRead(filename);
351
352   if ( ASDCP_FAILURE(result) )
353     const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
354
355   return result;
356 }
357
358 //
359 ASDCP::Result_t
360 ASDCP::TimedText::DCSubtitleParser::FillDescriptor(TimedTextDescriptor& TDesc) const
361 {
362   if ( m_Parser.empty() )
363     return RESULT_INIT;
364
365   TDesc = m_Parser->m_TDesc;
366   return RESULT_OK;
367 }
368
369 // Reads the complete Timed Text Resource into the given string.
370 ASDCP::Result_t
371 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
372 {
373   if ( m_Parser.empty() )
374     return RESULT_INIT;
375
376   s = m_Parser->m_XMLDoc;
377   return RESULT_OK;
378 }
379
380 //
381 ASDCP::Result_t
382 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
383                                                           const IResourceResolver* Resolver) const
384 {
385   if ( m_Parser.empty() )
386     return RESULT_INIT;
387
388   if ( Resolver == 0 )
389     Resolver = m_Parser->GetDefaultResolver();
390
391   return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
392 }
393
394
395 //
396 // end AS_DCP_timedText.cpp
397 //