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 std::string& filename);
140 Result_t OpenRead(const std::string& xml_doc, const std::string& 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 std::string& 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 std::string& filename)
198 if ( filename.empty() )
200 m_Filename = "<string>";
204 m_Filename = filename;
212 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
214 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
215 return RESULT_FORMAT;
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();
224 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
225 m_TDesc.NamespaceName = c_dcst_namespace_name;
229 m_TDesc.NamespaceName = ns->Name();
233 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
235 DefaultLogSink(). Error("Id element missing from input document\n");
236 return RESULT_FORMAT;
239 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
240 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
244 DefaultLogSink(). Error("EditRate element missing from input document\n");
245 return RESULT_FORMAT;
248 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
250 if ( m_TDesc.EditRate != EditRate_23_98
251 && m_TDesc.EditRate != EditRate_24
252 && m_TDesc.EditRate != EditRate_25
253 && m_TDesc.EditRate != EditRate_30
254 && m_TDesc.EditRate != EditRate_48
255 && m_TDesc.EditRate != EditRate_50
256 && m_TDesc.EditRate != EditRate_60 )
258 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
259 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
260 return RESULT_FORMAT;
264 ElementList FontList;
265 m_Root.GetChildrenWithName("LoadFont", FontList);
267 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
270 if ( ! get_UUID_from_element(*i, AssetID) )
272 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
273 return RESULT_FORMAT;
276 TimedTextResourceDescriptor TmpResource;
277 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
278 TmpResource.Type = MT_OPENTYPE;
279 m_TDesc.ResourceList.push_back(TmpResource);
280 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
284 ElementList ImageList;
285 m_Root.GetChildrenWithName("Image", ImageList);
286 std::set<Kumu::UUID> visited_items;
288 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
291 if ( ! get_UUID_from_element(*i, AssetID) )
293 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
294 return RESULT_FORMAT;
297 if ( visited_items.find(AssetID) == visited_items.end() )
299 TimedTextResourceDescriptor TmpResource;
300 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
301 TmpResource.Type = MT_PNG;
302 m_TDesc.ResourceList.push_back(TmpResource);
303 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
304 visited_items.insert(AssetID);
308 // Calculate the timeline duration.
309 // This is a little ugly because the last element in the file is not necessarily
310 // the last instance to be displayed, e.g., element n and element n-1 may have the
311 // same start time but n-1 may have a greater duration making it the last to be seen.
312 // We must scan the list to accumulate the latest TimeOut value.
313 ElementList InstanceList;
314 ElementList::const_iterator ei;
315 ui32_t end_count = 0;
317 m_Root.GetChildrenWithName("Subtitle", InstanceList);
319 if ( InstanceList.empty() )
321 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
322 return RESULT_FORMAT;
325 // assumes edit rate is constrained above
326 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
328 S12MTimecode beginTC;
329 beginTC.SetFPS(TCFrameRate);
330 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
332 if ( StartTime != 0 )
333 beginTC.DecodeString(StartTime->GetBody());
335 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
337 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
338 if ( end_count < tmpTC.GetFrames() )
339 end_count = tmpTC.GetFrames();
342 if ( end_count <= beginTC.GetFrames() )
344 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
345 return RESULT_FORMAT;
348 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
356 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
357 const IResourceResolver& Resolver) const
359 FrameBuf.AssetID(uuid);
363 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
365 if ( rmi == m_ResourceTypes.end() )
367 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
371 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
373 if ( KM_SUCCESS(result) )
375 if ( (*rmi).second == MT_PNG )
376 FrameBuf.MIMEType("image/png");
378 else if ( (*rmi).second == MT_OPENTYPE )
379 FrameBuf.MIMEType("application/x-font-opentype");
382 FrameBuf.MIMEType("application/octet-stream");
388 //------------------------------------------------------------------------------------------
390 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
394 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
398 // Opens the stream for reading, parses enough data to provide a complete
399 // set of stream metadata for the MXFWriter below.
401 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
403 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
405 Result_t result = m_Parser->OpenRead(filename);
407 if ( ASDCP_FAILURE(result) )
408 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
413 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
415 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
417 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
419 Result_t result = m_Parser->OpenRead(xml_doc, filename);
421 if ( ASDCP_FAILURE(result) )
422 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
429 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
431 if ( m_Parser.empty() )
434 TDesc = m_Parser->m_TDesc;
438 // Reads the complete Timed Text Resource into the given string.
440 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
442 if ( m_Parser.empty() )
445 s = m_Parser->m_XMLDoc;
451 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
452 const IResourceResolver* Resolver) const
454 if ( m_Parser.empty() )
458 Resolver = m_Parser->GetDefaultResolver();
460 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
465 // end AS_DCP_TimedTextParser.cpp