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() {}
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;
74 // TODO, fix this for win32 (needs regex)
75 FindInPath(PathMatchRegex(RID.EncodeHex(buf, 64)), m_Dirname, found_list);
78 if ( found_list.size() == 1 )
81 DefaultLogSink().Debug("retrieving resource %s from file %s\n", buf, found_list.front().c_str());
83 result = Reader.OpenRead(found_list.front().c_str());
85 if ( KM_SUCCESS(result) )
87 ui32_t read_count, read_size = Reader.Size();
88 result = FrameBuf.Capacity(read_size);
90 if ( KM_SUCCESS(result) )
91 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
93 if ( KM_SUCCESS(result) )
94 FrameBuf.Size(read_count);
97 else if ( ! found_list.empty() )
99 DefaultLogSink().Error("More than one file in %s matches %s.\n", m_Dirname.c_str(), buf);
100 result = RESULT_RAW_FORMAT;
106 //------------------------------------------------------------------------------------------
108 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
110 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
113 ResourceTypeMap_t m_ResourceTypes;
116 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
119 std::string m_Filename;
120 std::string m_XMLDoc;
121 TimedTextDescriptor m_TDesc;
122 mem_ptr<LocalFilenameResolver> m_DefaultResolver;
124 h__SubtitleParser() : m_Root("**ParserRoot**")
126 memset(&m_TDesc.AssetID, 0, UUIDlen);
129 ~h__SubtitleParser() {}
131 TimedText::IResourceResolver* GetDefaultResolver()
133 if ( m_DefaultResolver.empty() )
135 m_DefaultResolver = new LocalFilenameResolver();
136 m_DefaultResolver->OpenRead(PathDirname(m_Filename));
139 return m_DefaultResolver;
142 Result_t OpenRead(const std::string& filename);
143 Result_t OpenRead(const std::string& xml_doc, const std::string& filename);
144 Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
149 get_UUID_from_element(XMLElement* Element, UUID& ID)
152 const char* p = Element->GetBody().c_str();
153 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
154 return ID.DecodeHex(p);
159 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
161 assert(name); assert(Parent);
162 XMLElement* Child = Parent->GetChildWithName(name);
163 if ( Child == 0 ) return false;
164 return get_UUID_from_element(Child, outID);
168 static ASDCP::Rational
169 decode_rational(const char* str_rat)
172 ui32_t Num = atoi(str_rat);
175 const char* den_str = strrchr(str_rat, ' ');
177 Den = atoi(den_str+1);
179 return ASDCP::Rational(Num, Den);
184 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& filename)
186 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
188 if ( KM_SUCCESS(result) )
191 m_Filename = filename;
197 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename)
201 if ( filename.empty() )
203 m_Filename = "<string>";
207 m_Filename = filename;
215 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
217 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
218 return RESULT_FORMAT;
220 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
221 m_TDesc.ResourceList.clear();
222 m_TDesc.ContainerDuration = 0;
223 const XMLNamespace* ns = m_Root.Namespace();
227 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
228 m_TDesc.NamespaceName = c_dcst_namespace_name;
232 m_TDesc.NamespaceName = ns->Name();
236 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
238 DefaultLogSink(). Error("Id element missing from input document\n");
239 return RESULT_FORMAT;
242 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
243 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
247 DefaultLogSink(). Error("EditRate element missing from input document\n");
248 return RESULT_FORMAT;
251 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
253 if ( m_TDesc.EditRate != EditRate_23_98
254 && m_TDesc.EditRate != EditRate_24
255 && m_TDesc.EditRate != EditRate_25
256 && m_TDesc.EditRate != EditRate_30
257 && m_TDesc.EditRate != EditRate_48
258 && m_TDesc.EditRate != EditRate_50
259 && m_TDesc.EditRate != EditRate_60 )
261 DefaultLogSink(). Error("Unexpected EditRate: %d/%d\n",
262 m_TDesc.EditRate.Numerator, m_TDesc.EditRate.Denominator);
263 return RESULT_FORMAT;
267 ElementList FontList;
268 m_Root.GetChildrenWithName("LoadFont", FontList);
270 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
273 if ( ! get_UUID_from_element(*i, AssetID) )
275 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
276 return RESULT_FORMAT;
279 TimedTextResourceDescriptor TmpResource;
280 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
281 TmpResource.Type = MT_OPENTYPE;
282 m_TDesc.ResourceList.push_back(TmpResource);
283 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
287 ElementList ImageList;
288 m_Root.GetChildrenWithName("Image", ImageList);
289 std::set<Kumu::UUID> visited_items;
291 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
294 if ( ! get_UUID_from_element(*i, AssetID) )
296 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
297 return RESULT_FORMAT;
300 if ( visited_items.find(AssetID) == visited_items.end() )
302 TimedTextResourceDescriptor TmpResource;
303 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
304 TmpResource.Type = MT_PNG;
305 m_TDesc.ResourceList.push_back(TmpResource);
306 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
307 visited_items.insert(AssetID);
311 // Calculate the timeline duration.
312 // This is a little ugly because the last element in the file is not necessarily
313 // the last instance to be displayed, e.g., element n and element n-1 may have the
314 // same start time but n-1 may have a greater duration making it the last to be seen.
315 // We must scan the list to accumulate the latest TimeOut value.
316 ElementList InstanceList;
317 ElementList::const_iterator ei;
318 ui32_t end_count = 0;
320 m_Root.GetChildrenWithName("Subtitle", InstanceList);
322 if ( InstanceList.empty() )
324 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
325 return RESULT_FORMAT;
328 // assumes edit rate is constrained above
329 ui32_t TCFrameRate = ( m_TDesc.EditRate == EditRate_23_98 ) ? 24 : m_TDesc.EditRate.Numerator;
331 S12MTimecode beginTC;
332 beginTC.SetFPS(TCFrameRate);
333 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
335 if ( StartTime != 0 )
336 beginTC.DecodeString(StartTime->GetBody());
338 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
340 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), TCFrameRate);
341 if ( end_count < tmpTC.GetFrames() )
342 end_count = tmpTC.GetFrames();
345 if ( end_count <= beginTC.GetFrames() )
347 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
348 return RESULT_FORMAT;
351 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
359 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
360 const IResourceResolver& Resolver) const
362 FrameBuf.AssetID(uuid);
366 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
368 if ( rmi == m_ResourceTypes.end() )
370 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
374 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
376 if ( KM_SUCCESS(result) )
378 if ( (*rmi).second == MT_PNG )
379 FrameBuf.MIMEType("image/png");
381 else if ( (*rmi).second == MT_OPENTYPE )
382 FrameBuf.MIMEType("application/x-font-opentype");
385 FrameBuf.MIMEType("application/octet-stream");
391 //------------------------------------------------------------------------------------------
393 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
397 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
401 // Opens the stream for reading, parses enough data to provide a complete
402 // set of stream metadata for the MXFWriter below.
404 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& filename) const
406 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
408 Result_t result = m_Parser->OpenRead(filename);
410 if ( ASDCP_FAILURE(result) )
411 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
416 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
418 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const std::string& filename) const
420 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
422 Result_t result = m_Parser->OpenRead(xml_doc, filename);
424 if ( ASDCP_FAILURE(result) )
425 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
432 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
434 if ( m_Parser.empty() )
437 TDesc = m_Parser->m_TDesc;
441 // Reads the complete Timed Text Resource into the given string.
443 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
445 if ( m_Parser.empty() )
448 s = m_Parser->m_XMLDoc;
454 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
455 const IResourceResolver* Resolver) const
457 if ( m_Parser.empty() )
461 Resolver = m_Parser->GetDefaultResolver();
463 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
468 // end AS_DCP_TimedTextParser.cpp