2 Copyright (c) 2007, 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 "AS_DCP_TimedText.h"
35 #include "S12MTimecode.h"
39 using namespace ASDCP;
41 const char* c_dcst_namespace_name = "http://www.smpte-ra.org/schemas/428-7/2007/DCST";
43 //------------------------------------------------------------------------------------------
47 class FilenameResolver : public ASDCP::TimedText::IResourceResolver
49 std::string m_Dirname;
52 bool operator==(const FilenameResolver&);
55 FilenameResolver(const std::string& dirname)
57 if ( PathIsDirectory(dirname) )
63 DefaultLogSink().Error("Path '%s' is not a directory, defaulting to '.'\n", dirname.c_str());
68 Result_t ResolveRID(const byte_t* uuid, TimedText::FrameBuffer& FrameBuf) const
73 std::string filename = m_Dirname + "/" + RID.EncodeHex(buf, 64);
74 DefaultLogSink().Debug("retrieving resource %s from file %s\n", buf, filename.c_str());
76 Result_t result = Reader.OpenRead(filename.c_str());
78 if ( KM_SUCCESS(result) )
80 ui32_t read_count = 0;
81 result = Reader.Read(FrameBuf.Data(), FrameBuf.Capacity(), &read_count);
83 if ( KM_SUCCESS(result) )
84 FrameBuf.Size(read_count);
91 //------------------------------------------------------------------------------------------
93 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
95 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
98 ResourceTypeMap_t m_ResourceTypes;
100 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
103 std::string m_Filename;
104 std::string m_XMLDoc;
105 TimedTextDescriptor m_TDesc;
106 mem_ptr<FilenameResolver> m_DefaultResolver;
108 h__SubtitleParser() : m_Root("**ParserRoot**")
110 memset(&m_TDesc.AssetID, 0, UUIDlen);
113 ~h__SubtitleParser() {}
115 TimedText::IResourceResolver* GetDefaultResolver()
117 if ( m_DefaultResolver.empty() )
118 m_DefaultResolver = new FilenameResolver(PathDirname(m_Filename));
120 return m_DefaultResolver;
123 Result_t OpenRead(const char* filename);
124 Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
129 get_UUID_from_element(XMLElement* Element, UUID& ID)
132 const char* p = Element->GetBody().c_str();
133 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
134 return ID.DecodeHex(p);
139 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
141 assert(name); assert(Parent);
142 XMLElement* Child = Parent->GetChildWithName(name);
143 if ( Child == 0 ) return false;
144 return get_UUID_from_element(Child, outID);
148 static ASDCP::Rational
149 decode_rational(const char* str_rat)
152 ui32_t Num = atoi(str_rat);
155 const char* den_str = strrchr(str_rat, ' ');
157 Den = atoi(den_str+1);
159 return ASDCP::Rational(Num, Den);
164 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const char* filename)
166 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
168 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
169 return RESULT_FORMAT;
171 m_Filename = filename;
172 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
173 m_TDesc.ResourceList.clear();
174 m_TDesc.ContainerDuration = 0;
175 const XMLNamespace* ns = m_Root.Namespace();
179 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
180 m_TDesc.NamespaceName = c_dcst_namespace_name;
183 m_TDesc.NamespaceName = ns->Name();
186 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
188 DefaultLogSink(). Error("Id element missing from input document\n");
189 return RESULT_FORMAT;
192 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
193 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
197 DefaultLogSink(). Error("EditRate element missing from input document\n");
198 return RESULT_FORMAT;
201 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
203 if ( m_TDesc.EditRate != EditRate_24 && m_TDesc.EditRate != EditRate_48 )
205 DefaultLogSink(). Error("EditRate must be 24/1 or 48/1\n");
206 return RESULT_FORMAT;
210 ElementList FontList;
211 m_Root.GetChildrenWithName("LoadFont", FontList);
213 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
216 if ( ! get_UUID_from_element(*i, AssetID) )
218 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
219 return RESULT_FORMAT;
222 TimedTextResourceDescriptor TmpResource;
223 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
224 TmpResource.Type = MT_OPENTYPE;
225 m_TDesc.ResourceList.push_back(TmpResource);
226 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
230 ElementList ImageList;
231 m_Root.GetChildrenWithName("Image", ImageList);
233 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
236 if ( ! get_UUID_from_element(*i, AssetID) )
238 DefaultLogSink(). Error("Image element does not contain a urn:uuid value as expected.\n");
239 return RESULT_FORMAT;
242 TimedTextResourceDescriptor TmpResource;
243 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
244 TmpResource.Type = MT_PNG;
245 m_TDesc.ResourceList.push_back(TmpResource);
246 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
249 // Calculate the timeline duration.
250 // This is a little ugly because the last element in the file is not necessarily
251 // the last instance to be displayed, e.g., element n and element n-1 may have the
252 // same start time but n-1 may have a greater duration making it the last to be seen.
253 // We must scan the list to accumulate the latest TimeOut value.
254 ElementList InstanceList;
255 ElementList::const_iterator ei;
256 ui32_t end_count = 0;
258 m_Root.GetChildrenWithName("Subtitle", InstanceList);
260 if ( InstanceList.empty() )
262 DefaultLogSink(). Error("XML document contains no Subtitle elements!\n");
263 return RESULT_FORMAT;
266 // assumes 24/1 or 48/1 as constrained above
267 S12MTimecode beginTC(InstanceList.front()->GetAttrWithName("TimeIn"), m_TDesc.EditRate.Numerator);
269 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
271 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), m_TDesc.EditRate.Numerator);
272 if ( end_count < tmpTC.GetFrames() )
273 end_count = tmpTC.GetFrames();
276 assert( end_count > beginTC.GetFrames() );
277 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
285 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
286 const IResourceResolver& Resolver) const
288 FrameBuf.AssetID(uuid);
292 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
294 if ( rmi == m_ResourceTypes.end() )
296 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
300 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
302 if ( KM_SUCCESS(result) )
304 if ( (*rmi).second == MT_PNG )
305 FrameBuf.MIMEType("image/png");
307 else if ( (*rmi).second == MT_OPENTYPE )
308 FrameBuf.MIMEType("application/x-opentype");
311 FrameBuf.MIMEType("application/octet-stream");
317 //------------------------------------------------------------------------------------------
319 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
323 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
327 // Opens the stream for reading, parses enough data to provide a complete
328 // set of stream metadata for the MXFWriter below.
330 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
332 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
334 Result_t result = m_Parser->OpenRead(filename);
336 if ( ASDCP_FAILURE(result) )
337 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
344 ASDCP::TimedText::DCSubtitleParser::FillDescriptor(TimedTextDescriptor& TDesc) const
346 if ( m_Parser.empty() )
349 TDesc = m_Parser->m_TDesc;
353 // Reads the complete Timed Text Resource into the given string.
355 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
357 if ( m_Parser.empty() )
360 s = m_Parser->m_XMLDoc;
366 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
367 const IResourceResolver* Resolver) const
369 if ( m_Parser.empty() )
373 Resolver = m_Parser->GetDefaultResolver();
375 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
380 // end AS_DCP_timedText.cpp