2 Copyright (c) 2007-2009, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
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.
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.
27 /*! \file AS_DCP_TimedText.cpp
28 \version $Id: TimedText_Parser.cpp,v 1.15 2010/11/15 17:04:13 jhurst Exp $
29 \brief AS-DCP library, PCM essence reader and writer implementation
33 #include "AS_DCP_internal.h"
34 #include "S12MTimecode.h"
38 using namespace ASDCP;
40 using Kumu::DefaultLogSink;
42 const char* c_dcst_namespace_name = "http://www.smpte-ra.org/schemas/428-7/2007/DCST";
44 //------------------------------------------------------------------------------------------
48 class FilenameResolver : public ASDCP::TimedText::IResourceResolver
50 std::string m_Dirname;
53 bool operator==(const FilenameResolver&);
56 FilenameResolver(const std::string& dirname)
58 if ( PathIsDirectory(dirname) )
64 DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
69 Result_t ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
74 std::string filename = m_Dirname + "/" + RID.EncodeHex(buf, 64);
75 DefaultLogSink().Debug("retrieving resource %s from file %s\n", buf, filename.c_str());
77 Result_t result = Reader.OpenRead(filename.c_str());
79 if ( KM_SUCCESS(result) )
81 ui32_t read_count, read_size = Reader.Size();
83 result = FrameBuf.Capacity(read_size);
85 if ( KM_SUCCESS(result) )
86 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
88 if ( KM_SUCCESS(result) )
89 FrameBuf.Size(read_count);
96 //------------------------------------------------------------------------------------------
98 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
100 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
103 ResourceTypeMap_t m_ResourceTypes;
106 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
109 std::string m_Filename;
110 std::string m_XMLDoc;
111 TimedTextDescriptor m_TDesc;
112 mem_ptr<FilenameResolver> m_DefaultResolver;
114 h__SubtitleParser() : m_Root("**ParserRoot**")
116 memset(&m_TDesc.AssetID, 0, UUIDlen);
119 ~h__SubtitleParser() {}
121 TimedText::IResourceResolver* GetDefaultResolver()
123 if ( m_DefaultResolver.empty() )
124 m_DefaultResolver = new FilenameResolver(PathDirname(m_Filename));
126 return m_DefaultResolver;
129 Result_t OpenRead(const char* filename);
130 Result_t OpenRead(const std::string& xml_doc, const char* filename);
131 Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
136 get_UUID_from_element(XMLElement* Element, UUID& ID)
139 const char* p = Element->GetBody().c_str();
140 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
141 return ID.DecodeHex(p);
146 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
148 assert(name); assert(Parent);
149 XMLElement* Child = Parent->GetChildWithName(name);
150 if ( Child == 0 ) return false;
151 return get_UUID_from_element(Child, outID);
155 static ASDCP::Rational
156 decode_rational(const char* str_rat)
159 ui32_t Num = atoi(str_rat);
162 const char* den_str = strrchr(str_rat, ' ');
164 Den = atoi(den_str+1);
166 return ASDCP::Rational(Num, Den);
171 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const char* filename)
173 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
175 if ( KM_SUCCESS(result) )
178 m_Filename = filename;
184 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const char* filename)
189 m_Filename = filename;
191 m_Filename = "<string>";
198 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
200 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
201 return RESULT_FORMAT;
203 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
204 m_TDesc.ResourceList.clear();
205 m_TDesc.ContainerDuration = 0;
206 const XMLNamespace* ns = m_Root.Namespace();
210 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
211 m_TDesc.NamespaceName = c_dcst_namespace_name;
215 m_TDesc.NamespaceName = ns->Name();
219 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
221 DefaultLogSink(). Error("Id element missing from input document\n");
222 return RESULT_FORMAT;
225 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
226 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
230 DefaultLogSink(). Error("EditRate element missing from input document\n");
231 return RESULT_FORMAT;
234 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
236 if ( m_TDesc.EditRate != EditRate_23_98
237 && m_TDesc.EditRate != EditRate_24
238 && m_TDesc.EditRate != EditRate_25
239 && m_TDesc.EditRate != EditRate_30
240 && m_TDesc.EditRate != EditRate_48
241 && m_TDesc.EditRate != EditRate_50
242 && m_TDesc.EditRate != EditRate_60 )
244 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
245 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
246 return RESULT_FORMAT;
250 ElementList FontList;
251 m_Root.GetChildrenWithName("LoadFont", FontList);
253 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
256 if ( ! get_UUID_from_element(*i, AssetID) )
258 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
259 return RESULT_FORMAT;
262 TimedTextResourceDescriptor TmpResource;
263 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
264 TmpResource.Type = MT_OPENTYPE;
265 m_TDesc.ResourceList.push_back(TmpResource);
266 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
270 ElementList ImageList;
271 m_Root.GetChildrenWithName("Image", ImageList);
273 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
276 if ( ! get_UUID_from_element(*i, AssetID) )
278 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
279 return RESULT_FORMAT;
282 TimedTextResourceDescriptor TmpResource;
283 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
284 TmpResource.Type = MT_PNG;
285 m_TDesc.ResourceList.push_back(TmpResource);
286 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
289 // Calculate the timeline duration.
290 // This is a little ugly because the last element in the file is not necessarily
291 // the last instance to be displayed, e.g., element n and element n-1 may have the
292 // same start time but n-1 may have a greater duration making it the last to be seen.
293 // We must scan the list to accumulate the latest TimeOut value.
294 ElementList InstanceList;
295 ElementList::const_iterator ei;
296 ui32_t end_count = 0;
298 m_Root.GetChildrenWithName("Subtitle", InstanceList);
300 if ( InstanceList.empty() )
302 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
303 return RESULT_FORMAT;
306 // assumes edit rate is constrained above
307 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
309 S12MTimecode beginTC;
310 beginTC.SetFPS(TCFrameRate);
311 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
313 if ( StartTime != 0 )
314 beginTC.DecodeString(StartTime->GetBody());
316 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
318 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
319 if ( end_count < tmpTC.GetFrames() )
320 end_count = tmpTC.GetFrames();
323 if ( end_count <= beginTC.GetFrames() )
325 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
326 return RESULT_FORMAT;
329 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
337 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
338 const IResourceResolver& Resolver) const
340 FrameBuf.AssetID(uuid);
344 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
346 if ( rmi == m_ResourceTypes.end() )
348 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
352 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
354 if ( KM_SUCCESS(result) )
356 if ( (*rmi).second == MT_PNG )
357 FrameBuf.MIMEType("image/png");
359 else if ( (*rmi).second == MT_OPENTYPE )
360 FrameBuf.MIMEType("application/x-font-opentype");
363 FrameBuf.MIMEType("application/octet-stream");
369 //------------------------------------------------------------------------------------------
371 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
375 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
379 // Opens the stream for reading, parses enough data to provide a complete
380 // set of stream metadata for the MXFWriter below.
382 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
384 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
386 Result_t result = m_Parser->OpenRead(filename);
388 if ( ASDCP_FAILURE(result) )
389 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
394 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
396 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const char* filename) const
398 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
400 Result_t result = m_Parser->OpenRead(xml_doc, filename);
402 if ( ASDCP_FAILURE(result) )
403 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
410 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
412 if ( m_Parser.empty() )
415 TDesc = m_Parser->m_TDesc;
419 // Reads the complete Timed Text Resource into the given string.
421 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
423 if ( m_Parser.empty() )
426 s = m_Parser->m_XMLDoc;
432 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
433 const IResourceResolver* Resolver) const
435 if ( m_Parser.empty() )
439 Resolver = m_Parser->GetDefaultResolver();
441 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
446 // end AS_DCP_timedText.cpp