2 Copyright (c) 2008-2014, 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 Batch<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 )
117 TmpResource.Type = ASDCP::TimedText::MT_OPENTYPE;
119 else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
120 TmpResource.Type = ASDCP::TimedText::MT_PNG;
123 TmpResource.Type = ASDCP::TimedText::MT_BIN;
125 TDesc.ResourceList.push_back(TmpResource);
126 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
130 DefaultLogSink().Error("Broken sub-descriptor link\n");
131 return RESULT_FORMAT;
140 AS_02::TimedText::MXFReader::h__Reader::OpenRead(const std::string& filename)
142 Result_t result = OpenMXFRead(filename.c_str());
144 if( ASDCP_SUCCESS(result) )
146 if ( m_EssenceDescriptor == 0 )
148 InterchangeObject* tmp_iobj = 0;
149 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
150 m_EssenceDescriptor = static_cast<ASDCP::MXF::TimedTextDescriptor*>(tmp_iobj);
153 if( ASDCP_SUCCESS(result) )
154 result = MD_to_TimedText_TDesc(m_TDesc);
162 AS_02::TimedText::MXFReader::h__Reader::ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf,
163 AESDecContext* Ctx, HMACContext* HMAC)
165 if ( ! m_File.IsOpen() )
169 Result_t result = ReadEKLVFrame(0, FrameBuf, m_Dict->ul(MDD_TimedTextEssence), Ctx, HMAC);
171 if( ASDCP_SUCCESS(result) )
173 FrameBuf.AssetID(m_TDesc.AssetID);
174 FrameBuf.MIMEType("text/xml");
182 AS_02::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const Kumu::UUID& uuid,
183 ASDCP::TimedText::FrameBuffer& FrameBuf,
184 AESDecContext* Ctx, HMACContext* HMAC)
186 ResourceMap_t::const_iterator ri = m_ResourceMap.find(uuid);
187 if ( ri == m_ResourceMap.end() )
190 DefaultLogSink().Error("No such resource: %s\n", uuid.EncodeHex(buf, 64));
194 TimedTextResourceSubDescriptor* DescObject = 0;
195 // get the subdescriptor
196 InterchangeObject* tmp_iobj = 0;
197 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
198 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
200 if ( KM_SUCCESS(result) )
202 Array<RIP::Pair>::const_iterator pi;
206 // Look up the partition start in the RIP using the SID.
207 // Count the sequence length in because this is the sequence
208 // value needed to complete the HMAC.
209 for ( pi = m_RIP.PairArray.begin(); pi != m_RIP.PairArray.end(); ++pi, ++sequence )
211 if ( (*pi).BodySID == DescObject->EssenceStreamID )
218 if ( TmpPair.ByteOffset == 0 )
220 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
221 return RESULT_FORMAT;
224 if ( KM_SUCCESS(result) )
226 FrameBuf.AssetID(uuid.Value());
227 FrameBuf.MIMEType(DescObject->MIMEMediaType);
229 // seek tp the start of the partition
230 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
232 m_LastPosition = TmpPair.ByteOffset;
233 result = m_File.Seek(TmpPair.ByteOffset);
236 // read the partition header
237 ASDCP::MXF::Partition GSPart(m_Dict);
238 result = GSPart.InitFromFile(m_File);
240 if( ASDCP_SUCCESS(result) )
243 if ( DescObject->EssenceStreamID != GSPart.BodySID )
246 DefaultLogSink().Error("Generic stream partition body differs: %s\n", uuid.EncodeHex(buf, 64));
247 return RESULT_FORMAT;
250 // read the essence packet
252 if( ASDCP_SUCCESS(result) )
253 result = ReadEKLVPacket(0, sequence, FrameBuf, m_Dict->ul(MDD_GenericStream_DataElement), Ctx, HMAC);
262 //------------------------------------------------------------------------------------------
264 AS_02::TimedText::MXFReader::MXFReader()
266 m_Reader = new h__Reader(DefaultSMPTEDict());
270 AS_02::TimedText::MXFReader::~MXFReader()
274 // Warning: direct manipulation of MXF structures can interfere
275 // with the normal operation of the wrapper. Caveat emptor!
277 ASDCP::MXF::OP1aHeader&
278 AS_02::TimedText::MXFReader::OP1aHeader()
280 if ( m_Reader.empty() )
282 assert(g_OP1aHeader);
283 return *g_OP1aHeader;
286 return m_Reader->m_HeaderPart;
289 // Warning: direct manipulation of MXF structures can interfere
290 // with the normal operation of the wrapper. Caveat emptor!
292 AS_02::MXF::AS02IndexReader&
293 AS_02::TimedText::MXFReader::AS02IndexReader()
295 if ( m_Reader.empty() )
297 assert(g_AS02IndexReader);
298 return *g_AS02IndexReader;
301 return m_Reader->m_IndexAccess;
304 // Warning: direct manipulation of MXF structures can interfere
305 // with the normal operation of the wrapper. Caveat emptor!
308 AS_02::TimedText::MXFReader::RIP()
310 if ( m_Reader.empty() )
316 return m_Reader->m_RIP;
319 // Open the file for reading. The file must exist. Returns error if the
320 // operation cannot be completed.
322 AS_02::TimedText::MXFReader::OpenRead(const std::string& filename) const
324 return m_Reader->OpenRead(filename);
327 // Fill the struct with the values from the file's header.
328 // Returns RESULT_INIT if the file is not open.
330 AS_02::TimedText::MXFReader::FillTimedTextDescriptor(TimedText::TimedTextDescriptor& TDesc) const
332 if ( m_Reader && m_Reader->m_File.IsOpen() )
334 TDesc = m_Reader->m_TDesc;
341 // Fill the struct with the values from the file's header.
342 // Returns RESULT_INIT if the file is not open.
344 AS_02::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
346 if ( m_Reader && m_Reader->m_File.IsOpen() )
348 Info = m_Reader->m_Info;
357 AS_02::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
359 ASDCP::TimedText::FrameBuffer FrameBuf(8*Kumu::Megabyte);
361 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
363 if ( ASDCP_SUCCESS(result) )
364 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
371 AS_02::TimedText::MXFReader::ReadTimedTextResource(ASDCP::TimedText::FrameBuffer& FrameBuf,
372 AESDecContext* Ctx, HMACContext* HMAC) const
374 if ( m_Reader && m_Reader->m_File.IsOpen() )
375 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
382 AS_02::TimedText::MXFReader::ReadAncillaryResource(const Kumu::UUID& uuid, ASDCP::TimedText::FrameBuffer& FrameBuf,
383 AESDecContext* Ctx, HMACContext* HMAC) const
385 if ( m_Reader && m_Reader->m_File.IsOpen() )
386 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
394 AS_02::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
396 if ( m_Reader->m_File.IsOpen() )
397 m_Reader->m_HeaderPart.Dump(stream);
403 AS_02::TimedText::MXFReader::DumpIndex(FILE* stream) const
405 if ( m_Reader->m_File.IsOpen() )
406 m_Reader->m_IndexAccess.Dump(stream);
411 AS_02::TimedText::MXFReader::Close() const
413 if ( m_Reader && m_Reader->m_File.IsOpen() )
423 //------------------------------------------------------------------------------------------
427 class AS_02::TimedText::MXFWriter::h__Writer : public AS_02::h__AS02WriterClip
429 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
433 TimedTextDescriptor m_TDesc;
434 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
435 ui32_t m_EssenceStreamID;
437 h__Writer(const Dictionary& d) : AS_02::h__AS02WriterClip(d), m_EssenceStreamID(10)
439 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
442 virtual ~h__Writer() {}
444 Result_t OpenWrite(const std::string&, ui32_t HeaderSize);
445 Result_t SetSourceStream(const ASDCP::TimedText::TimedTextDescriptor&);
446 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
447 Result_t WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
449 Result_t TimedText_TDesc_to_MD(ASDCP::TimedText::TimedTextDescriptor& TDesc);
454 AS_02::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
456 assert(m_EssenceDescriptor);
457 ASDCP::MXF::TimedTextDescriptor* TDescObj = (ASDCP::MXF::TimedTextDescriptor*)m_EssenceDescriptor;
459 TDescObj->SampleRate = TDesc.EditRate;
460 TDescObj->ContainerDuration = TDesc.ContainerDuration;
461 TDescObj->ResourceID.Set(TDesc.AssetID);
462 TDescObj->NamespaceURI = TDesc.NamespaceName;
463 TDescObj->UCSEncoding = TDesc.EncodingName;
470 AS_02::TimedText::MXFWriter::h__Writer::OpenWrite(const std::string& filename, ui32_t HeaderSize)
472 if ( ! m_State.Test_BEGIN() )
474 KM_RESULT_STATE_HERE();
478 Result_t result = m_File.OpenWrite(filename.c_str());
480 if ( ASDCP_SUCCESS(result) )
482 m_HeaderSize = HeaderSize;
483 m_EssenceDescriptor = new ASDCP::MXF::TimedTextDescriptor(m_Dict);
484 result = m_State.Goto_INIT();
492 AS_02::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
494 if ( ! m_State.Test_INIT() )
496 KM_RESULT_STATE_HERE();
503 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
505 if ( KM_SUCCESS(result) )
507 ResourceList_t::const_iterator i;
508 for ( i = m_TDesc.ResourceList.begin() ; i != m_TDesc.ResourceList.end(); ++i )
510 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor(m_Dict);
511 GenRandomValue(resourceSubdescriptor->InstanceUID);
512 resourceSubdescriptor->AncillaryResourceID.Set((*i).ResourceID);
513 resourceSubdescriptor->MIMEMediaType = MIME2str((*i).Type);
514 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
515 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
516 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
520 if ( KM_SUCCESS(result) )
522 result = WriteAS02Header(TIMED_TEXT_PACKAGE_LABEL, UL(m_Dict->ul(MDD_TimedTextWrappingClip)),
523 "Data Track", UL(m_EssenceUL), UL(m_Dict->ul(MDD_TimedTextEssence)),
524 TDesc.EditRate, derive_timecode_rate_from_edit_rate(TDesc.EditRate));
527 if ( KM_SUCCESS(result) )
529 this->m_IndexWriter.SetPrimerLookup(&this->m_HeaderPart.m_Primer);
532 if ( KM_SUCCESS(result) )
534 memcpy(m_EssenceUL, m_Dict->ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
535 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
536 result = m_State.Goto_READY();
544 AS_02::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
545 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
547 Result_t result = m_State.Goto_RUNNING();
549 if ( KM_SUCCESS(result) )
551 // TODO: make sure it's XML
553 ui32_t str_size = XMLDoc.size();
554 ASDCP::TimedText::FrameBuffer FrameBuf(str_size);
556 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
557 FrameBuf.Size(str_size);
559 IndexTableSegment::IndexEntry Entry;
560 Entry.StreamOffset = m_StreamOffset;
562 if ( KM_SUCCESS(result) )
564 ui64_t this_stream_offset = m_StreamOffset; // m_StreamOffset will be changed by the call to Write_EKLV_Packet
566 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
567 m_StreamOffset, FrameBuf, m_EssenceUL, Ctx, HMAC);
577 AS_02::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
578 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
580 if ( ! m_State.Test_RUNNING() )
582 KM_RESULT_STATE_HERE();
586 Kumu::fpos_t here = m_File.Tell();
589 // create generic stream partition header
590 static UL GenericStream_DataElement(m_Dict->ul(MDD_GenericStream_DataElement));
591 ASDCP::MXF::Partition GSPart(m_Dict);
593 GSPart.ThisPartition = here;
594 GSPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
595 GSPart.BodySID = m_EssenceStreamID;
596 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
598 m_RIP.PairArray.push_back(RIP::Pair(m_EssenceStreamID++, here));
599 GSPart.EssenceContainers.push_back(UL(m_Dict->ul(MDD_TimedTextEssence)));
600 UL TmpUL(m_Dict->ul(MDD_GenericStreamPartition));
601 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
603 if ( KM_SUCCESS(result) )
605 ui64_t this_stream_offset = m_StreamOffset; // m_StreamOffset will be changed by the call to Write_EKLV_Packet
607 result = Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
608 m_StreamOffset, FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
617 AS_02::TimedText::MXFWriter::h__Writer::Finalize()
619 if ( ! m_State.Test_RUNNING() )
621 DefaultLogSink().Error("Cannot finalize file, the primary essence resource has not been written.\n");
625 m_IndexWriter.m_Duration = m_FramesWritten = m_TDesc.ContainerDuration;
626 fprintf(stderr, "m_IndexWriter.m_Duration=%d\n", m_IndexWriter.m_Duration);
628 Result_t result = m_State.Goto_FINAL();
630 if ( KM_SUCCESS(result) )
632 result = WriteAS02Footer();
639 //------------------------------------------------------------------------------------------
641 AS_02::TimedText::MXFWriter::MXFWriter()
645 AS_02::TimedText::MXFWriter::~MXFWriter()
649 // Warning: direct manipulation of MXF structures can interfere
650 // with the normal operation of the wrapper. Caveat emptor!
652 ASDCP::MXF::OP1aHeader&
653 AS_02::TimedText::MXFWriter::OP1aHeader()
655 if ( m_Writer.empty() )
657 assert(g_OP1aHeader);
658 return *g_OP1aHeader;
661 return m_Writer->m_HeaderPart;
664 // Warning: direct manipulation of MXF structures can interfere
665 // with the normal operation of the wrapper. Caveat emptor!
668 AS_02::TimedText::MXFWriter::RIP()
670 if ( m_Writer.empty() )
676 return m_Writer->m_RIP;
679 // Open the file for writing. The file must not exist. Returns error if
680 // the operation cannot be completed.
682 AS_02::TimedText::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info,
683 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
685 if ( Info.LabelSetType != LS_MXF_SMPTE )
687 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
688 return RESULT_FORMAT;
691 m_Writer = new h__Writer(DefaultSMPTEDict());
692 m_Writer->m_Info = Info;
694 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
696 if ( ASDCP_SUCCESS(result) )
697 result = m_Writer->SetSourceStream(TDesc);
699 if ( ASDCP_FAILURE(result) )
707 AS_02::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
709 if ( m_Writer.empty() )
712 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
717 AS_02::TimedText::MXFWriter::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
719 if ( m_Writer.empty() )
722 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
725 // Closes the MXF file, writing the index and other closing information.
727 AS_02::TimedText::MXFWriter::Finalize()
729 if ( m_Writer.empty() )
732 return m_Writer->Finalize();
738 // end AS_02_timedText.cpp