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();
155 if ( strncmp(p, "urn:uuid:", 9) == 0 )
160 return ID.DecodeHex(p);
165 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
169 XMLElement* Child = Parent->GetChildWithName(name);
176 return get_UUID_from_element(Child, outID);
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) )
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 if ( ! DecodeRational(EditRate->GetBody().c_str(), m_TDesc.EditRate) )
250 DefaultLogSink().Error("Error decoding edit rate value: \"%s\"\n", EditRate->GetBody().c_str());
251 return RESULT_FORMAT;
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 )
268 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
269 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
270 return RESULT_FORMAT;
274 ElementList FontList;
275 m_Root.GetChildrenWithName("LoadFont", FontList);
277 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
280 if ( ! get_UUID_from_element(*i, AssetID) )
282 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
283 return RESULT_FORMAT;
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));
294 ElementList ImageList;
295 m_Root.GetChildrenWithName("Image", ImageList);
296 std::set<Kumu::UUID> visited_items;
298 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
301 if ( ! get_UUID_from_element(*i, AssetID) )
303 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
304 return RESULT_FORMAT;
307 if ( visited_items.find(AssetID) == visited_items.end() )
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);
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;
327 m_Root.GetChildrenWithName("Subtitle", InstanceList);
329 if ( InstanceList.empty() )
331 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
332 return RESULT_FORMAT;
335 // assumes edit rate is constrained above
336 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
338 S12MTimecode beginTC;
339 beginTC.SetFPS(TCFrameRate);
340 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
342 if ( StartTime != 0 )
343 beginTC.DecodeString(StartTime->GetBody());
345 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
347 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
348 if ( end_count < tmpTC.GetFrames() )
349 end_count = tmpTC.GetFrames();
352 if ( end_count <= beginTC.GetFrames() )
354 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
355 return RESULT_FORMAT;
358 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
366 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
367 const IResourceResolver& Resolver) const
369 FrameBuf.AssetID(uuid);
373 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
375 if ( rmi == m_ResourceTypes.end() )
377 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
381 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
383 if ( KM_SUCCESS(result) )
385 if ( (*rmi).second == MT_PNG )
386 FrameBuf.MIMEType("image/png");
388 else if ( (*rmi).second == MT_OPENTYPE )
389 FrameBuf.MIMEType("application/x-font-opentype");
392 FrameBuf.MIMEType("application/octet-stream");
398 //------------------------------------------------------------------------------------------
400 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
404 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
408 // Opens the stream for reading, parses enough data to provide a complete
409 // set of stream metadata for the MXFWriter below.
411 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
413 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
415 Result_t result = m_Parser->OpenRead(filename);
417 if ( ASDCP_FAILURE(result) )
418 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
423 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
425 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
427 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
429 Result_t result = m_Parser->OpenRead(xml_doc, filename);
431 if ( ASDCP_FAILURE(result) )
432 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
439 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
441 if ( m_Parser.empty() )
444 TDesc = m_Parser->m_TDesc;
448 // Reads the complete Timed Text Resource into the given string.
450 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
452 if ( m_Parser.empty() )
455 s = m_Parser->m_XMLDoc;
461 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
462 const IResourceResolver* Resolver) const
464 if ( m_Parser.empty() )
468 Resolver = m_Parser->GetDefaultResolver();
470 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
475 // end AS_DCP_TimedTextParser.cpp