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"
36 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 frame wrapping of D-Cinema Timed Text data";
37 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
40 //------------------------------------------------------------------------------------------
43 MIME2str(TimedText::MIMEType_t m)
45 if ( m == TimedText::MT_PNG )
48 else if ( m == TimedText::MT_OPENTYPE )
49 return "application/x-opentype";
51 return "application/octet-stream";
56 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
61 UUID TmpID(TDesc.AssetID);
64 fprintf(stream, " EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
65 fprintf(stream, "ContainerDuration: %u\n", TDesc.ContainerDuration);
66 fprintf(stream, " AssetID: %s\n", TmpID.EncodeHex(buf, 64));
67 fprintf(stream, " NamespaceName: %s\n", TDesc.NamespaceName.c_str());
68 fprintf(stream, " ResourceCount: %lu\n", TDesc.ResourceList.size());
70 TimedText::ResourceList_t::const_iterator ri;
71 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
73 TmpID.Set((*ri).ResourceID);
74 fprintf(stream, " %s: %s\n",
75 TmpID.EncodeHex(buf, 64),
76 MIME2str((*ri).Type));
82 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
87 UUID TmpID(m_AssetID);
89 fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
92 Kumu::hexdump(m_Data, dump_len, stream);
95 //------------------------------------------------------------------------------------------
97 typedef std::map<UUID, UUID> ResourceMap_t;
99 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
101 DCTimedTextDescriptor* m_EssenceDescriptor;
102 ResourceMap_t m_ResourceMap;
104 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
107 TimedTextDescriptor m_TDesc;
109 h__Reader() : m_EssenceDescriptor(0) {
110 memset(&m_TDesc.AssetID, 0, UUIDlen);
113 Result_t OpenRead(const char*);
114 Result_t MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
115 Result_t ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
116 Result_t ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
121 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
123 assert(m_EssenceDescriptor);
124 memset(&m_TDesc.AssetID, 0, UUIDlen);
125 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
127 TDesc.EditRate = TDescObj->SampleRate;
128 TDesc.ContainerDuration = TDescObj->ContainerDuration;
129 memcpy(TDesc.AssetID, TDescObj->ResourceID.Value(), UUIDlen);
130 TDesc.NamespaceName = TDescObj->RootNamespaceName;
131 TDesc.EncodingName = TDescObj->UTFEncoding;
133 Batch<UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
134 DCTimedTextResourceDescriptor* DescObject = 0;
135 Result_t result = RESULT_OK;
137 for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
139 InterchangeObject* tmp_iobj = 0;
140 result = m_HeaderPart.GetMDObjectByID(*sdi, &tmp_iobj);
141 DescObject = static_cast<DCTimedTextResourceDescriptor*>(tmp_iobj);
143 if ( KM_SUCCESS(result) )
145 TimedTextResourceDescriptor TmpResource;
146 memcpy(TmpResource.ResourceID, DescObject->ResourceID.Value(), UUIDlen);
148 if ( DescObject->ResourceMIMEType.find("font/") != std::string::npos )
149 TmpResource.Type = MT_OPENTYPE;
151 else if ( DescObject->ResourceMIMEType.find("image/png") != std::string::npos )
152 TmpResource.Type = MT_PNG;
155 TmpResource.Type = MT_BIN;
157 TDesc.ResourceList.push_back(TmpResource);
158 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->ResourceID, *sdi));
162 DefaultLogSink().Error("Broken sub-descriptor link\n");
163 return RESULT_FORMAT;
172 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
174 Result_t result = OpenMXFRead(filename);
176 if( ASDCP_SUCCESS(result) )
178 if ( m_EssenceDescriptor == 0 )
180 InterchangeObject* tmp_iobj = 0;
181 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor), &tmp_iobj);
182 m_EssenceDescriptor = static_cast<DCTimedTextDescriptor*>(tmp_iobj);
185 if( ASDCP_SUCCESS(result) )
186 result = MD_to_TimedText_TDesc(m_TDesc);
189 if( ASDCP_SUCCESS(result) )
190 result = InitMXFIndex();
192 if( ASDCP_SUCCESS(result) )
200 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
201 AESDecContext* Ctx, HMACContext* HMAC)
203 if ( ! m_File.IsOpen() )
206 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_DCTimedTextEssence), Ctx, HMAC);
208 if( ASDCP_SUCCESS(result) )
210 FrameBuf.AssetID(m_TDesc.AssetID);
211 FrameBuf.MIMEType("text/xml");
219 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
220 AESDecContext* Ctx, HMACContext* HMAC)
222 KM_TEST_NULL_L(uuid);
225 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
226 if ( ri == m_ResourceMap.end() )
229 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
233 DCTimedTextResourceDescriptor* DescObject = 0;
234 // get the subdescriptor
235 InterchangeObject* tmp_iobj = 0;
236 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
237 DescObject = static_cast<DCTimedTextResourceDescriptor*>(tmp_iobj);
239 if ( KM_SUCCESS(result) )
241 Array<RIP::Pair>::const_iterator pi;
245 // Look up the partition start in the RIP using the SID.
246 // Count the sequence length in because this is the sequence
247 // value needed to complete the HMAC.
248 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
250 if ( (*pi).BodySID == DescObject->ResourceSID )
257 if ( TmpPair.ByteOffset == 0 )
259 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->ResourceSID);
260 return RESULT_FORMAT;
263 if ( KM_SUCCESS(result) )
265 FrameBuf.AssetID(uuid);
266 FrameBuf.MIMEType(DescObject->ResourceMIMEType);
268 // seek tp the start of the partition
269 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
271 m_LastPosition = TmpPair.ByteOffset;
272 result = m_File.Seek(TmpPair.ByteOffset);
275 // read the partition header
276 MXF::Partition GSPart;
277 result = GSPart.InitFromFile(m_File);
279 if( ASDCP_SUCCESS(result) )
282 if ( DescObject->ResourceSID != GSPart.BodySID )
285 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
286 return RESULT_FORMAT;
289 // read the essence packet
290 if( ASDCP_SUCCESS(result) )
291 result = ReadEKLVPacket(0, 1, FrameBuf, Dict::ul(MDD_DCTimedTextDescriptor), Ctx, HMAC);
300 //------------------------------------------------------------------------------------------
302 ASDCP::TimedText::MXFReader::MXFReader()
304 m_Reader = new h__Reader;
308 ASDCP::TimedText::MXFReader::~MXFReader()
312 // Open the file for reading. The file must exist. Returns error if the
313 // operation cannot be completed.
315 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
317 return m_Reader->OpenRead(filename);
320 // Fill the struct with the values from the file's header.
321 // Returns RESULT_INIT if the file is not open.
323 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
325 if ( m_Reader && m_Reader->m_File.IsOpen() )
327 TDesc = m_Reader->m_TDesc;
334 // Fill the struct with the values from the file's header.
335 // Returns RESULT_INIT if the file is not open.
337 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
339 if ( m_Reader && m_Reader->m_File.IsOpen() )
341 Info = m_Reader->m_Info;
350 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
352 FrameBuffer FrameBuf(2*Kumu::Megabyte);
354 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
356 if ( ASDCP_SUCCESS(result) )
357 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
364 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
365 AESDecContext* Ctx, HMACContext* HMAC) const
367 if ( m_Reader && m_Reader->m_File.IsOpen() )
368 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
375 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
376 AESDecContext* Ctx, HMACContext* HMAC) const
378 if ( m_Reader && m_Reader->m_File.IsOpen() )
379 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
387 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
389 if ( m_Reader->m_File.IsOpen() )
390 m_Reader->m_HeaderPart.Dump(stream);
396 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
398 if ( m_Reader->m_File.IsOpen() )
399 m_Reader->m_FooterPart.Dump(stream);
402 //------------------------------------------------------------------------------------------
406 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
409 TimedTextDescriptor m_TDesc;
410 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
411 ui32_t m_ResourceSID;
413 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
415 h__Writer() : m_ResourceSID(10) {
416 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
421 Result_t OpenWrite(const char*, ui32_t HeaderSize);
422 Result_t SetSourceStream(const TimedTextDescriptor&);
423 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
424 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
426 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
431 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
433 assert(m_EssenceDescriptor);
434 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
436 TDescObj->SampleRate = TDesc.EditRate;
437 TDescObj->ContainerDuration = TDesc.ContainerDuration;
438 TDescObj->ResourceID.Set(TDesc.AssetID);
439 TDescObj->RootNamespaceName = TDesc.NamespaceName;
440 TDescObj->UTFEncoding = TDesc.EncodingName;
447 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
449 if ( ! m_State.Test_BEGIN() )
452 Result_t result = m_File.OpenWrite(filename);
454 if ( ASDCP_SUCCESS(result) )
456 m_HeaderSize = HeaderSize;
457 m_EssenceDescriptor = new DCTimedTextDescriptor();
458 result = m_State.Goto_INIT();
466 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
468 if ( ! m_State.Test_INIT() )
472 ResourceList_t::const_iterator ri;
473 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
475 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
477 DCTimedTextResourceDescriptor* resourceSubdescriptor = new DCTimedTextResourceDescriptor;
478 GenRandomValue(resourceSubdescriptor->InstanceUID);
479 resourceSubdescriptor->ResourceID.Set((*ri).ResourceID);
480 resourceSubdescriptor->ResourceMIMEType = MIME2str((*ri).Type);
481 resourceSubdescriptor->ResourceSID = m_ResourceSID++;
482 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
483 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
488 if ( ASDCP_SUCCESS(result) )
491 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
492 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
494 AddEssenceDescriptor(UL(Dict::ul(MDD_DCTimedTextWrapping)));
496 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
498 if ( KM_SUCCESS(result) )
499 result = CreateBodyPart(m_TDesc.EditRate);
502 if ( ASDCP_SUCCESS(result) )
504 memcpy(m_EssenceUL, Dict::ul(MDD_DCTimedTextEssence), SMPTE_UL_LENGTH);
505 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
506 result = m_State.Goto_READY();
514 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
515 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
517 Result_t result = m_State.Goto_RUNNING();
519 if ( ASDCP_SUCCESS(result) )
521 // TODO: make sure it's XML
523 ui32_t str_size = XMLDoc.size();
524 FrameBuffer FrameBuf(str_size);
526 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
527 FrameBuf.Size(str_size);
529 IndexTableSegment::IndexEntry Entry;
530 Entry.StreamOffset = m_StreamOffset;
532 if ( ASDCP_SUCCESS(result) )
533 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
535 if ( ASDCP_SUCCESS(result) )
537 m_FooterPart.PushIndexEntry(Entry);
548 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
549 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
551 if ( ! m_State.Test_RUNNING() )
554 Kumu::fpos_t here = m_File.Tell();
556 // create generic stream partition header
557 MXF::Partition GSPart;
559 GSPart.ThisPartition = here;
560 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
561 GSPart.BodySID = m_ResourceSID;
562 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
564 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_ResourceSID++, here));
565 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_DCTimedTextEssence)));
566 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
567 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
569 if ( ASDCP_SUCCESS(result) )
570 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
578 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
580 if ( ! m_State.Test_RUNNING() )
583 m_FramesWritten = m_TDesc.ContainerDuration;
584 m_State.Goto_FINAL();
586 return WriteMXFFooter();
590 //------------------------------------------------------------------------------------------
592 ASDCP::TimedText::MXFWriter::MXFWriter()
596 ASDCP::TimedText::MXFWriter::~MXFWriter()
601 // Open the file for writing. The file must not exist. Returns error if
602 // the operation cannot be completed.
604 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
605 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
607 if ( Info.LabelSetType != LS_MXF_SMPTE )
609 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
610 return RESULT_FORMAT;
613 m_Writer = new h__Writer;
615 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
617 if ( ASDCP_SUCCESS(result) )
619 m_Writer->m_Info = Info;
620 result = m_Writer->SetSourceStream(TDesc);
623 if ( ASDCP_FAILURE(result) )
631 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
633 if ( m_Writer.empty() )
636 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
641 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
643 if ( m_Writer.empty() )
646 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
649 // Closes the MXF file, writing the index and other closing information.
651 ASDCP::TimedText::MXFWriter::Finalize()
653 if ( m_Writer.empty() )
656 return m_Writer->Finalize();
662 // end AS_DCP_timedText.cpp