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"
34 #include "AS_DCP_TimedText.h"
37 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 frame wrapping of D-Cinema Timed Text data";
38 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
41 //------------------------------------------------------------------------------------------
44 MIME2str(TimedText::MIMEType_t m)
46 if ( m == TimedText::MT_PNG )
49 else if ( m == TimedText::MT_OPENTYPE )
50 return "application/x-opentype";
52 return "application/octet-stream";
57 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
62 UUID TmpID(TDesc.AssetID);
65 fprintf(stream, " EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
66 fprintf(stream, "ContainerDuration: %u\n", TDesc.ContainerDuration);
67 fprintf(stream, " AssetID: %s\n", TmpID.EncodeHex(buf, 64));
68 fprintf(stream, " NamespaceName: %s\n", TDesc.NamespaceName.c_str());
69 fprintf(stream, " ResourceCount: %lu\n", TDesc.ResourceList.size());
71 TimedText::ResourceList_t::const_iterator ri;
72 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
74 TmpID.Set((*ri).ResourceID);
75 fprintf(stream, " %s: %s\n",
76 TmpID.EncodeHex(buf, 64),
77 MIME2str((*ri).Type));
83 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
88 UUID TmpID(m_AssetID);
90 fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
93 Kumu::hexdump(m_Data, dump_len, stream);
96 //------------------------------------------------------------------------------------------
98 typedef std::map<UUID, UUID> ResourceMap_t;
100 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
102 TimedTextDescriptor* m_EssenceDescriptor;
103 ResourceMap_t m_ResourceMap;
105 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
108 TimedTextDescriptor m_TDesc;
110 h__Reader() : m_EssenceDescriptor(0) {
111 memset(&m_TDesc.AssetID, 0, UUIDlen);
114 Result_t OpenRead(const char*);
115 Result_t MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
116 Result_t ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
117 Result_t ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
122 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
124 assert(m_EssenceDescriptor);
125 memset(&m_TDesc.AssetID, 0, UUIDlen);
126 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
128 TDesc.EditRate = TDescObj->SampleRate;
129 TDesc.ContainerDuration = TDescObj->ContainerDuration;
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 result = m_HeaderPart.GetMDObjectByID(*sdi, (InterchangeObject**)&DescObject);
141 if ( KM_SUCCESS(result) )
143 TimedTextResourceDescriptor TmpResource;
144 memcpy(TmpResource.ResourceID, DescObject->ResourcePackageID.Value(), UUIDlen);
146 if ( DescObject->ResourceMIMEType.find("font/") != std::string::npos )
147 TmpResource.Type = MT_OPENTYPE;
149 else if ( DescObject->ResourceMIMEType.find("image/png") != std::string::npos )
150 TmpResource.Type = MT_PNG;
153 TmpResource.Type = MT_BIN;
155 TDesc.ResourceList.push_back(TmpResource);
156 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->ResourcePackageID, *sdi));
160 DefaultLogSink().Error("Broken sub-descriptor link\n");
161 return RESULT_FORMAT;
170 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
172 Result_t result = OpenMXFRead(filename);
174 if( ASDCP_SUCCESS(result) )
176 if ( m_EssenceDescriptor == 0 )
177 m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor), (InterchangeObject**)&m_EssenceDescriptor);
179 result = MD_to_TimedText_TDesc(m_TDesc);
182 if( ASDCP_SUCCESS(result) )
183 result = InitMXFIndex();
185 if( ASDCP_SUCCESS(result) )
188 if( ASDCP_SUCCESS(result) )
189 memcpy(m_TDesc.AssetID, m_Info.AssetUUID, UUIDlen);
196 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
197 AESDecContext* Ctx, HMACContext* HMAC)
199 if ( ! m_File.IsOpen() )
202 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_DCTimedTextEssence), Ctx, HMAC);
204 if( ASDCP_SUCCESS(result) )
206 FrameBuf.AssetID(m_TDesc.AssetID);
207 FrameBuf.MIMEType("text/xml");
215 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
216 AESDecContext* Ctx, HMACContext* HMAC)
218 KM_TEST_NULL_L(uuid);
221 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
222 if ( ri == m_ResourceMap.end() )
225 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
229 DCTimedTextResourceDescriptor* DescObject = 0;
230 // get the subdescriptor
231 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, (InterchangeObject**)&DescObject);
233 if ( KM_SUCCESS(result) )
235 Array<RIP::Pair>::const_iterator pi;
239 // Look up the partition start in the RIP using the SID.
240 // Count the sequence length in because this is the sequence
241 // value needed to complete the HMAC.
242 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
244 if ( (*pi).BodySID == DescObject->ResourceSID )
251 if ( TmpPair.ByteOffset == 0 )
253 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->ResourceSID);
254 return RESULT_FORMAT;
257 if ( KM_SUCCESS(result) )
259 FrameBuf.AssetID(uuid);
260 FrameBuf.MIMEType(DescObject->ResourceMIMEType);
262 // seek tp the start of the partition
263 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
265 m_LastPosition = TmpPair.ByteOffset;
266 result = m_File.Seek(TmpPair.ByteOffset);
269 // read the partition header
270 MXF::Partition GSPart;
271 result = GSPart.InitFromFile(m_File);
273 if( ASDCP_SUCCESS(result) )
276 if ( DescObject->ResourceSID != GSPart.BodySID )
279 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
280 return RESULT_FORMAT;
283 // read the essence packet
284 if( ASDCP_SUCCESS(result) )
285 result = ReadEKLVPacket(0, FrameBuf, Dict::ul(MDD_DCTimedTextDescriptor), Ctx, HMAC);
294 //------------------------------------------------------------------------------------------
296 ASDCP::TimedText::MXFReader::MXFReader()
298 m_Reader = new h__Reader;
302 ASDCP::TimedText::MXFReader::~MXFReader()
306 // Open the file for reading. The file must exist. Returns error if the
307 // operation cannot be completed.
309 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
311 return m_Reader->OpenRead(filename);
314 // Fill the struct with the values from the file's header.
315 // Returns RESULT_INIT if the file is not open.
317 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
319 if ( m_Reader && m_Reader->m_File.IsOpen() )
321 TDesc = m_Reader->m_TDesc;
328 // Fill the struct with the values from the file's header.
329 // Returns RESULT_INIT if the file is not open.
331 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
333 if ( m_Reader && m_Reader->m_File.IsOpen() )
335 Info = m_Reader->m_Info;
344 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
346 FrameBuffer FrameBuf(2*Kumu::Megabyte);
348 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
350 if ( ASDCP_SUCCESS(result) )
351 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
358 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
359 AESDecContext* Ctx, HMACContext* HMAC) const
361 if ( m_Reader && m_Reader->m_File.IsOpen() )
362 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
369 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
370 AESDecContext* Ctx, HMACContext* HMAC) const
372 if ( m_Reader && m_Reader->m_File.IsOpen() )
373 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
381 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
383 if ( m_Reader->m_File.IsOpen() )
384 m_Reader->m_HeaderPart.Dump(stream);
390 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
392 if ( m_Reader->m_File.IsOpen() )
393 m_Reader->m_FooterPart.Dump(stream);
396 //------------------------------------------------------------------------------------------
400 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
403 TimedTextDescriptor m_TDesc;
404 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
405 ui32_t m_ResourceSID;
407 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
409 h__Writer() : m_ResourceSID(10) {
410 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
415 Result_t OpenWrite(const char*, ui32_t HeaderSize);
416 Result_t SetSourceStream(const TimedTextDescriptor&);
417 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
418 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
420 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
425 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
427 assert(m_EssenceDescriptor);
428 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
430 TDescObj->SampleRate = TDesc.EditRate;
431 TDescObj->ContainerDuration = TDesc.ContainerDuration;
432 TDescObj->RootNamespaceName = TDesc.NamespaceName;
433 TDescObj->UTFEncoding = TDesc.EncodingName;
440 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
442 if ( ! m_State.Test_BEGIN() )
445 Result_t result = m_File.OpenWrite(filename);
447 if ( ASDCP_SUCCESS(result) )
449 m_HeaderSize = HeaderSize;
450 m_EssenceDescriptor = new DCTimedTextDescriptor();
451 result = m_State.Goto_INIT();
459 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
461 if ( ! m_State.Test_INIT() )
465 ResourceList_t::const_iterator ri;
466 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
468 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
470 DCTimedTextResourceDescriptor* resourceSubdescriptor = new DCTimedTextResourceDescriptor;
471 GenRandomValue(resourceSubdescriptor->InstanceUID);
472 resourceSubdescriptor->ResourcePackageID.Set((*ri).ResourceID);
473 resourceSubdescriptor->ResourceMIMEType = MIME2str((*ri).Type);
474 resourceSubdescriptor->ResourceSID = m_ResourceSID++;
475 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
476 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
481 if ( ASDCP_SUCCESS(result) )
483 UMID SourcePackageUMID;
484 SourcePackageUMID.MakeUMID(0x0f, m_TDesc.AssetID);
487 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
488 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL, SourcePackageUMID);
490 AddEssenceDescriptor(UL(Dict::ul(MDD_DCTimedTextWrapping)));
492 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
494 if ( KM_SUCCESS(result) )
495 result = CreateBodyPart(m_TDesc.EditRate);
498 if ( ASDCP_SUCCESS(result) )
500 memcpy(m_EssenceUL, Dict::ul(MDD_DCTimedTextEssence), SMPTE_UL_LENGTH);
501 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
502 result = m_State.Goto_READY();
510 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
511 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
513 Result_t result = m_State.Goto_RUNNING();
515 if ( ASDCP_SUCCESS(result) )
517 // TODO: make sure it's XML
519 ui32_t str_size = XMLDoc.size();
520 FrameBuffer FrameBuf(str_size);
522 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
523 FrameBuf.Size(str_size);
525 IndexTableSegment::IndexEntry Entry;
526 Entry.StreamOffset = m_StreamOffset;
528 if ( ASDCP_SUCCESS(result) )
529 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
531 if ( ASDCP_SUCCESS(result) )
533 m_FooterPart.PushIndexEntry(Entry);
544 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
545 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
547 if ( ! m_State.Test_RUNNING() )
550 Kumu::fpos_t here = m_File.Tell();
552 // create generic stream partition header
553 MXF::Partition GSPart;
555 GSPart.ThisPartition = here;
556 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
557 GSPart.BodySID = m_ResourceSID;
558 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
560 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_ResourceSID++, here));
561 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_DCTimedTextEssence)));
562 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
563 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
565 if ( ASDCP_SUCCESS(result) )
566 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
574 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
576 if ( ! m_State.Test_RUNNING() )
579 m_FramesWritten = m_TDesc.ContainerDuration;
580 m_State.Goto_FINAL();
582 return WriteMXFFooter();
586 //------------------------------------------------------------------------------------------
588 ASDCP::TimedText::MXFWriter::MXFWriter()
592 ASDCP::TimedText::MXFWriter::~MXFWriter()
597 // Open the file for writing. The file must not exist. Returns error if
598 // the operation cannot be completed.
600 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
601 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
603 if ( Info.LabelSetType != LS_MXF_SMPTE )
605 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
606 return RESULT_FORMAT;
609 m_Writer = new h__Writer;
611 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
613 if ( ASDCP_SUCCESS(result) )
615 m_Writer->m_Info = Info;
616 result = m_Writer->SetSourceStream(TDesc);
619 if ( ASDCP_FAILURE(result) )
627 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
629 if ( m_Writer.empty() )
632 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
637 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
639 if ( m_Writer.empty() )
642 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
645 // Closes the MXF file, writing the index and other closing information.
647 ASDCP::TimedText::MXFWriter::Finalize()
649 if ( m_Writer.empty() )
652 return m_Writer->Finalize();
658 // end AS_DCP_timedText.cpp