2 Copyright (c) 2008-2016, 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_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 ui64_t this_stream_offset = m_StreamOffset; // m_StreamOffset will be changed by the call to Write_EKLV_Packet
573 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
574 m_StreamOffset, FrameBuf, m_EssenceUL, Ctx, HMAC);
584 AS_02::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
585 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
587 if ( ! m_State.Test_RUNNING() )
589 KM_RESULT_STATE_HERE();
593 Kumu::fpos_t here = m_File.Tell();
596 // create generic stream partition header
597 static UL GenericStream_DataElement(m_Dict->ul(MDD_GenericStream_DataElement));
598 ASDCP::MXF::Partition GSPart(m_Dict);
600 GSPart.ThisPartition = here;
601 GSPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
602 GSPart.BodySID = m_EssenceStreamID;
603 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
605 m_RIP.PairArray.push_back(RIP::PartitionPair(m_EssenceStreamID++, here));
606 GSPart.EssenceContainers.push_back(UL(m_Dict->ul(MDD_TimedTextEssence)));
607 UL TmpUL(m_Dict->ul(MDD_GenericStreamPartition));
608 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
610 if ( KM_SUCCESS(result) )
612 ui64_t this_stream_offset = m_StreamOffset; // m_StreamOffset will be changed by the call to Write_EKLV_Packet
614 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
615 m_StreamOffset, FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
624 AS_02::TimedText::MXFWriter::h__Writer::Finalize()
626 if ( ! m_State.Test_RUNNING() )
628 DefaultLogSink().Error("Cannot finalize file, the primary essence resource has not been written.\n");
632 m_IndexWriter.m_Duration = m_FramesWritten = m_TDesc.ContainerDuration;
634 Result_t result = m_State.Goto_FINAL();
636 if ( KM_SUCCESS(result) )
638 result = WriteAS02Footer();
645 //------------------------------------------------------------------------------------------
647 AS_02::TimedText::MXFWriter::MXFWriter()
651 AS_02::TimedText::MXFWriter::~MXFWriter()
655 // Warning: direct manipulation of MXF structures can interfere
656 // with the normal operation of the wrapper. Caveat emptor!
658 ASDCP::MXF::OP1aHeader&
659 AS_02::TimedText::MXFWriter::OP1aHeader()
661 if ( m_Writer.empty() )
663 assert(g_OP1aHeader);
664 return *g_OP1aHeader;
667 return m_Writer->m_HeaderPart;
670 // Warning: direct manipulation of MXF structures can interfere
671 // with the normal operation of the wrapper. Caveat emptor!
674 AS_02::TimedText::MXFWriter::RIP()
676 if ( m_Writer.empty() )
682 return m_Writer->m_RIP;
685 // Open the file for writing. The file must not exist. Returns error if
686 // the operation cannot be completed.
688 AS_02::TimedText::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info,
689 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
691 if ( Info.LabelSetType != LS_MXF_SMPTE )
693 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
694 return RESULT_FORMAT;
697 m_Writer = new h__Writer(DefaultSMPTEDict());
698 m_Writer->m_Info = Info;
700 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
702 if ( ASDCP_SUCCESS(result) )
703 result = m_Writer->SetSourceStream(TDesc);
705 if ( ASDCP_FAILURE(result) )
713 AS_02::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
715 if ( m_Writer.empty() )
718 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
723 AS_02::TimedText::MXFWriter::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
725 if ( m_Writer.empty() )
728 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
731 // Closes the MXF file, writing the index and other closing information.
733 AS_02::TimedText::MXFWriter::Finalize()
735 if ( m_Writer.empty() )
738 return m_Writer->Finalize();
744 // end AS_02_timedText.cpp