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, read_size = Reader.Size();
83 result = FrameBuf.Capacity(read_size);
85 if ( KM_SUCCESS(result) )
86 result = Reader.Read(FrameBuf.Data(), read_size, &read_count);
88 if ( KM_SUCCESS(result) )
89 FrameBuf.Size(read_count);
96 //------------------------------------------------------------------------------------------
98 typedef std::map<Kumu::UUID, TimedText::MIMEType_t> ResourceTypeMap_t;
100 class ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser
103 ResourceTypeMap_t m_ResourceTypes;
106 ASDCP_NO_COPY_CONSTRUCT(h__SubtitleParser);
109 std::string m_Filename;
110 std::string m_XMLDoc;
111 TimedTextDescriptor m_TDesc;
112 mem_ptr<FilenameResolver> m_DefaultResolver;
114 h__SubtitleParser() : m_Root("**ParserRoot**")
116 memset(&m_TDesc.AssetID, 0, UUIDlen);
119 ~h__SubtitleParser() {}
121 TimedText::IResourceResolver* GetDefaultResolver()
123 if ( m_DefaultResolver.empty() )
124 m_DefaultResolver = new FilenameResolver(PathDirname(m_Filename));
126 return m_DefaultResolver;
129 Result_t OpenRead(const char* filename);
130 Result_t OpenRead(const std::string& xml_doc, const char* filename);
131 Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf, const IResourceResolver& Resolver) const;
136 get_UUID_from_element(XMLElement* Element, UUID& ID)
139 const char* p = Element->GetBody().c_str();
140 if ( strncmp(p, "urn:uuid:", 9) == 0 ) p += 9;
141 return ID.DecodeHex(p);
146 get_UUID_from_child_element(const char* name, XMLElement* Parent, UUID& outID)
148 assert(name); assert(Parent);
149 XMLElement* Child = Parent->GetChildWithName(name);
150 if ( Child == 0 ) return false;
151 return get_UUID_from_element(Child, outID);
155 static ASDCP::Rational
156 decode_rational(const char* str_rat)
159 ui32_t Num = atoi(str_rat);
162 const char* den_str = strrchr(str_rat, ' ');
164 Den = atoi(den_str+1);
166 return ASDCP::Rational(Num, Den);
171 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const char* filename)
173 Result_t result = ReadFileIntoString(filename, m_XMLDoc);
175 if ( KM_SUCCESS(result) )
178 m_Filename = filename;
184 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead(const std::string& xml_doc, const char* filename)
189 m_Filename = filename;
191 m_Filename = "<string>";
198 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::OpenRead()
200 if ( ! m_Root.ParseString(m_XMLDoc.c_str()) )
201 return RESULT_FORMAT;
203 m_TDesc.EncodingName = "UTF-8"; // the XML parser demands UTF-8
204 m_TDesc.ResourceList.clear();
205 m_TDesc.ContainerDuration = 0;
206 const XMLNamespace* ns = m_Root.Namespace();
210 DefaultLogSink(). Warn("Document has no namespace name, assuming %s\n", c_dcst_namespace_name);
211 m_TDesc.NamespaceName = c_dcst_namespace_name;
215 m_TDesc.NamespaceName = ns->Name();
219 if ( ! get_UUID_from_child_element("Id", &m_Root, DocID) )
221 DefaultLogSink(). Error("Id element missing from input document\n");
222 return RESULT_FORMAT;
225 memcpy(m_TDesc.AssetID, DocID.Value(), DocID.Size());
226 XMLElement* EditRate = m_Root.GetChildWithName("EditRate");
230 DefaultLogSink(). Error("EditRate element missing from input document\n");
231 return RESULT_FORMAT;
234 m_TDesc.EditRate = decode_rational(EditRate->GetBody().c_str());
236 if ( m_TDesc.EditRate != EditRate_24 && m_TDesc.EditRate != EditRate_48 )
238 DefaultLogSink(). Error("EditRate must be 24/1 or 48/1\n");
239 return RESULT_FORMAT;
243 ElementList FontList;
244 m_Root.GetChildrenWithName("LoadFont", FontList);
246 for ( Elem_i i = FontList.begin(); i != FontList.end(); i++ )
249 if ( ! get_UUID_from_element(*i, AssetID) )
251 DefaultLogSink(). Error("LoadFont element does not contain a urn:uuid value as expected.\n");
252 return RESULT_FORMAT;
255 TimedTextResourceDescriptor TmpResource;
256 memcpy(TmpResource.ResourceID, AssetID.Value(), UUIDlen);
257 TmpResource.Type = MT_OPENTYPE;
258 m_TDesc.ResourceList.push_back(TmpResource);
259 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_OPENTYPE));
263 ElementList ImageList;
264 m_Root.GetChildrenWithName("Image", ImageList);
266 for ( Elem_i i = ImageList.begin(); i != ImageList.end(); i++ )
269 if ( ! get_UUID_from_element(*i, AssetID) )
271 DefaultLogSink(). Error("Image 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_PNG;
278 m_TDesc.ResourceList.push_back(TmpResource);
279 m_ResourceTypes.insert(ResourceTypeMap_t::value_type(UUID(TmpResource.ResourceID), MT_PNG));
282 // Calculate the timeline duration.
283 // This is a little ugly because the last element in the file is not necessarily
284 // the last instance to be displayed, e.g., element n and element n-1 may have the
285 // same start time but n-1 may have a greater duration making it the last to be seen.
286 // We must scan the list to accumulate the latest TimeOut value.
287 ElementList InstanceList;
288 ElementList::const_iterator ei;
289 ui32_t end_count = 0;
291 m_Root.GetChildrenWithName("Subtitle", InstanceList);
293 if ( InstanceList.empty() )
295 DefaultLogSink(). Error("XML document contains no Subtitle elements.\n");
296 return RESULT_FORMAT;
299 // assumes 24/1 or 48/1 as constrained above
300 assert(m_TDesc.EditRate.Denominator == 1);
302 S12MTimecode beginTC;
303 beginTC.SetFPS(m_TDesc.EditRate.Numerator);
304 XMLElement* StartTime = m_Root.GetChildWithName("StartTime");
306 if ( StartTime != 0 )
307 beginTC.DecodeString(StartTime->GetBody());
309 for ( ei = InstanceList.begin(); ei != InstanceList.end(); ei++ )
311 S12MTimecode tmpTC((*ei)->GetAttrWithName("TimeOut"), m_TDesc.EditRate.Numerator);
312 if ( end_count < tmpTC.GetFrames() )
313 end_count = tmpTC.GetFrames();
316 if ( end_count <= beginTC.GetFrames() )
318 DefaultLogSink(). Error("Timed Text file has zero-length timeline.\n");
319 return RESULT_FORMAT;
322 m_TDesc.ContainerDuration = end_count - beginTC.GetFrames();
330 ASDCP::TimedText::DCSubtitleParser::h__SubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
331 const IResourceResolver& Resolver) const
333 FrameBuf.AssetID(uuid);
337 ResourceTypeMap_t::const_iterator rmi = m_ResourceTypes.find(TmpID);
339 if ( rmi == m_ResourceTypes.end() )
341 DefaultLogSink().Error("Unknown ancillary resource id: %s\n", TmpID.EncodeHex(buf, 64));
345 Result_t result = Resolver.ResolveRID(uuid, FrameBuf);
347 if ( KM_SUCCESS(result) )
349 if ( (*rmi).second == MT_PNG )
350 FrameBuf.MIMEType("image/png");
352 else if ( (*rmi).second == MT_OPENTYPE )
353 FrameBuf.MIMEType("application/x-font-opentype");
356 FrameBuf.MIMEType("application/octet-stream");
362 //------------------------------------------------------------------------------------------
364 ASDCP::TimedText::DCSubtitleParser::DCSubtitleParser()
368 ASDCP::TimedText::DCSubtitleParser::~DCSubtitleParser()
372 // Opens the stream for reading, parses enough data to provide a complete
373 // set of stream metadata for the MXFWriter below.
375 ASDCP::TimedText::DCSubtitleParser::OpenRead(const char* filename) const
377 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
379 Result_t result = m_Parser->OpenRead(filename);
381 if ( ASDCP_FAILURE(result) )
382 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
387 // Parses an XML document to provide a complete set of stream metadata for the MXFWriter below.
389 ASDCP::TimedText::DCSubtitleParser::OpenRead(const std::string& xml_doc, const char* filename) const
391 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = new h__SubtitleParser;
393 Result_t result = m_Parser->OpenRead(xml_doc, filename);
395 if ( ASDCP_FAILURE(result) )
396 const_cast<ASDCP::TimedText::DCSubtitleParser*>(this)->m_Parser = 0;
403 ASDCP::TimedText::DCSubtitleParser::FillTimedTextDescriptor(TimedTextDescriptor& TDesc) const
405 if ( m_Parser.empty() )
408 TDesc = m_Parser->m_TDesc;
412 // Reads the complete Timed Text Resource into the given string.
414 ASDCP::TimedText::DCSubtitleParser::ReadTimedTextResource(std::string& s) const
416 if ( m_Parser.empty() )
419 s = m_Parser->m_XMLDoc;
425 ASDCP::TimedText::DCSubtitleParser::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
426 const IResourceResolver* Resolver) const
428 if ( m_Parser.empty() )
432 Resolver = m_Parser->GetDefaultResolver();
434 return m_Parser->ReadAncillaryResource(uuid, FrameBuf, *Resolver);
439 // end AS_DCP_timedText.cpp