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"
37 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 frame wrapping of D-Cinema Timed Text data";
38 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
41 //------------------------------------------------------------------------------------------
44 MIME2str(TimedText::MIMEType_t m)
46 if ( m == TimedText::MT_PNG )
49 else if ( m == TimedText::MT_OPENTYPE )
50 return "application/x-opentype";
52 return "application/octet-stream";
57 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
62 UUID TmpID(TDesc.AssetID);
65 fprintf(stream, " EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
66 fprintf(stream, "ContainerDuration: %u\n", TDesc.ContainerDuration);
67 fprintf(stream, " AssetID: %s\n", TmpID.EncodeHex(buf, 64));
68 fprintf(stream, " NamespaceName: %s\n", TDesc.NamespaceName.c_str());
69 fprintf(stream, " ResourceCount: %lu\n", TDesc.ResourceList.size());
71 TimedText::ResourceList_t::const_iterator ri;
72 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
74 TmpID.Set((*ri).ResourceID);
75 fprintf(stream, " %s: %s\n",
76 TmpID.EncodeHex(buf, 64),
77 MIME2str((*ri).Type));
83 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
88 UUID TmpID(m_AssetID);
90 fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
93 Kumu::hexdump(m_Data, dump_len, stream);
96 //------------------------------------------------------------------------------------------
98 typedef std::map<UUID, UUID> ResourceMap_t;
100 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
102 TimedTextDescriptor* m_EssenceDescriptor;
103 ResourceMap_t m_ResourceMap;
105 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
108 TimedTextDescriptor m_TDesc;
110 h__Reader() : m_EssenceDescriptor(0) {
111 memset(&m_TDesc.AssetID, 0, UUIDlen);
114 Result_t OpenRead(const char*);
115 Result_t MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
116 Result_t ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
117 Result_t ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
122 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
124 assert(m_EssenceDescriptor);
125 memset(&m_TDesc.AssetID, 0, UUIDlen);
126 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
128 TDesc.EditRate = TDescObj->SampleRate;
129 TDesc.ContainerDuration = TDescObj->ContainerDuration;
130 memcpy(TDesc.AssetID, TDescObj->AssetID.Value(), UUIDlen);
131 TDesc.NamespaceName = TDescObj->RootNamespaceName;
132 TDesc.EncodingName = TDescObj->UTFEncoding;
134 Batch<UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
135 DCTimedTextResourceDescriptor* DescObject = 0;
136 Result_t result = RESULT_OK;
138 for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
140 result = m_HeaderPart.GetMDObjectByID(*sdi, (InterchangeObject**)&DescObject);
142 if ( KM_SUCCESS(result) )
144 TimedTextResourceDescriptor TmpResource;
145 memcpy(TmpResource.ResourceID, DescObject->ResourceID.Value(), UUIDlen);
147 if ( DescObject->ResourceMIMEType.find("font/") != std::string::npos )
148 TmpResource.Type = MT_OPENTYPE;
150 else if ( DescObject->ResourceMIMEType.find("image/png") != std::string::npos )
151 TmpResource.Type = MT_PNG;
154 TmpResource.Type = MT_BIN;
156 TDesc.ResourceList.push_back(TmpResource);
157 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->ResourceID, *sdi));
161 DefaultLogSink().Error("Broken sub-descriptor link\n");
162 return RESULT_FORMAT;
171 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
173 Result_t result = OpenMXFRead(filename);
175 if( ASDCP_SUCCESS(result) )
177 if ( m_EssenceDescriptor == 0 )
178 m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor), (InterchangeObject**)&m_EssenceDescriptor);
180 result = MD_to_TimedText_TDesc(m_TDesc);
183 if( ASDCP_SUCCESS(result) )
184 result = InitMXFIndex();
186 if( ASDCP_SUCCESS(result) )
194 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
195 AESDecContext* Ctx, HMACContext* HMAC)
197 if ( ! m_File.IsOpen() )
200 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_DCTimedTextEssence), Ctx, HMAC);
202 if( ASDCP_SUCCESS(result) )
204 FrameBuf.AssetID(m_TDesc.AssetID);
205 FrameBuf.MIMEType("text/xml");
213 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
214 AESDecContext* Ctx, HMACContext* HMAC)
216 KM_TEST_NULL_L(uuid);
219 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
220 if ( ri == m_ResourceMap.end() )
223 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
227 DCTimedTextResourceDescriptor* DescObject = 0;
228 // get the subdescriptor
229 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, (InterchangeObject**)&DescObject);
231 if ( KM_SUCCESS(result) )
233 Array<RIP::Pair>::const_iterator pi;
237 // Look up the partition start in the RIP using the SID.
238 // Count the sequence length in because this is the sequence
239 // value needed to complete the HMAC.
240 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
242 if ( (*pi).BodySID == DescObject->ResourceSID )
249 if ( TmpPair.ByteOffset == 0 )
251 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->ResourceSID);
252 return RESULT_FORMAT;
255 if ( KM_SUCCESS(result) )
257 FrameBuf.AssetID(uuid);
258 FrameBuf.MIMEType(DescObject->ResourceMIMEType);
260 // seek tp the start of the partition
261 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
263 m_LastPosition = TmpPair.ByteOffset;
264 result = m_File.Seek(TmpPair.ByteOffset);
267 // read the partition header
268 MXF::Partition GSPart;
269 result = GSPart.InitFromFile(m_File);
271 if( ASDCP_SUCCESS(result) )
274 if ( DescObject->ResourceSID != GSPart.BodySID )
277 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
278 return RESULT_FORMAT;
281 // read the essence packet
282 if( ASDCP_SUCCESS(result) )
283 result = ReadEKLVPacket(0, FrameBuf, Dict::ul(MDD_DCTimedTextDescriptor), Ctx, HMAC);
292 //------------------------------------------------------------------------------------------
294 ASDCP::TimedText::MXFReader::MXFReader()
296 m_Reader = new h__Reader;
300 ASDCP::TimedText::MXFReader::~MXFReader()
304 // Open the file for reading. The file must exist. Returns error if the
305 // operation cannot be completed.
307 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
309 return m_Reader->OpenRead(filename);
312 // Fill the struct with the values from the file's header.
313 // Returns RESULT_INIT if the file is not open.
315 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
317 if ( m_Reader && m_Reader->m_File.IsOpen() )
319 TDesc = m_Reader->m_TDesc;
326 // Fill the struct with the values from the file's header.
327 // Returns RESULT_INIT if the file is not open.
329 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
331 if ( m_Reader && m_Reader->m_File.IsOpen() )
333 Info = m_Reader->m_Info;
342 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
344 FrameBuffer FrameBuf(2*Kumu::Megabyte);
346 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
348 if ( ASDCP_SUCCESS(result) )
349 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
356 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
357 AESDecContext* Ctx, HMACContext* HMAC) const
359 if ( m_Reader && m_Reader->m_File.IsOpen() )
360 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
367 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
368 AESDecContext* Ctx, HMACContext* HMAC) const
370 if ( m_Reader && m_Reader->m_File.IsOpen() )
371 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
379 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
381 if ( m_Reader->m_File.IsOpen() )
382 m_Reader->m_HeaderPart.Dump(stream);
388 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
390 if ( m_Reader->m_File.IsOpen() )
391 m_Reader->m_FooterPart.Dump(stream);
394 //------------------------------------------------------------------------------------------
398 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
401 TimedTextDescriptor m_TDesc;
402 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
403 ui32_t m_ResourceSID;
405 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
407 h__Writer() : m_ResourceSID(10) {
408 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
413 Result_t OpenWrite(const char*, ui32_t HeaderSize);
414 Result_t SetSourceStream(const TimedTextDescriptor&);
415 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
416 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
418 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
423 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
425 assert(m_EssenceDescriptor);
426 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
428 TDescObj->SampleRate = TDesc.EditRate;
429 TDescObj->ContainerDuration = TDesc.ContainerDuration;
430 TDescObj->AssetID.Set(TDesc.AssetID);
431 TDescObj->RootNamespaceName = TDesc.NamespaceName;
432 TDescObj->UTFEncoding = TDesc.EncodingName;
439 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
441 if ( ! m_State.Test_BEGIN() )
444 Result_t result = m_File.OpenWrite(filename);
446 if ( ASDCP_SUCCESS(result) )
448 m_HeaderSize = HeaderSize;
449 m_EssenceDescriptor = new DCTimedTextDescriptor();
450 result = m_State.Goto_INIT();
458 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
460 if ( ! m_State.Test_INIT() )
464 ResourceList_t::const_iterator ri;
465 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
467 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
469 DCTimedTextResourceDescriptor* resourceSubdescriptor = new DCTimedTextResourceDescriptor;
470 GenRandomValue(resourceSubdescriptor->InstanceUID);
471 resourceSubdescriptor->ResourceID.Set((*ri).ResourceID);
472 resourceSubdescriptor->ResourceMIMEType = MIME2str((*ri).Type);
473 resourceSubdescriptor->ResourceSID = m_ResourceSID++;
474 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
475 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
480 if ( ASDCP_SUCCESS(result) )
483 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
484 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
486 AddEssenceDescriptor(UL(Dict::ul(MDD_DCTimedTextWrapping)));
488 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
490 if ( KM_SUCCESS(result) )
491 result = CreateBodyPart(m_TDesc.EditRate);
494 if ( ASDCP_SUCCESS(result) )
496 memcpy(m_EssenceUL, Dict::ul(MDD_DCTimedTextEssence), SMPTE_UL_LENGTH);
497 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
498 result = m_State.Goto_READY();
506 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
507 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
509 Result_t result = m_State.Goto_RUNNING();
511 if ( ASDCP_SUCCESS(result) )
513 // TODO: make sure it's XML
515 ui32_t str_size = XMLDoc.size();
516 FrameBuffer FrameBuf(str_size);
518 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
519 FrameBuf.Size(str_size);
521 IndexTableSegment::IndexEntry Entry;
522 Entry.StreamOffset = m_StreamOffset;
524 if ( ASDCP_SUCCESS(result) )
525 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
527 if ( ASDCP_SUCCESS(result) )
529 m_FooterPart.PushIndexEntry(Entry);
540 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
541 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
543 if ( ! m_State.Test_RUNNING() )
546 Kumu::fpos_t here = m_File.Tell();
548 // create generic stream partition header
549 MXF::Partition GSPart;
551 GSPart.ThisPartition = here;
552 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
553 GSPart.BodySID = m_ResourceSID;
554 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
556 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_ResourceSID++, here));
557 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_DCTimedTextEssence)));
558 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
559 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
561 if ( ASDCP_SUCCESS(result) )
562 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
570 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
572 if ( ! m_State.Test_RUNNING() )
575 m_FramesWritten = m_TDesc.ContainerDuration;
576 m_State.Goto_FINAL();
578 return WriteMXFFooter();
582 //------------------------------------------------------------------------------------------
584 ASDCP::TimedText::MXFWriter::MXFWriter()
588 ASDCP::TimedText::MXFWriter::~MXFWriter()
593 // Open the file for writing. The file must not exist. Returns error if
594 // the operation cannot be completed.
596 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
597 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
599 if ( Info.LabelSetType != LS_MXF_SMPTE )
601 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
602 return RESULT_FORMAT;
605 m_Writer = new h__Writer;
607 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
609 if ( ASDCP_SUCCESS(result) )
611 m_Writer->m_Info = Info;
612 result = m_Writer->SetSourceStream(TDesc);
615 if ( ASDCP_FAILURE(result) )
623 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
625 if ( m_Writer.empty() )
628 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
633 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
635 if ( m_Writer.empty() )
638 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
641 // Closes the MXF file, writing the index and other closing information.
643 ASDCP::TimedText::MXFWriter::Finalize()
645 if ( m_Writer.empty() )
648 return m_Writer->Finalize();
654 // end AS_DCP_timedText.cpp