2 Copyright (c) 2007-2014, 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() {}
48 ASDCP::TimedText::LocalFilenameResolver::~LocalFilenameResolver() {}
52 ASDCP::TimedText::LocalFilenameResolver::OpenRead(const std::string& dirname)
54 if ( PathIsDirectory(dirname) )
60 DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
67 ASDCP::TimedText::LocalFilenameResolver::ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
69 Result_t result = RESULT_NOT_FOUND;
72 PathList_t found_list;
75 // TODO, fix this for win32 (needs regex)
76 FindInPath(PathMatchRegex(RID.EncodeHex(buf, 64)), m_Dirname, found_list);
79 if ( found_list.size() == 1 )
82 DefaultLogSink().Debug("Retrieving resource %s from file %s\n", buf, found_list.front().c_str());
84 result = Reader.OpenRead(found_list.front().c_str());
86 if ( KM_SUCCESS(result) )
88 ui32_t read_count, read_size = Reader.Size();
89 result = FrameBuf.Capacity(read_size);
91 if ( KM_SUCCESS(result) )
92 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
94 if ( KM_SUCCESS(result) )
95 FrameBuf.Size(read_count);
98 else if ( ! found_list.empty() )
100 DefaultLogSink().Error("More than one file in %s matches %s.\n", m_Dirname.c_str(), buf);
101 result = RESULT_RAW_FORMAT;
107 //------------------------------------------------------------------------------------------
109 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
111 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
114 ResourceTypeMap_t m_ResourceTypes;
117 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
120 std::string m_Filename;
121 std::string m_XMLDoc;
122 TimedTextDescriptor m_TDesc;
123 mem_ptr<LocalFilenameResolver> m_DefaultResolver;
125 h__SubtitleParser() : m_Root("**ParserRoot**")
127 memset(&m_TDesc.AssetID, 0, UUIDlen);
130 ~h__SubtitleParser() {}
132 TimedText::IResourceResolver* GetDefaultResolver()
134 if ( m_DefaultResolver.empty() )
136 m_DefaultResolver = new LocalFilenameResolver();
137 m_DefaultResolver->OpenRead(PathDirname(m_Filename));
140 return m_DefaultResolver;
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;
150 get_UUID_from_element(XMLElement* Element, UUID& ID)
153 const char* p = Element->GetBody().c_str();
154 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
155 return ID.DecodeHex(p);
160 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
162 assert(name); assert(Parent);
163 XMLElement* Child = Parent->GetChildWithName(name);
164 if ( Child == 0 ) return false;
165 return get_UUID_from_element(Child, outID);
169 static ASDCP::Rational
170 decode_rational(const char* str_rat)
173 ui32_t Num = atoi(str_rat);
176 const char* den_str = strrchr(str_rat, ' ');
178 Den = atoi(den_str+1);
180 return ASDCP::Rational(Num, Den);
185 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& filename)
187 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
189 if ( KM_SUCCESS(result) )
192 m_Filename = filename;
198 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename)
202 if ( filename.empty() )
204 m_Filename = "<string>";
208 m_Filename = filename;
216 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
218 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
219 return RESULT_FORMAT;
221 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
222 m_TDesc.ResourceList.clear();
223 m_TDesc.ContainerDuration = 0;
224 const XMLNamespace* ns = m_Root.Namespace();
228 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
229 m_TDesc.NamespaceName = c_dcst_namespace_name;
233 m_TDesc.NamespaceName = ns->Name();
237 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
239 DefaultLogSink(). Error("Id element missing from input document\n");
240 return RESULT_FORMAT;
243 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
244 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
248 DefaultLogSink(). Error("EditRate element missing from input document\n");
249 return RESULT_FORMAT;
252 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
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 )
262 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
263 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
264 return RESULT_FORMAT;
268 ElementList FontList;
269 m_Root.GetChildrenWithName("LoadFont", FontList);
271 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
274 if ( ! get_UUID_from_element(*i, AssetID) )
276 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
277 return RESULT_FORMAT;
280 TimedTextResourceDescriptor TmpResource;
281 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
282 TmpResource.Type = MT_OPENTYPE;
283 m_TDesc.ResourceList.push_back(TmpResource);
284 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
288 ElementList ImageList;
289 m_Root.GetChildrenWithName("Image", ImageList);
290 std::set<Kumu::UUID> visited_items;
292 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
295 if ( ! get_UUID_from_element(*i, AssetID) )
297 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
298 return RESULT_FORMAT;
301 if ( visited_items.find(AssetID) == visited_items.end() )
303 TimedTextResourceDescriptor TmpResource;
304 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
305 TmpResource.Type = MT_PNG;
306 m_TDesc.ResourceList.push_back(TmpResource);
307 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
308 visited_items.insert(AssetID);
312 // Calculate the timeline duration.
313 // This is a little ugly because the last element in the file is not necessarily
314 // the last instance to be displayed, e.g., element n and element n-1 may have the
315 // same start time but n-1 may have a greater duration making it the last to be seen.
316 // We must scan the list to accumulate the latest TimeOut value.
317 ElementList InstanceList;
318 ElementList::const_iterator ei;
319 ui32_t end_count = 0;
321 m_Root.GetChildrenWithName("Subtitle", InstanceList);
323 if ( InstanceList.empty() )
325 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
326 return RESULT_FORMAT;
329 // assumes edit rate is constrained above
330 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
332 S12MTimecode beginTC;
333 beginTC.SetFPS(TCFrameRate);
334 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
336 if ( StartTime != 0 )
337 beginTC.DecodeString(StartTime->GetBody());
339 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
341 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
342 if ( end_count < tmpTC.GetFrames() )
343 end_count = tmpTC.GetFrames();
346 if ( end_count <= beginTC.GetFrames() )
348 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
349 return RESULT_FORMAT;
352 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
360 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
361 const IResourceResolver& Resolver) const
363 FrameBuf.AssetID(uuid);
367 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
369 if ( rmi == m_ResourceTypes.end() )
371 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
375 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
377 if ( KM_SUCCESS(result) )
379 if ( (*rmi).second == MT_PNG )
380 FrameBuf.MIMEType("image/png");
382 else if ( (*rmi).second == MT_OPENTYPE )
383 FrameBuf.MIMEType("application/x-font-opentype");
386 FrameBuf.MIMEType("application/octet-stream");
392 //------------------------------------------------------------------------------------------
394 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
398 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
402 // Opens the stream for reading, parses enough data to provide a complete
403 // set of stream metadata for the MXFWriter below.
405 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
407 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
409 Result_t result = m_Parser->OpenRead(filename);
411 if ( ASDCP_FAILURE(result) )
412 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
417 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
419 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
421 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
423 Result_t result = m_Parser->OpenRead(xml_doc, filename);
425 if ( ASDCP_FAILURE(result) )
426 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
433 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
435 if ( m_Parser.empty() )
438 TDesc = m_Parser->m_TDesc;
442 // Reads the complete Timed Text Resource into the given string.
444 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
446 if ( m_Parser.empty() )
449 s = m_Parser->m_XMLDoc;
455 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
456 const IResourceResolver* Resolver) const
458 if ( m_Parser.empty() )
462 Resolver = m_Parser->GetDefaultResolver();
464 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
469 // end AS_DCP_TimedTextParser.cpp