ISXDDataEssenceDescriptor_NamespaceURI UL fixed
[asdcplib.git] / src / TimedText_Parser.cpp
1 /*
2 Copyright (c) 2007-2014, 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 ASDCP::TimedText::LocalFilenameResolver::LocalFilenameResolver() {}
48 ASDCP::TimedText::LocalFilenameResolver::~LocalFilenameResolver() {}
49
50 //
51 Result_t
52 ASDCP::TimedText::LocalFilenameResolver::OpenRead(const std::string& dirname)
53 {
54   if ( PathIsDirectory(dirname) )
55     {
56       m_Dirname = dirname;
57       return RESULT_OK;
58     }
59
60   DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
61   m_Dirname = ".";
62   return RESULT_FALSE;
63 }
64
65 //
66 Result_t
67 ASDCP::TimedText::LocalFilenameResolver::ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
68 {
69   Result_t result = RESULT_NOT_FOUND;
70   char buf[64];
71   UUID RID(uuid);
72   PathList_t found_list;
73
74 #ifndef KM_WIN32
75   // TODO, fix this for win32 (needs regex)
76   FindInPath(PathMatchRegex(RID.EncodeHex(buf, 64)), m_Dirname, found_list);
77 #endif
78
79   if ( found_list.size() == 1 )
80     {
81       FileReader Reader;
82       DefaultLogSink().Debug("Retrieving resource %s from file %s\n", buf, found_list.front().c_str());
83
84       result = Reader.OpenRead(found_list.front().c_str());
85
86       if ( KM_SUCCESS(result) )
87         {
88           ui32_t read_count, read_size = Reader.Size();
89           result = FrameBuf.Capacity(read_size);
90       
91           if ( KM_SUCCESS(result) )
92             result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
93
94           if ( KM_SUCCESS(result) )
95             FrameBuf.Size(read_count);
96         }
97     }
98   else if ( ! found_list.empty() )
99     {
100       DefaultLogSink().Error("More than one file in %s matches %s.\n", m_Dirname.c_str(), buf);
101       result = RESULT_RAW_FORMAT;
102     }
103
104   return result;
105 }
106
107 //------------------------------------------------------------------------------------------
108
109 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
110
111 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
112 {
113   XMLElement  m_Root;
114   ResourceTypeMap_t m_ResourceTypes;
115   Result_t OpenRead();
116
117   ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
118
119 public:
120   std::string m_Filename;
121   std::string m_XMLDoc;
122   TimedTextDescriptor  m_TDesc;
123   mem_ptr<LocalFilenameResolver> m_DefaultResolver;
124
125   h__SubtitleParser() : m_Root("**ParserRoot**")
126   {
127     memset(&m_TDesc.AssetID, 0, UUIDlen);
128   }
129
130   ~h__SubtitleParser() {}
131
132   TimedText::IResourceResolver* GetDefaultResolver()
133   {
134     if ( m_DefaultResolver.empty() )
135       {
136         m_DefaultResolver = new LocalFilenameResolver();
137         m_DefaultResolver->OpenRead(PathDirname(m_Filename));
138       }
139
140     return m_DefaultResolver;
141   }
142
143   Result_t OpenRead(const std::string& filename);
144   Result_t OpenRead(const std::string& xml_doc, const std::string& filename);
145   Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
146 };
147
148 //
149 bool
150 get_UUID_from_element(XMLElement* Element, UUID& ID)
151 {
152   assert(Element);
153   const char* p = Element->GetBody().c_str();
154
155   if ( strncmp(p, "urn:uuid:", 9) == 0 )
156     {
157       p += 9;
158     }
159   
160   return ID.DecodeHex(p);
161 }
162
163 //
164 bool
165 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
166 {
167   assert(name);
168   assert(Parent);
169   XMLElement* Child = Parent->GetChildWithName(name);
170
171   if ( Child == 0 )
172     {
173       return false;
174     }
175
176   return get_UUID_from_element(Child, outID);
177 }
178
179 //
180 Result_t
181 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& filename)
182 {
183   Result_t result = ReadFileIntoString(filename, m_XMLDoc);
184
185   if ( KM_SUCCESS(result) )
186     result = OpenRead();
187
188   m_Filename = filename;
189   return result;
190 }
191
192 //
193 Result_t
194 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename)
195 {
196   m_XMLDoc = xml_doc;
197
198   if ( filename.empty() )
199     {
200       m_Filename = "<string>";
201     }
202   else
203     {
204       m_Filename = filename;
205     }
206
207   return OpenRead();
208 }
209
210 //
211 Result_t
212 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
213 {
214   if ( ! m_Root.ParseString(m_XMLDoc) )
215     return RESULT_FORMAT;
216
217   m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
218   m_TDesc.ResourceList.clear();
219   m_TDesc.ContainerDuration = 0;
220   const XMLNamespace* ns = m_Root.Namespace();
221
222   if ( ns == 0 )
223     {
224       DefaultLogSink(). Warn("Document has no namespace name, assuming \"%s\".\n", c_dcst_namespace_name);
225       m_TDesc.NamespaceName = c_dcst_namespace_name;
226     }
227   else
228     {
229       m_TDesc.NamespaceName = ns->Name();
230     }
231
232   UUID DocID;
233   if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
234     {
235       DefaultLogSink(). Error("Id element missing from input document.\n");
236       return RESULT_FORMAT;
237     }
238
239   memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
240   XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
241
242   if ( EditRate == 0 )
243     {
244       DefaultLogSink().Error("EditRate element missing from input document.\n");
245       return RESULT_FORMAT;
246     }
247
248   if ( ! DecodeRational(EditRate->GetBody().c_str(), m_TDesc.EditRate) )
249     {
250       DefaultLogSink().Error("Error decoding edit rate value: \"%s\"\n", EditRate->GetBody().c_str());
251       return RESULT_FORMAT;
252     }
253
254   if ( m_TDesc.EditRate != EditRate_23_98
255        && m_TDesc.EditRate != EditRate_24
256        && m_TDesc.EditRate != EditRate_25
257        && m_TDesc.EditRate != EditRate_30
258        && m_TDesc.EditRate != EditRate_48
259        && m_TDesc.EditRate != EditRate_50
260        && m_TDesc.EditRate != EditRate_60
261        && m_TDesc.EditRate != EditRate_96
262        && m_TDesc.EditRate != EditRate_100
263        && m_TDesc.EditRate != EditRate_120
264        && m_TDesc.EditRate != EditRate_192
265        && m_TDesc.EditRate != EditRate_200
266        && m_TDesc.EditRate != EditRate_240 )
267     {
268       DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
269                               m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
270       return RESULT_FORMAT;
271     }
272
273   // list of fonts
274   ElementList FontList;
275   m_Root.GetChildrenWithName("LoadFont", FontList);
276
277   for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
278     {
279       UUID AssetID;
280       if ( ! get_UUID_from_element(*i, AssetID) )
281         {
282           DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
283           return RESULT_FORMAT;
284         }
285
286       TimedTextResourceDescriptor TmpResource;
287       memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
288       TmpResource.Type = MT_OPENTYPE;
289       m_TDesc.ResourceList.push_back(TmpResource);
290       m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
291     }
292
293   // list of images
294   ElementList ImageList;
295   m_Root.GetChildrenWithName("Image", ImageList);
296   std::set<Kumu::UUID> visited_items;
297
298   for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
299     {
300       UUID AssetID;
301       if ( ! get_UUID_from_element(*i, AssetID) )
302         {
303           DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
304           return RESULT_FORMAT;
305         }
306
307       if ( visited_items.find(AssetID) == visited_items.end() )
308         {
309           TimedTextResourceDescriptor TmpResource;
310           memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
311           TmpResource.Type = MT_PNG;
312           m_TDesc.ResourceList.push_back(TmpResource);
313           m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
314           visited_items.insert(AssetID);
315         }
316     }
317
318   // Calculate the timeline duration.
319   // This is a little ugly because the last element in the file is not necessarily
320   // the last instance to be displayed, e.g., element n and element n-1 may have the
321   // same start time but n-1 may have a greater duration making it the last to be seen.
322   // We must scan the list to accumulate the latest TimeOut value.
323   ElementList InstanceList;
324   ElementList::const_iterator ei;
325   ui32_t end_count = 0;
326   
327   m_Root.GetChildrenWithName("Subtitle", InstanceList);
328
329   if ( InstanceList.empty() )
330     {
331       DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
332       return RESULT_FORMAT;
333     }
334
335   // assumes edit rate is constrained above
336   ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98  ) ? 24 : m_TDesc.EditRate.Numerator;
337
338   S12MTimecode beginTC;
339   beginTC.SetFPS(TCFrameRate);
340   XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
341
342   if ( StartTime != 0 )
343     beginTC.DecodeString(StartTime->GetBody());
344
345   for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
346     {
347       S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
348       if ( end_count < tmpTC.GetFrames() )
349         end_count = tmpTC.GetFrames();
350     }
351
352   if ( end_count <= beginTC.GetFrames() )
353     {
354       DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
355       return RESULT_FORMAT;
356     }
357
358   m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
359
360   return RESULT_OK;
361 }
362
363
364 //
365 Result_t
366 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
367                                                                              const IResourceResolver& Resolver) const
368 {
369   FrameBuf.AssetID(uuid);
370   UUID TmpID(uuid);
371   char buf[64];
372
373   ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
374
375   if ( rmi == m_ResourceTypes.end() )
376     {
377       DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
378       return RESULT_RANGE;
379     }
380
381   Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
382
383   if ( KM_SUCCESS(result) )
384     {
385       if ( (*rmi).second == MT_PNG )
386         FrameBuf.MIMEType("image/png");
387               
388       else if ( (*rmi).second == MT_OPENTYPE )
389         FrameBuf.MIMEType("application/x-font-opentype");
390
391       else
392         FrameBuf.MIMEType("application/octet-stream");
393     }
394
395   return result;
396 }
397
398 //------------------------------------------------------------------------------------------
399
400 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
401 {
402 }
403
404 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
405 {
406 }
407
408 // Opens the stream for reading, parses enough data to provide a complete
409 // set of stream metadata for the MXFWriter below.
410 ASDCP::Result_t
411 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
412 {
413   const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
414
415   Result_t result = m_Parser->OpenRead(filename);
416
417   if ( ASDCP_FAILURE(result) )
418     const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
419
420   return result;
421 }
422
423 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
424 Result_t
425 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
426 {
427   const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
428
429   Result_t result = m_Parser->OpenRead(xml_doc, filename);
430
431   if ( ASDCP_FAILURE(result) )
432     const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
433
434   return result;
435 }
436
437 //
438 ASDCP::Result_t
439 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
440 {
441   if ( m_Parser.empty() )
442     return RESULT_INIT;
443
444   TDesc = m_Parser->m_TDesc;
445   return RESULT_OK;
446 }
447
448 // Reads the complete Timed Text Resource into the given string.
449 ASDCP::Result_t
450 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
451 {
452   if ( m_Parser.empty() )
453     return RESULT_INIT;
454
455   s = m_Parser->m_XMLDoc;
456   return RESULT_OK;
457 }
458
459 //
460 ASDCP::Result_t
461 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
462                                                           const IResourceResolver* Resolver) const
463 {
464   if ( m_Parser.empty() )
465     return RESULT_INIT;
466
467   if ( Resolver == 0 )
468     Resolver = m_Parser->GetDefaultResolver();
469
470   return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
471 }
472
473
474 //
475 // end AS_DCP_TimedTextParser.cpp
476 //