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