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 //------------------------------------------------------------------------------------------
48 MIME2str(TimedText::MIMEType_t m)
50 if ( m == TimedText::MT_PNG )
53 else if ( m == TimedText::MT_OPENTYPE )
54 return "application/x-font-opentype";
56 return "application/octet-stream";
59 //------------------------------------------------------------------------------------------
61 typedef std::map<Kumu::UUID, Kumu::UUID> ResourceMap_t;
63 class AS_02::TimedText::MXFReader::h__Reader : public AS_02::h__AS02Reader
65 ASDCP::MXF::TimedTextDescriptor* m_EssenceDescriptor;
66 ResourceMap_t m_ResourceMap;
68 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
71 TimedTextDescriptor m_TDesc;
73 h__Reader(const Dictionary& d) : AS_02::h__AS02Reader(d), m_EssenceDescriptor(0) {
74 memset(&m_TDesc.AssetID, 0, UUIDlen);
77 virtual ~h__Reader() {}
79 Result_t OpenRead(const std::string&);
80 Result_t MD_to_TimedText_TDesc(TimedTextDescriptor& TDesc);
81 Result_t ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
82 Result_t ReadAncillaryResource(const Kumu::UUID&, ASDCP::TimedText::FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
87 AS_02::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedTextDescriptor& TDesc)
89 assert(m_EssenceDescriptor);
90 memset(&m_TDesc.AssetID, 0, UUIDlen);
91 ASDCP::MXF::TimedTextDescriptor* TDescObj = (ASDCP::MXF::TimedTextDescriptor*)m_EssenceDescriptor;
93 TDesc.EditRate = TDescObj->SampleRate;
94 assert(TDescObj->ContainerDuration <= 0xFFFFFFFFL);
95 TDesc.ContainerDuration = (ui32_t) TDescObj->ContainerDuration;
96 memcpy(TDesc.AssetID, TDescObj->ResourceID.Value(), UUIDlen);
97 TDesc.NamespaceName = TDescObj->NamespaceURI;
98 TDesc.EncodingName = TDescObj->UCSEncoding;
100 Array<Kumu::UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
101 TimedTextResourceSubDescriptor* DescObject = 0;
102 Result_t result = RESULT_OK;
104 for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
106 InterchangeObject* tmp_iobj = 0;
107 result = m_HeaderPart.GetMDObjectByID(*sdi, &tmp_iobj);
108 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
110 if ( KM_SUCCESS(result) )
112 TimedTextResourceDescriptor TmpResource;
113 memcpy(TmpResource.ResourceID, DescObject->AncillaryResourceID.Value(), UUIDlen);
115 if ( DescObject->MIMEMediaType.find("application/x-font-opentype") != std::string::npos
116 || DescObject->MIMEMediaType.find("application/x-opentype") != std::string::npos
117 || DescObject->MIMEMediaType.find("font/opentype") != std::string::npos )
119 TmpResource.Type = ASDCP::TimedText::MT_OPENTYPE;
121 else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
123 TmpResource.Type = ASDCP::TimedText::MT_PNG;
127 TmpResource.Type = ASDCP::TimedText::MT_BIN;
130 TDesc.ResourceList.push_back(TmpResource);
131 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
135 DefaultLogSink().Error("Broken sub-descriptor link\n");
136 return RESULT_FORMAT;
145 AS_02::TimedText::MXFReader::h__Reader::OpenRead(const std::string& filename)
147 Result_t result = OpenMXFRead(filename.c_str());
149 if( ASDCP_SUCCESS(result) )
151 if ( m_EssenceDescriptor == 0 )
153 InterchangeObject* tmp_iobj = 0;
154 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
155 m_EssenceDescriptor = static_cast<ASDCP::MXF::TimedTextDescriptor*>(tmp_iobj);
158 if( ASDCP_SUCCESS(result) )
159 result = MD_to_TimedText_TDesc(m_TDesc);
167 AS_02::TimedText::MXFReader::h__Reader::ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf,
168 AESDecContext* Ctx, HMACContext* HMAC)
170 if ( ! m_File.IsOpen() )
174 Result_t result = ReadEKLVFrame(0, FrameBuf, m_Dict->ul(MDD_TimedTextEssence), Ctx, HMAC);
176 if( ASDCP_SUCCESS(result) )
178 FrameBuf.AssetID(m_TDesc.AssetID);
179 FrameBuf.MIMEType("text/xml");
187 AS_02::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const Kumu::UUID& uuid,
188 ASDCP::TimedText::FrameBuffer& FrameBuf,
189 AESDecContext* Ctx, HMACContext* HMAC)
191 ResourceMap_t::const_iterator ri = m_ResourceMap.find(uuid);
192 if ( ri == m_ResourceMap.end() )
195 DefaultLogSink().Error("No such resource: %s\n", uuid.EncodeHex(buf, 64));
199 TimedTextResourceSubDescriptor* DescObject = 0;
200 // get the subdescriptor
201 InterchangeObject* tmp_iobj = 0;
202 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
203 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
205 if ( KM_SUCCESS(result) )
207 RIP::const_pair_iterator pi;
208 RIP::PartitionPair TmpPair;
211 // Look up the partition start in the RIP using the SID.
212 // Count the sequence length in because this is the sequence
213 // value needed to complete the HMAC.
214 for ( pi = m_RIP.PairArray.begin(); pi != m_RIP.PairArray.end(); ++pi, ++sequence )
216 if ( (*pi).BodySID == DescObject->EssenceStreamID )
223 if ( TmpPair.ByteOffset == 0 )
225 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
226 return RESULT_FORMAT;
229 if ( KM_SUCCESS(result) )
231 FrameBuf.AssetID(uuid.Value());
232 FrameBuf.MIMEType(DescObject->MIMEMediaType);
234 // seek tp the start of the partition
235 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
237 m_LastPosition = TmpPair.ByteOffset;
238 result = m_File.Seek(TmpPair.ByteOffset);
241 // read the partition header
242 ASDCP::MXF::Partition GSPart(m_Dict);
243 result = GSPart.InitFromFile(m_File);
245 if( ASDCP_SUCCESS(result) )
248 if ( DescObject->EssenceStreamID != GSPart.BodySID )
251 DefaultLogSink().Error("Generic stream partition body differs: %s\n", uuid.EncodeHex(buf, 64));
252 return RESULT_FORMAT;
255 // read the essence packet
257 if( ASDCP_SUCCESS(result) )
258 result = ReadEKLVPacket(0, sequence, FrameBuf, m_Dict->ul(MDD_GenericStream_DataElement), Ctx, HMAC);
267 //------------------------------------------------------------------------------------------
269 AS_02::TimedText::MXFReader::MXFReader()
271 m_Reader = new h__Reader(DefaultSMPTEDict());
275 AS_02::TimedText::MXFReader::~MXFReader()
279 // Warning: direct manipulation of MXF structures can interfere
280 // with the normal operation of the wrapper. Caveat emptor!
282 ASDCP::MXF::OP1aHeader&
283 AS_02::TimedText::MXFReader::OP1aHeader()
285 if ( m_Reader.empty() )
287 assert(g_OP1aHeader);
288 return *g_OP1aHeader;
291 return m_Reader->m_HeaderPart;
294 // Warning: direct manipulation of MXF structures can interfere
295 // with the normal operation of the wrapper. Caveat emptor!
297 AS_02::MXF::AS02IndexReader&
298 AS_02::TimedText::MXFReader::AS02IndexReader()
300 if ( m_Reader.empty() )
302 assert(g_AS02IndexReader);
303 return *g_AS02IndexReader;
306 return m_Reader->m_IndexAccess;
309 // Warning: direct manipulation of MXF structures can interfere
310 // with the normal operation of the wrapper. Caveat emptor!
313 AS_02::TimedText::MXFReader::RIP()
315 if ( m_Reader.empty() )
321 return m_Reader->m_RIP;
324 // Open the file for reading. The file must exist. Returns error if the
325 // operation cannot be completed.
327 AS_02::TimedText::MXFReader::OpenRead(const std::string& filename) const
329 return m_Reader->OpenRead(filename);
332 // Fill the struct with the values from the file's header.
333 // Returns RESULT_INIT if the file is not open.
335 AS_02::TimedText::MXFReader::FillTimedTextDescriptor(TimedText::TimedTextDescriptor& TDesc) const
337 if ( m_Reader && m_Reader->m_File.IsOpen() )
339 TDesc = m_Reader->m_TDesc;
346 // Fill the struct with the values from the file's header.
347 // Returns RESULT_INIT if the file is not open.
349 AS_02::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
351 if ( m_Reader && m_Reader->m_File.IsOpen() )
353 Info = m_Reader->m_Info;
362 AS_02::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
364 ASDCP::TimedText::FrameBuffer FrameBuf(8*Kumu::Megabyte);
366 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
368 if ( ASDCP_SUCCESS(result) )
369 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
376 AS_02::TimedText::MXFReader::ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf,
377 AESDecContext* Ctx, HMACContext* HMAC) const
379 if ( m_Reader && m_Reader->m_File.IsOpen() )
380 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
387 AS_02::TimedText::MXFReader::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
388 AESDecContext* Ctx, HMACContext* HMAC) const
390 if ( m_Reader && m_Reader->m_File.IsOpen() )
391 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
399 AS_02::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
401 if ( m_Reader->m_File.IsOpen() )
402 m_Reader->m_HeaderPart.Dump(stream);
408 AS_02::TimedText::MXFReader::DumpIndex(FILE* stream) const
410 if ( m_Reader->m_File.IsOpen() )
411 m_Reader->m_IndexAccess.Dump(stream);
416 AS_02::TimedText::MXFReader::Close() const
418 if ( m_Reader && m_Reader->m_File.IsOpen() )
428 //------------------------------------------------------------------------------------------
432 class AS_02::TimedText::MXFWriter::h__Writer : public AS_02::h__AS02WriterClip
434 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
438 TimedTextDescriptor m_TDesc;
439 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
440 ui32_t m_EssenceStreamID;
442 h__Writer(const Dictionary& d) : AS_02::h__AS02WriterClip(d), m_EssenceStreamID(10)
444 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
447 virtual ~h__Writer() {}
449 Result_t OpenWrite(const std::string&, ui32_t HeaderSize);
450 Result_t SetSourceStream(const ASDCP::TimedText::TimedTextDescriptor&);
451 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
452 Result_t WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
454 Result_t TimedText_TDesc_to_MD(ASDCP::TimedText::TimedTextDescriptor& TDesc);
459 AS_02::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
461 assert(m_EssenceDescriptor);
462 ASDCP::MXF::TimedTextDescriptor* TDescObj = (ASDCP::MXF::TimedTextDescriptor*)m_EssenceDescriptor;
464 TDescObj->SampleRate = TDesc.EditRate;
465 TDescObj->ContainerDuration = TDesc.ContainerDuration;
466 TDescObj->ResourceID.Set(TDesc.AssetID);
467 TDescObj->NamespaceURI = TDesc.NamespaceName;
468 TDescObj->UCSEncoding = TDesc.EncodingName;
475 AS_02::TimedText::MXFWriter::h__Writer::OpenWrite(const std::string& filename, ui32_t HeaderSize)
477 if ( ! m_State.Test_BEGIN() )
479 KM_RESULT_STATE_HERE();
483 Result_t result = m_File.OpenWrite(filename.c_str());
485 if ( ASDCP_SUCCESS(result) )
487 m_HeaderSize = HeaderSize;
488 m_EssenceDescriptor = new ASDCP::MXF::TimedTextDescriptor(m_Dict);
489 result = m_State.Goto_INIT();
497 AS_02::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
499 if ( ! m_State.Test_INIT() )
501 KM_RESULT_STATE_HERE();
508 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
510 if ( KM_SUCCESS(result) )
512 ResourceList_t::const_iterator i;
513 for ( i = m_TDesc.ResourceList.begin() ; i != m_TDesc.ResourceList.end(); ++i )
515 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor(m_Dict);
516 GenRandomValue(resourceSubdescriptor->InstanceUID);
517 resourceSubdescriptor->AncillaryResourceID.Set((*i).ResourceID);
518 resourceSubdescriptor->MIMEMediaType = MIME2str((*i).Type);
519 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
520 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
521 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
523 // 72 == sizeof K, L, instanceuid, uuid + sizeof int32 + tag/len * 4
524 m_HeaderSize += ( resourceSubdescriptor->MIMEMediaType.ArchiveLength() * 2 /*ArchiveLength is broken*/ ) + 72;
528 if ( KM_SUCCESS(result) )
530 result = WriteAS02Header(TIMED_TEXT_PACKAGE_LABEL, UL(m_Dict->ul(MDD_TimedTextWrappingClip)),
531 "Data Track", UL(m_EssenceUL), UL(m_Dict->ul(MDD_TimedTextEssence)),
532 TDesc.EditRate, derive_timecode_rate_from_edit_rate(TDesc.EditRate));
535 if ( KM_SUCCESS(result) )
537 this->m_IndexWriter.SetPrimerLookup(&this->m_HeaderPart.m_Primer);
540 if ( KM_SUCCESS(result) )
542 memcpy(m_EssenceUL, m_Dict->ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
543 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
544 result = m_State.Goto_READY();
552 AS_02::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
553 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
555 Result_t result = m_State.Goto_RUNNING();
557 if ( KM_SUCCESS(result) )
559 // TODO: make sure it's XML
561 ui32_t str_size = XMLDoc.size();
562 ASDCP::TimedText::FrameBuffer FrameBuf(str_size);
564 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
565 FrameBuf.Size(str_size);
567 IndexTableSegment::IndexEntry Entry;
568 Entry.StreamOffset = m_StreamOffset;
570 if ( KM_SUCCESS(result) )
572 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
573 m_StreamOffset, FrameBuf, m_EssenceUL, Ctx, HMAC);
583 AS_02::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
584 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
586 if ( ! m_State.Test_RUNNING() )
588 KM_RESULT_STATE_HERE();
592 Kumu::fpos_t here = m_File.Tell();
595 // create generic stream partition header
596 static UL GenericStream_DataElement(m_Dict->ul(MDD_GenericStream_DataElement));
597 ASDCP::MXF::Partition GSPart(m_Dict);
599 GSPart.ThisPartition = here;
600 GSPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
601 GSPart.BodySID = m_EssenceStreamID;
602 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
604 m_RIP.PairArray.push_back(RIP::PartitionPair(m_EssenceStreamID++, here));
605 GSPart.EssenceContainers.push_back(UL(m_Dict->ul(MDD_TimedTextEssence)));
606 UL TmpUL(m_Dict->ul(MDD_GenericStreamPartition));
607 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
609 if ( KM_SUCCESS(result) )
611 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
612 m_StreamOffset, FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
621 AS_02::TimedText::MXFWriter::h__Writer::Finalize()
623 if ( ! m_State.Test_RUNNING() )
625 DefaultLogSink().Error("Cannot finalize file, the primary essence resource has not been written.\n");
629 m_IndexWriter.m_Duration = m_FramesWritten = m_TDesc.ContainerDuration;
631 Result_t result = m_State.Goto_FINAL();
633 if ( KM_SUCCESS(result) )
635 result = WriteAS02Footer();
642 //------------------------------------------------------------------------------------------
644 AS_02::TimedText::MXFWriter::MXFWriter()
648 AS_02::TimedText::MXFWriter::~MXFWriter()
652 // Warning: direct manipulation of MXF structures can interfere
653 // with the normal operation of the wrapper. Caveat emptor!
655 ASDCP::MXF::OP1aHeader&
656 AS_02::TimedText::MXFWriter::OP1aHeader()
658 if ( m_Writer.empty() )
660 assert(g_OP1aHeader);
661 return *g_OP1aHeader;
664 return m_Writer->m_HeaderPart;
667 // Warning: direct manipulation of MXF structures can interfere
668 // with the normal operation of the wrapper. Caveat emptor!
671 AS_02::TimedText::MXFWriter::RIP()
673 if ( m_Writer.empty() )
679 return m_Writer->m_RIP;
682 // Open the file for writing. The file must not exist. Returns error if
683 // the operation cannot be completed.
685 AS_02::TimedText::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info,
686 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
688 if ( Info.LabelSetType != LS_MXF_SMPTE )
690 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
691 return RESULT_FORMAT;
694 m_Writer = new h__Writer(DefaultSMPTEDict());
695 m_Writer->m_Info = Info;
697 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
699 if ( ASDCP_SUCCESS(result) )
700 result = m_Writer->SetSourceStream(TDesc);
702 if ( ASDCP_FAILURE(result) )
710 AS_02::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
712 if ( m_Writer.empty() )
715 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
720 AS_02::TimedText::MXFWriter::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
722 if ( m_Writer.empty() )
725 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
728 // Closes the MXF file, writing the index and other closing information.
730 AS_02::TimedText::MXFWriter::Finalize()
732 if ( m_Writer.empty() )
735 return m_Writer->Finalize();
741 // end AS_02_timedText.cpp