2 Copyright (c) 2008-2015, 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
28 \version $Id: AS_02_TimedText.cpp,v 1.5 2015/10/09 23:41:11 jhurst Exp $
29 \brief AS-DCP library, PCM essence reader and writer implementation
33 #include "AS_02_internal.h"
38 using Kumu::GenRandomValue;
40 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE ST 429-5 / ST 2067-5 clip wrapping of IMF Timed Text data";
41 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
44 //------------------------------------------------------------------------------------------
47 MIME2str(TimedText::MIMEType_t m)
49 if ( m == TimedText::MT_PNG )
52 else if ( m == TimedText::MT_OPENTYPE )
53 return "application/x-font-opentype";
55 return "application/octet-stream";
58 //------------------------------------------------------------------------------------------
60 typedef std::map<Kumu::UUID, Kumu::UUID> ResourceMap_t;
62 class AS_02::TimedText::MXFReader::h__Reader : public AS_02::h__AS02Reader
64 ASDCP::MXF::TimedTextDescriptor* m_EssenceDescriptor;
65 ResourceMap_t m_ResourceMap;
67 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
70 TimedTextDescriptor m_TDesc;
72 h__Reader(const Dictionary& d) : AS_02::h__AS02Reader(d), m_EssenceDescriptor(0) {
73 memset(&m_TDesc.AssetID, 0, UUIDlen);
76 virtual ~h__Reader() {}
78 Result_t OpenRead(const std::string&);
79 Result_t MD_to_TimedText_TDesc(TimedTextDescriptor& TDesc);
80 Result_t ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
81 Result_t ReadAncillaryResource(const Kumu::UUID&, ASDCP::TimedText::FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
86 AS_02::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedTextDescriptor& TDesc)
88 assert(m_EssenceDescriptor);
89 memset(&m_TDesc.AssetID, 0, UUIDlen);
90 ASDCP::MXF::TimedTextDescriptor* TDescObj = (ASDCP::MXF::TimedTextDescriptor*)m_EssenceDescriptor;
92 TDesc.EditRate = TDescObj->SampleRate;
93 assert(TDescObj->ContainerDuration <= 0xFFFFFFFFL);
94 TDesc.ContainerDuration = (ui32_t) TDescObj->ContainerDuration;
95 memcpy(TDesc.AssetID, TDescObj->ResourceID.Value(), UUIDlen);
96 TDesc.NamespaceName = TDescObj->NamespaceURI;
97 TDesc.EncodingName = TDescObj->UCSEncoding;
99 Array<Kumu::UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
100 TimedTextResourceSubDescriptor* DescObject = 0;
101 Result_t result = RESULT_OK;
103 for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
105 InterchangeObject* tmp_iobj = 0;
106 result = m_HeaderPart.GetMDObjectByID(*sdi, &tmp_iobj);
107 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
109 if ( KM_SUCCESS(result) )
111 TimedTextResourceDescriptor TmpResource;
112 memcpy(TmpResource.ResourceID, DescObject->AncillaryResourceID.Value(), UUIDlen);
114 if ( DescObject->MIMEMediaType.find("application/x-font-opentype") != std::string::npos
115 || DescObject->MIMEMediaType.find("application/x-opentype") != std::string::npos
116 || DescObject->MIMEMediaType.find("font/opentype") != std::string::npos )
118 TmpResource.Type = ASDCP::TimedText::MT_OPENTYPE;
120 else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
122 TmpResource.Type = ASDCP::TimedText::MT_PNG;
126 TmpResource.Type = ASDCP::TimedText::MT_BIN;
129 TDesc.ResourceList.push_back(TmpResource);
130 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
134 DefaultLogSink().Error("Broken sub-descriptor link\n");
135 return RESULT_FORMAT;
144 AS_02::TimedText::MXFReader::h__Reader::OpenRead(const std::string& filename)
146 Result_t result = OpenMXFRead(filename.c_str());
148 if( ASDCP_SUCCESS(result) )
150 if ( m_EssenceDescriptor == 0 )
152 InterchangeObject* tmp_iobj = 0;
153 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
154 m_EssenceDescriptor = static_cast<ASDCP::MXF::TimedTextDescriptor*>(tmp_iobj);
157 if( ASDCP_SUCCESS(result) )
158 result = MD_to_TimedText_TDesc(m_TDesc);
166 AS_02::TimedText::MXFReader::h__Reader::ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf,
167 AESDecContext* Ctx, HMACContext* HMAC)
169 if ( ! m_File.IsOpen() )
173 Result_t result = ReadEKLVFrame(0, FrameBuf, m_Dict->ul(MDD_TimedTextEssence), Ctx, HMAC);
175 if( ASDCP_SUCCESS(result) )
177 FrameBuf.AssetID(m_TDesc.AssetID);
178 FrameBuf.MIMEType("text/xml");
186 AS_02::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const Kumu::UUID& uuid,
187 ASDCP::TimedText::FrameBuffer& FrameBuf,
188 AESDecContext* Ctx, HMACContext* HMAC)
190 ResourceMap_t::const_iterator ri = m_ResourceMap.find(uuid);
191 if ( ri == m_ResourceMap.end() )
194 DefaultLogSink().Error("No such resource: %s\n", uuid.EncodeHex(buf, 64));
198 TimedTextResourceSubDescriptor* DescObject = 0;
199 // get the subdescriptor
200 InterchangeObject* tmp_iobj = 0;
201 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
202 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
204 if ( KM_SUCCESS(result) )
206 RIP::const_pair_iterator pi;
207 RIP::PartitionPair TmpPair;
210 // Look up the partition start in the RIP using the SID.
211 // Count the sequence length in because this is the sequence
212 // value needed to complete the HMAC.
213 for ( pi = m_RIP.PairArray.begin(); pi != m_RIP.PairArray.end(); ++pi, ++sequence )
215 if ( (*pi).BodySID == DescObject->EssenceStreamID )
222 if ( TmpPair.ByteOffset == 0 )
224 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
225 return RESULT_FORMAT;
228 if ( KM_SUCCESS(result) )
230 FrameBuf.AssetID(uuid.Value());
231 FrameBuf.MIMEType(DescObject->MIMEMediaType);
233 // seek tp the start of the partition
234 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
236 m_LastPosition = TmpPair.ByteOffset;
237 result = m_File.Seek(TmpPair.ByteOffset);
240 // read the partition header
241 ASDCP::MXF::Partition GSPart(m_Dict);
242 result = GSPart.InitFromFile(m_File);
244 if( ASDCP_SUCCESS(result) )
247 if ( DescObject->EssenceStreamID != GSPart.BodySID )
250 DefaultLogSink().Error("Generic stream partition body differs: %s\n", uuid.EncodeHex(buf, 64));
251 return RESULT_FORMAT;
254 // read the essence packet
256 if( ASDCP_SUCCESS(result) )
257 result = ReadEKLVPacket(0, sequence, FrameBuf, m_Dict->ul(MDD_GenericStream_DataElement), Ctx, HMAC);
266 //------------------------------------------------------------------------------------------
268 AS_02::TimedText::MXFReader::MXFReader()
270 m_Reader = new h__Reader(DefaultSMPTEDict());
274 AS_02::TimedText::MXFReader::~MXFReader()
278 // Warning: direct manipulation of MXF structures can interfere
279 // with the normal operation of the wrapper. Caveat emptor!
281 ASDCP::MXF::OP1aHeader&
282 AS_02::TimedText::MXFReader::OP1aHeader()
284 if ( m_Reader.empty() )
286 assert(g_OP1aHeader);
287 return *g_OP1aHeader;
290 return m_Reader->m_HeaderPart;
293 // Warning: direct manipulation of MXF structures can interfere
294 // with the normal operation of the wrapper. Caveat emptor!
296 AS_02::MXF::AS02IndexReader&
297 AS_02::TimedText::MXFReader::AS02IndexReader()
299 if ( m_Reader.empty() )
301 assert(g_AS02IndexReader);
302 return *g_AS02IndexReader;
305 return m_Reader->m_IndexAccess;
308 // Warning: direct manipulation of MXF structures can interfere
309 // with the normal operation of the wrapper. Caveat emptor!
312 AS_02::TimedText::MXFReader::RIP()
314 if ( m_Reader.empty() )
320 return m_Reader->m_RIP;
323 // Open the file for reading. The file must exist. Returns error if the
324 // operation cannot be completed.
326 AS_02::TimedText::MXFReader::OpenRead(const std::string& filename) const
328 return m_Reader->OpenRead(filename);
331 // Fill the struct with the values from the file's header.
332 // Returns RESULT_INIT if the file is not open.
334 AS_02::TimedText::MXFReader::FillTimedTextDescriptor(TimedText::TimedTextDescriptor& TDesc) const
336 if ( m_Reader && m_Reader->m_File.IsOpen() )
338 TDesc = m_Reader->m_TDesc;
345 // Fill the struct with the values from the file's header.
346 // Returns RESULT_INIT if the file is not open.
348 AS_02::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
350 if ( m_Reader && m_Reader->m_File.IsOpen() )
352 Info = m_Reader->m_Info;
361 AS_02::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
363 ASDCP::TimedText::FrameBuffer FrameBuf(8*Kumu::Megabyte);
365 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
367 if ( ASDCP_SUCCESS(result) )
368 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
375 AS_02::TimedText::MXFReader::ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf,
376 AESDecContext* Ctx, HMACContext* HMAC) const
378 if ( m_Reader && m_Reader->m_File.IsOpen() )
379 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
386 AS_02::TimedText::MXFReader::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
387 AESDecContext* Ctx, HMACContext* HMAC) const
389 if ( m_Reader && m_Reader->m_File.IsOpen() )
390 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
398 AS_02::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
400 if ( m_Reader->m_File.IsOpen() )
401 m_Reader->m_HeaderPart.Dump(stream);
407 AS_02::TimedText::MXFReader::DumpIndex(FILE* stream) const
409 if ( m_Reader->m_File.IsOpen() )
410 m_Reader->m_IndexAccess.Dump(stream);
415 AS_02::TimedText::MXFReader::Close() const
417 if ( m_Reader && m_Reader->m_File.IsOpen() )
427 //------------------------------------------------------------------------------------------
431 class AS_02::TimedText::MXFWriter::h__Writer : public AS_02::h__AS02WriterClip
433 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
437 TimedTextDescriptor m_TDesc;
438 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
439 ui32_t m_EssenceStreamID;
441 h__Writer(const Dictionary& d) : AS_02::h__AS02WriterClip(d), m_EssenceStreamID(10)
443 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
446 virtual ~h__Writer() {}
448 Result_t OpenWrite(const std::string&, ui32_t HeaderSize);
449 Result_t SetSourceStream(const ASDCP::TimedText::TimedTextDescriptor&);
450 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
451 Result_t WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
453 Result_t TimedText_TDesc_to_MD(ASDCP::TimedText::TimedTextDescriptor& TDesc);
458 AS_02::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
460 assert(m_EssenceDescriptor);
461 ASDCP::MXF::TimedTextDescriptor* TDescObj = (ASDCP::MXF::TimedTextDescriptor*)m_EssenceDescriptor;
463 TDescObj->SampleRate = TDesc.EditRate;
464 TDescObj->ContainerDuration = TDesc.ContainerDuration;
465 TDescObj->ResourceID.Set(TDesc.AssetID);
466 TDescObj->NamespaceURI = TDesc.NamespaceName;
467 TDescObj->UCSEncoding = TDesc.EncodingName;
474 AS_02::TimedText::MXFWriter::h__Writer::OpenWrite(const std::string& filename, ui32_t HeaderSize)
476 if ( ! m_State.Test_BEGIN() )
478 KM_RESULT_STATE_HERE();
482 Result_t result = m_File.OpenWrite(filename.c_str());
484 if ( ASDCP_SUCCESS(result) )
486 m_HeaderSize = HeaderSize;
487 m_EssenceDescriptor = new ASDCP::MXF::TimedTextDescriptor(m_Dict);
488 result = m_State.Goto_INIT();
496 AS_02::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
498 if ( ! m_State.Test_INIT() )
500 KM_RESULT_STATE_HERE();
507 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
509 if ( KM_SUCCESS(result) )
511 ResourceList_t::const_iterator i;
512 for ( i = m_TDesc.ResourceList.begin() ; i != m_TDesc.ResourceList.end(); ++i )
514 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor(m_Dict);
515 GenRandomValue(resourceSubdescriptor->InstanceUID);
516 resourceSubdescriptor->AncillaryResourceID.Set((*i).ResourceID);
517 resourceSubdescriptor->MIMEMediaType = MIME2str((*i).Type);
518 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
519 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
520 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
522 // 72 == sizeof K, L, instanceuid, uuid + sizeof int32 + tag/len * 4
523 m_HeaderSize += ( resourceSubdescriptor->MIMEMediaType.ArchiveLength() * 2 /*ArchiveLength is broken*/ ) + 72;
527 if ( KM_SUCCESS(result) )
529 result = WriteAS02Header(TIMED_TEXT_PACKAGE_LABEL, UL(m_Dict->ul(MDD_TimedTextWrappingClip)),
530 "Data Track", UL(m_EssenceUL), UL(m_Dict->ul(MDD_TimedTextEssence)),
531 TDesc.EditRate, derive_timecode_rate_from_edit_rate(TDesc.EditRate));
534 if ( KM_SUCCESS(result) )
536 this->m_IndexWriter.SetPrimerLookup(&this->m_HeaderPart.m_Primer);
539 if ( KM_SUCCESS(result) )
541 memcpy(m_EssenceUL, m_Dict->ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
542 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
543 result = m_State.Goto_READY();
551 AS_02::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
552 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
554 Result_t result = m_State.Goto_RUNNING();
556 if ( KM_SUCCESS(result) )
558 // TODO: make sure it's XML
560 ui32_t str_size = XMLDoc.size();
561 ASDCP::TimedText::FrameBuffer FrameBuf(str_size);
563 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
564 FrameBuf.Size(str_size);
566 IndexTableSegment::IndexEntry Entry;
567 Entry.StreamOffset = m_StreamOffset;
569 if ( KM_SUCCESS(result) )
571 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
572 m_StreamOffset, FrameBuf, m_EssenceUL, Ctx, HMAC);
582 AS_02::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
583 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
585 if ( ! m_State.Test_RUNNING() )
587 KM_RESULT_STATE_HERE();
591 Kumu::fpos_t here = m_File.Tell();
594 // create generic stream partition header
595 static UL GenericStream_DataElement(m_Dict->ul(MDD_GenericStream_DataElement));
596 ASDCP::MXF::Partition GSPart(m_Dict);
598 GSPart.ThisPartition = here;
599 GSPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
600 GSPart.BodySID = m_EssenceStreamID;
601 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
603 m_RIP.PairArray.push_back(RIP::PartitionPair(m_EssenceStreamID++, here));
604 GSPart.EssenceContainers.push_back(UL(m_Dict->ul(MDD_TimedTextEssence)));
605 UL TmpUL(m_Dict->ul(MDD_GenericStreamPartition));
606 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
608 if ( KM_SUCCESS(result) )
610 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
611 m_StreamOffset, FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
620 AS_02::TimedText::MXFWriter::h__Writer::Finalize()
622 if ( ! m_State.Test_RUNNING() )
624 DefaultLogSink().Error("Cannot finalize file, the primary essence resource has not been written.\n");
628 m_IndexWriter.m_Duration = m_FramesWritten = m_TDesc.ContainerDuration;
630 Result_t result = m_State.Goto_FINAL();
632 if ( KM_SUCCESS(result) )
634 result = WriteAS02Footer();
641 //------------------------------------------------------------------------------------------
643 AS_02::TimedText::MXFWriter::MXFWriter()
647 AS_02::TimedText::MXFWriter::~MXFWriter()
651 // Warning: direct manipulation of MXF structures can interfere
652 // with the normal operation of the wrapper. Caveat emptor!
654 ASDCP::MXF::OP1aHeader&
655 AS_02::TimedText::MXFWriter::OP1aHeader()
657 if ( m_Writer.empty() )
659 assert(g_OP1aHeader);
660 return *g_OP1aHeader;
663 return m_Writer->m_HeaderPart;
666 // Warning: direct manipulation of MXF structures can interfere
667 // with the normal operation of the wrapper. Caveat emptor!
670 AS_02::TimedText::MXFWriter::RIP()
672 if ( m_Writer.empty() )
678 return m_Writer->m_RIP;
681 // Open the file for writing. The file must not exist. Returns error if
682 // the operation cannot be completed.
684 AS_02::TimedText::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info,
685 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
687 if ( Info.LabelSetType != LS_MXF_SMPTE )
689 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
690 return RESULT_FORMAT;
693 m_Writer = new h__Writer(DefaultSMPTEDict());
694 m_Writer->m_Info = Info;
696 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
698 if ( ASDCP_SUCCESS(result) )
699 result = m_Writer->SetSourceStream(TDesc);
701 if ( ASDCP_FAILURE(result) )
709 AS_02::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
711 if ( m_Writer.empty() )
714 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
719 AS_02::TimedText::MXFWriter::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
721 if ( m_Writer.empty() )
724 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
727 // Closes the MXF file, writing the index and other closing information.
729 AS_02::TimedText::MXFWriter::Finalize()
731 if ( m_Writer.empty() )
734 return m_Writer->Finalize();
740 // end AS_02_timedText.cpp