2 Copyright (c) 2007-2013, 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
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 //------------------------------------------------------------------------------------------
47 ASDCP::TimedText::LocalFilenameResolver::LocalFilenameResolver() {}
51 ASDCP::TimedText::LocalFilenameResolver::OpenRead(const std::string& dirname)
53 if ( PathIsDirectory(dirname) )
59 DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
66 ASDCP::TimedText::LocalFilenameResolver::ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
68 Result_t result = RESULT_NOT_FOUND;
71 PathList_t found_list;
73 FindInPath(PathMatchRegex(RID.EncodeHex(buf, 64)), m_Dirname, found_list);
75 if ( found_list.size() == 1 )
78 DefaultLogSink().Debug("retrieving resource %s from file %s\n", buf, found_list.front().c_str());
80 result = Reader.OpenRead(found_list.front().c_str());
82 if ( KM_SUCCESS(result) )
84 ui32_t read_count, read_size = Reader.Size();
85 result = FrameBuf.Capacity(read_size);
87 if ( KM_SUCCESS(result) )
88 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
90 if ( KM_SUCCESS(result) )
91 FrameBuf.Size(read_count);
94 else if ( ! found_list.empty() )
96 DefaultLogSink().Error("More than one file in %s matches %s.\n", m_Dirname.c_str(), buf);
97 result = RESULT_RAW_FORMAT;
103 //------------------------------------------------------------------------------------------
105 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
107 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
110 ResourceTypeMap_t m_ResourceTypes;
113 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
116 std::string m_Filename;
117 std::string m_XMLDoc;
118 TimedTextDescriptor m_TDesc;
119 mem_ptr<LocalFilenameResolver> m_DefaultResolver;
121 h__SubtitleParser() : m_Root("**ParserRoot**")
123 memset(&m_TDesc.AssetID, 0, UUIDlen);
126 ~h__SubtitleParser() {}
128 TimedText::IResourceResolver* GetDefaultResolver()
130 if ( m_DefaultResolver.empty() )
132 m_DefaultResolver = new LocalFilenameResolver();
133 m_DefaultResolver->OpenRead(PathDirname(m_Filename));
136 return m_DefaultResolver;
139 Result_t OpenRead(const char* filename);
140 Result_t OpenRead(const std::string& xml_doc, const char* filename);
141 Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
146 get_UUID_from_element(XMLElement* Element, UUID& ID)
149 const char* p = Element->GetBody().c_str();
150 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
151 return ID.DecodeHex(p);
156 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
158 assert(name); assert(Parent);
159 XMLElement* Child = Parent->GetChildWithName(name);
160 if ( Child == 0 ) return false;
161 return get_UUID_from_element(Child, outID);
165 static ASDCP::Rational
166 decode_rational(const char* str_rat)
169 ui32_t Num = atoi(str_rat);
172 const char* den_str = strrchr(str_rat, ' ');
174 Den = atoi(den_str+1);
176 return ASDCP::Rational(Num, Den);
181 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const char* filename)
183 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
185 if ( KM_SUCCESS(result) )
188 m_Filename = filename;
194 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const char* filename)
199 m_Filename = filename;
201 m_Filename = "<string>";
208 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
210 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
211 return RESULT_FORMAT;
213 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
214 m_TDesc.ResourceList.clear();
215 m_TDesc.ContainerDuration = 0;
216 const XMLNamespace* ns = m_Root.Namespace();
220 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
221 m_TDesc.NamespaceName = c_dcst_namespace_name;
225 m_TDesc.NamespaceName = ns->Name();
229 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
231 DefaultLogSink(). Error("Id element missing from input document\n");
232 return RESULT_FORMAT;
235 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
236 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
240 DefaultLogSink(). Error("EditRate element missing from input document\n");
241 return RESULT_FORMAT;
244 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
246 if ( m_TDesc.EditRate != EditRate_23_98
247 && m_TDesc.EditRate != EditRate_24
248 && m_TDesc.EditRate != EditRate_25
249 && m_TDesc.EditRate != EditRate_30
250 && m_TDesc.EditRate != EditRate_48
251 && m_TDesc.EditRate != EditRate_50
252 && m_TDesc.EditRate != EditRate_60 )
254 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
255 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
256 return RESULT_FORMAT;
260 ElementList FontList;
261 m_Root.GetChildrenWithName("LoadFont", FontList);
263 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
266 if ( ! get_UUID_from_element(*i, AssetID) )
268 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
269 return RESULT_FORMAT;
272 TimedTextResourceDescriptor TmpResource;
273 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
274 TmpResource.Type = MT_OPENTYPE;
275 m_TDesc.ResourceList.push_back(TmpResource);
276 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
280 ElementList ImageList;
281 m_Root.GetChildrenWithName("Image", ImageList);
282 std::set<Kumu::UUID> visited_items;
284 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
287 if ( ! get_UUID_from_element(*i, AssetID) )
289 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
290 return RESULT_FORMAT;
293 if ( visited_items.find(AssetID) == visited_items.end() )
295 TimedTextResourceDescriptor TmpResource;
296 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
297 TmpResource.Type = MT_PNG;
298 m_TDesc.ResourceList.push_back(TmpResource);
299 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
300 visited_items.insert(AssetID);
304 // Calculate the timeline duration.
305 // This is a little ugly because the last element in the file is not necessarily
306 // the last instance to be displayed, e.g., element n and element n-1 may have the
307 // same start time but n-1 may have a greater duration making it the last to be seen.
308 // We must scan the list to accumulate the latest TimeOut value.
309 ElementList InstanceList;
310 ElementList::const_iterator ei;
311 ui32_t end_count = 0;
313 m_Root.GetChildrenWithName("Subtitle", InstanceList);
315 if ( InstanceList.empty() )
317 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
318 return RESULT_FORMAT;
321 // assumes edit rate is constrained above
322 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
324 S12MTimecode beginTC;
325 beginTC.SetFPS(TCFrameRate);
326 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
328 if ( StartTime != 0 )
329 beginTC.DecodeString(StartTime->GetBody());
331 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
333 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
334 if ( end_count < tmpTC.GetFrames() )
335 end_count = tmpTC.GetFrames();
338 if ( end_count <= beginTC.GetFrames() )
340 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
341 return RESULT_FORMAT;
344 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
352 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
353 const IResourceResolver& Resolver) const
355 FrameBuf.AssetID(uuid);
359 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
361 if ( rmi == m_ResourceTypes.end() )
363 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
367 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
369 if ( KM_SUCCESS(result) )
371 if ( (*rmi).second == MT_PNG )
372 FrameBuf.MIMEType("image/png");
374 else if ( (*rmi).second == MT_OPENTYPE )
375 FrameBuf.MIMEType("application/x-font-opentype");
378 FrameBuf.MIMEType("application/octet-stream");
384 //------------------------------------------------------------------------------------------
386 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
390 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
394 // Opens the stream for reading, parses enough data to provide a complete
395 // set of stream metadata for the MXFWriter below.
397 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
399 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
401 Result_t result = m_Parser->OpenRead(filename);
403 if ( ASDCP_FAILURE(result) )
404 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
409 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
411 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const char* filename) const
413 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
415 Result_t result = m_Parser->OpenRead(xml_doc, filename);
417 if ( ASDCP_FAILURE(result) )
418 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
425 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
427 if ( m_Parser.empty() )
430 TDesc = m_Parser->m_TDesc;
434 // Reads the complete Timed Text Resource into the given string.
436 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
438 if ( m_Parser.empty() )
441 s = m_Parser->m_XMLDoc;
447 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
448 const IResourceResolver* Resolver) const
450 if ( m_Parser.empty() )
454 Resolver = m_Parser->GetDefaultResolver();
456 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
461 // end AS_DCP_TimedTextParser.cpp