2 Copyright (c) 2007-2009, 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 //------------------------------------------------------------------------------------------
48 class FilenameResolver : public ASDCP::TimedText::IResourceResolver
50 std::string m_Dirname;
53 bool operator==(const FilenameResolver&);
56 FilenameResolver(const std::string& dirname)
58 if ( PathIsDirectory(dirname) )
64 DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
69 Result_t ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
74 std::string filename = m_Dirname + "/" + RID.EncodeHex(buf, 64);
75 DefaultLogSink().Debug("retrieving resource %s from file %s\n", buf, filename.c_str());
77 Result_t result = Reader.OpenRead(filename.c_str());
79 if ( KM_SUCCESS(result) )
81 ui32_t read_count = 0;
82 result = Reader.Read(FrameBuf.Data(), FrameBuf.Capacity(), &read_count);
84 if ( KM_SUCCESS(result) )
85 FrameBuf.Size(read_count);
92 //------------------------------------------------------------------------------------------
94 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
96 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
99 ResourceTypeMap_t m_ResourceTypes;
102 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
105 std::string m_Filename;
106 std::string m_XMLDoc;
107 TimedTextDescriptor m_TDesc;
108 mem_ptr<FilenameResolver> m_DefaultResolver;
110 h__SubtitleParser() : m_Root("**ParserRoot**")
112 memset(&m_TDesc.AssetID, 0, UUIDlen);
115 ~h__SubtitleParser() {}
117 TimedText::IResourceResolver* GetDefaultResolver()
119 if ( m_DefaultResolver.empty() )
120 m_DefaultResolver = new FilenameResolver(PathDirname(m_Filename));
122 return m_DefaultResolver;
125 Result_t OpenRead(const char* filename);
126 Result_t OpenRead(const std::string& xml_doc, const char* filename);
127 Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
132 get_UUID_from_element(XMLElement* Element, UUID& ID)
135 const char* p = Element->GetBody().c_str();
136 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
137 return ID.DecodeHex(p);
142 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
144 assert(name); assert(Parent);
145 XMLElement* Child = Parent->GetChildWithName(name);
146 if ( Child == 0 ) return false;
147 return get_UUID_from_element(Child, outID);
151 static ASDCP::Rational
152 decode_rational(const char* str_rat)
155 ui32_t Num = atoi(str_rat);
158 const char* den_str = strrchr(str_rat, ' ');
160 Den = atoi(den_str+1);
162 return ASDCP::Rational(Num, Den);
167 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const char* filename)
169 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
171 if ( KM_SUCCESS(result) )
174 m_Filename = filename;
180 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const char* filename)
185 m_Filename = filename;
187 m_Filename = "<string>";
194 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
196 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
197 return RESULT_FORMAT;
199 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
200 m_TDesc.ResourceList.clear();
201 m_TDesc.ContainerDuration = 0;
202 const XMLNamespace* ns = m_Root.Namespace();
206 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
207 m_TDesc.NamespaceName = c_dcst_namespace_name;
211 m_TDesc.NamespaceName = ns->Name();
215 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
217 DefaultLogSink(). Error("Id element missing from input document\n");
218 return RESULT_FORMAT;
221 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
222 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
226 DefaultLogSink(). Error("EditRate element missing from input document\n");
227 return RESULT_FORMAT;
230 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
232 if ( m_TDesc.EditRate != EditRate_24 && m_TDesc.EditRate != EditRate_48 )
234 DefaultLogSink(). Error("EditRate must be 24/1 or 48/1\n");
235 return RESULT_FORMAT;
239 ElementList FontList;
240 m_Root.GetChildrenWithName("LoadFont", FontList);
242 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
245 if ( ! get_UUID_from_element(*i, AssetID) )
247 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
248 return RESULT_FORMAT;
251 TimedTextResourceDescriptor TmpResource;
252 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
253 TmpResource.Type = MT_OPENTYPE;
254 m_TDesc.ResourceList.push_back(TmpResource);
255 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
259 ElementList ImageList;
260 m_Root.GetChildrenWithName("Image", ImageList);
262 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
265 if ( ! get_UUID_from_element(*i, AssetID) )
267 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
268 return RESULT_FORMAT;
271 TimedTextResourceDescriptor TmpResource;
272 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
273 TmpResource.Type = MT_PNG;
274 m_TDesc.ResourceList.push_back(TmpResource);
275 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
278 // Calculate the timeline duration.
279 // This is a little ugly because the last element in the file is not necessarily
280 // the last instance to be displayed, e.g., element n and element n-1 may have the
281 // same start time but n-1 may have a greater duration making it the last to be seen.
282 // We must scan the list to accumulate the latest TimeOut value.
283 ElementList InstanceList;
284 ElementList::const_iterator ei;
285 ui32_t end_count = 0;
287 m_Root.GetChildrenWithName("Subtitle", InstanceList);
289 if ( InstanceList.empty() )
291 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
292 return RESULT_FORMAT;
295 // assumes 24/1 or 48/1 as constrained above
296 assert(m_TDesc.EditRate.Denominator == 1);
298 S12MTimecode beginTC;
299 beginTC.SetFPS(m_TDesc.EditRate.Numerator);
300 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
302 if ( StartTime != 0 )
303 beginTC.DecodeString(StartTime->GetBody());
305 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
307 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), m_TDesc.EditRate.Numerator);
308 if ( end_count < tmpTC.GetFrames() )
309 end_count = tmpTC.GetFrames();
312 if ( end_count <= beginTC.GetFrames() )
314 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
315 return RESULT_FORMAT;
318 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
326 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
327 const IResourceResolver& Resolver) const
329 FrameBuf.AssetID(uuid);
333 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
335 if ( rmi == m_ResourceTypes.end() )
337 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
341 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
343 if ( KM_SUCCESS(result) )
345 if ( (*rmi).second == MT_PNG )
346 FrameBuf.MIMEType("image/png");
348 else if ( (*rmi).second == MT_OPENTYPE )
349 FrameBuf.MIMEType("application/x-font-opentype");
352 FrameBuf.MIMEType("application/octet-stream");
358 //------------------------------------------------------------------------------------------
360 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
364 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
368 // Opens the stream for reading, parses enough data to provide a complete
369 // set of stream metadata for the MXFWriter below.
371 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
373 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
375 Result_t result = m_Parser->OpenRead(filename);
377 if ( ASDCP_FAILURE(result) )
378 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
383 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
385 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const char* filename) const
387 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
389 Result_t result = m_Parser->OpenRead(xml_doc, filename);
391 if ( ASDCP_FAILURE(result) )
392 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
399 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
401 if ( m_Parser.empty() )
404 TDesc = m_Parser->m_TDesc;
408 // Reads the complete Timed Text Resource into the given string.
410 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
412 if ( m_Parser.empty() )
415 s = m_Parser->m_XMLDoc;
421 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
422 const IResourceResolver* Resolver) const
424 if ( m_Parser.empty() )
428 Resolver = m_Parser->GetDefaultResolver();
430 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
435 // end AS_DCP_timedText.cpp