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
250 && m_TDesc.EditRate != EditRate_96
251 && m_TDesc.EditRate != EditRate_100
252 && m_TDesc.EditRate != EditRate_120
253 && m_TDesc.EditRate != EditRate_192
254 && m_TDesc.EditRate != EditRate_200
255 && m_TDesc.EditRate != EditRate_240 )
257 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
258 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
259 return RESULT_FORMAT;
263 ElementList FontList;
264 m_Root.GetChildrenWithName("LoadFont", FontList);
266 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
269 if ( ! get_UUID_from_element(*i, AssetID) )
271 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
272 return RESULT_FORMAT;
275 TimedTextResourceDescriptor TmpResource;
276 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
277 TmpResource.Type = MT_OPENTYPE;
278 m_TDesc.ResourceList.push_back(TmpResource);
279 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
283 ElementList ImageList;
284 m_Root.GetChildrenWithName("Image", ImageList);
285 std::set<Kumu::UUID> visited_items;
287 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
290 if ( ! get_UUID_from_element(*i, AssetID) )
292 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
293 return RESULT_FORMAT;
296 if ( visited_items.find(AssetID) == visited_items.end() )
298 TimedTextResourceDescriptor TmpResource;
299 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
300 TmpResource.Type = MT_PNG;
301 m_TDesc.ResourceList.push_back(TmpResource);
302 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
303 visited_items.insert(AssetID);
307 // Calculate the timeline duration.
308 // This is a little ugly because the last element in the file is not necessarily
309 // the last instance to be displayed, e.g., element n and element n-1 may have the
310 // same start time but n-1 may have a greater duration making it the last to be seen.
311 // We must scan the list to accumulate the latest TimeOut value.
312 ElementList InstanceList;
313 ElementList::const_iterator ei;
314 ui32_t end_count = 0;
316 m_Root.GetChildrenWithName("Subtitle", InstanceList);
318 if ( InstanceList.empty() )
320 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
321 return RESULT_FORMAT;
324 // assumes edit rate is constrained above
325 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
327 S12MTimecode beginTC;
328 beginTC.SetFPS(TCFrameRate);
329 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
331 if ( StartTime != 0 )
332 beginTC.DecodeString(StartTime->GetBody());
334 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
336 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
337 if ( end_count < tmpTC.GetFrames() )
338 end_count = tmpTC.GetFrames();
341 if ( end_count <= beginTC.GetFrames() )
343 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
344 return RESULT_FORMAT;
347 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
355 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
356 const IResourceResolver& Resolver) const
358 FrameBuf.AssetID(uuid);
362 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
364 if ( rmi == m_ResourceTypes.end() )
366 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
370 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
372 if ( KM_SUCCESS(result) )
374 if ( (*rmi).second == MT_PNG )
375 FrameBuf.MIMEType("image/png");
377 else if ( (*rmi).second == MT_OPENTYPE )
378 FrameBuf.MIMEType("application/x-font-opentype");
381 FrameBuf.MIMEType("application/octet-stream");
387 //------------------------------------------------------------------------------------------
389 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
393 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
397 // Opens the stream for reading, parses enough data to provide a complete
398 // set of stream metadata for the MXFWriter below.
400 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
402 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
404 Result_t result = m_Parser->OpenRead(filename);
406 if ( ASDCP_FAILURE(result) )
407 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
412 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
414 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
416 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
418 Result_t result = m_Parser->OpenRead(xml_doc, filename);
420 if ( ASDCP_FAILURE(result) )
421 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
428 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
430 if ( m_Parser.empty() )
433 TDesc = m_Parser->m_TDesc;
437 // Reads the complete Timed Text Resource into the given string.
439 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
441 if ( m_Parser.empty() )
444 s = m_Parser->m_XMLDoc;
450 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
451 const IResourceResolver* Resolver) const
453 if ( m_Parser.empty() )
457 Resolver = m_Parser->GetDefaultResolver();
459 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
464 // end AS_DCP_TimedTextParser.cpp