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);
170 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& filename)
172 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
174 if ( KM_SUCCESS(result) )
177 m_Filename = filename;
183 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename)
187 if ( filename.empty() )
189 m_Filename = "<string>";
193 m_Filename = filename;
201 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
203 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
204 return RESULT_FORMAT;
206 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
207 m_TDesc.ResourceList.clear();
208 m_TDesc.ContainerDuration = 0;
209 const XMLNamespace* ns = m_Root.Namespace();
213 DefaultLogSink(). Warn("Document has no namespace name, assuming \"%s\".\n", c_dcst_namespace_name);
214 m_TDesc.NamespaceName = c_dcst_namespace_name;
218 m_TDesc.NamespaceName = ns->Name();
222 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
224 DefaultLogSink(). Error("Id element missing from input document.\n");
225 return RESULT_FORMAT;
228 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
229 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
233 DefaultLogSink().Error("EditRate element missing from input document.\n");
234 return RESULT_FORMAT;
237 if ( ! DecodeRational(EditRate->GetBody().c_str(), m_TDesc.EditRate) )
239 DefaultLogSink().Error("Error decoding edit rate value: \"%s\"\n", EditRate->GetBody().c_str());
240 return RESULT_FORMAT;
243 if ( m_TDesc.EditRate != EditRate_23_98
244 && m_TDesc.EditRate != EditRate_24
245 && m_TDesc.EditRate != EditRate_25
246 && m_TDesc.EditRate != EditRate_30
247 && m_TDesc.EditRate != EditRate_48
248 && m_TDesc.EditRate != EditRate_50
249 && m_TDesc.EditRate != EditRate_60 )
251 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
252 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
253 return RESULT_FORMAT;
257 ElementList FontList;
258 m_Root.GetChildrenWithName("LoadFont", FontList);
260 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
263 if ( ! get_UUID_from_element(*i, AssetID) )
265 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
266 return RESULT_FORMAT;
269 TimedTextResourceDescriptor TmpResource;
270 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
271 TmpResource.Type = MT_OPENTYPE;
272 m_TDesc.ResourceList.push_back(TmpResource);
273 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
277 ElementList ImageList;
278 m_Root.GetChildrenWithName("Image", ImageList);
279 std::set<Kumu::UUID> visited_items;
281 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
284 if ( ! get_UUID_from_element(*i, AssetID) )
286 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
287 return RESULT_FORMAT;
290 if ( visited_items.find(AssetID) == visited_items.end() )
292 TimedTextResourceDescriptor TmpResource;
293 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
294 TmpResource.Type = MT_PNG;
295 m_TDesc.ResourceList.push_back(TmpResource);
296 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
297 visited_items.insert(AssetID);
301 // Calculate the timeline duration.
302 // This is a little ugly because the last element in the file is not necessarily
303 // the last instance to be displayed, e.g., element n and element n-1 may have the
304 // same start time but n-1 may have a greater duration making it the last to be seen.
305 // We must scan the list to accumulate the latest TimeOut value.
306 ElementList InstanceList;
307 ElementList::const_iterator ei;
308 ui32_t end_count = 0;
310 m_Root.GetChildrenWithName("Subtitle", InstanceList);
312 if ( InstanceList.empty() )
314 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
315 return RESULT_FORMAT;
318 // assumes edit rate is constrained above
319 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
321 S12MTimecode beginTC;
322 beginTC.SetFPS(TCFrameRate);
323 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
325 if ( StartTime != 0 )
326 beginTC.DecodeString(StartTime->GetBody());
328 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
330 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
331 if ( end_count < tmpTC.GetFrames() )
332 end_count = tmpTC.GetFrames();
335 if ( end_count <= beginTC.GetFrames() )
337 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
338 return RESULT_FORMAT;
341 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
349 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
350 const IResourceResolver& Resolver) const
352 FrameBuf.AssetID(uuid);
356 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
358 if ( rmi == m_ResourceTypes.end() )
360 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
364 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
366 if ( KM_SUCCESS(result) )
368 if ( (*rmi).second == MT_PNG )
369 FrameBuf.MIMEType("image/png");
371 else if ( (*rmi).second == MT_OPENTYPE )
372 FrameBuf.MIMEType("application/x-font-opentype");
375 FrameBuf.MIMEType("application/octet-stream");
381 //------------------------------------------------------------------------------------------
383 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
387 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
391 // Opens the stream for reading, parses enough data to provide a complete
392 // set of stream metadata for the MXFWriter below.
394 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
396 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
398 Result_t result = m_Parser->OpenRead(filename);
400 if ( ASDCP_FAILURE(result) )
401 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
406 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
408 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
410 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
412 Result_t result = m_Parser->OpenRead(xml_doc, filename);
414 if ( ASDCP_FAILURE(result) )
415 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
422 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
424 if ( m_Parser.empty() )
427 TDesc = m_Parser->m_TDesc;
431 // Reads the complete Timed Text Resource into the given string.
433 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
435 if ( m_Parser.empty() )
438 s = m_Parser->m_XMLDoc;
444 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
445 const IResourceResolver* Resolver) const
447 if ( m_Parser.empty() )
451 Resolver = m_Parser->GetDefaultResolver();
453 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
458 // end AS_DCP_TimedTextParser.cpp