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