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"
36 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 frame wrapping of D-Cinema Timed Text data";
37 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
40 //------------------------------------------------------------------------------------------
43 MIME2str(TimedText::MIMEType_t m)
45 if ( m == TimedText::MT_PNG )
48 else if ( m == TimedText::MT_OPENTYPE )
49 return "application/x-opentype";
51 return "application/octet-stream";
56 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
61 UUID TmpID(TDesc.AssetID);
64 fprintf(stream, " EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
65 fprintf(stream, "ContainerDuration: %u\n", TDesc.ContainerDuration);
66 fprintf(stream, " AssetID: %s\n", TmpID.EncodeHex(buf, 64));
67 fprintf(stream, " NamespaceName: %s\n", TDesc.NamespaceName.c_str());
68 fprintf(stream, " ResourceCount: %lu\n", TDesc.ResourceList.size());
70 TimedText::ResourceList_t::const_iterator ri;
71 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
73 TmpID.Set((*ri).ResourceID);
74 fprintf(stream, " %s: %s\n",
75 TmpID.EncodeHex(buf, 64),
76 MIME2str((*ri).Type));
82 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
87 UUID TmpID(m_AssetID);
89 fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
92 Kumu::hexdump(m_Data, dump_len, stream);
95 //------------------------------------------------------------------------------------------
97 typedef std::map<UUID, UUID> ResourceMap_t;
99 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
101 TimedTextDescriptor* m_EssenceDescriptor;
102 ResourceMap_t m_ResourceMap;
104 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
107 TimedTextDescriptor m_TDesc;
109 h__Reader() : m_EssenceDescriptor(0) {
110 memset(&m_TDesc.AssetID, 0, UUIDlen);
113 Result_t OpenRead(const char*);
114 Result_t MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
115 Result_t ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
116 Result_t ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
121 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
123 assert(m_EssenceDescriptor);
124 memset(&m_TDesc.AssetID, 0, UUIDlen);
125 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
127 TDesc.EditRate = TDescObj->SampleRate;
128 TDesc.ContainerDuration = TDescObj->ContainerDuration;
129 memcpy(TDesc.AssetID, TDescObj->AssetID.Value(), UUIDlen);
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->ResourceID.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->ResourceID, *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) )
193 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
194 AESDecContext* Ctx, HMACContext* HMAC)
196 if ( ! m_File.IsOpen() )
199 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_DCTimedTextEssence), Ctx, HMAC);
201 if( ASDCP_SUCCESS(result) )
203 FrameBuf.AssetID(m_TDesc.AssetID);
204 FrameBuf.MIMEType("text/xml");
212 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
213 AESDecContext* Ctx, HMACContext* HMAC)
215 KM_TEST_NULL_L(uuid);
218 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
219 if ( ri == m_ResourceMap.end() )
222 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
226 DCTimedTextResourceDescriptor* DescObject = 0;
227 // get the subdescriptor
228 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, (InterchangeObject**)&DescObject);
230 if ( KM_SUCCESS(result) )
232 Array<RIP::Pair>::const_iterator pi;
236 // Look up the partition start in the RIP using the SID.
237 // Count the sequence length in because this is the sequence
238 // value needed to complete the HMAC.
239 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
241 if ( (*pi).BodySID == DescObject->ResourceSID )
248 if ( TmpPair.ByteOffset == 0 )
250 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->ResourceSID);
251 return RESULT_FORMAT;
254 if ( KM_SUCCESS(result) )
256 FrameBuf.AssetID(uuid);
257 FrameBuf.MIMEType(DescObject->ResourceMIMEType);
259 // seek tp the start of the partition
260 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
262 m_LastPosition = TmpPair.ByteOffset;
263 result = m_File.Seek(TmpPair.ByteOffset);
266 // read the partition header
267 MXF::Partition GSPart;
268 result = GSPart.InitFromFile(m_File);
270 if( ASDCP_SUCCESS(result) )
273 if ( DescObject->ResourceSID != GSPart.BodySID )
276 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
277 return RESULT_FORMAT;
280 // read the essence packet
281 if( ASDCP_SUCCESS(result) )
282 result = ReadEKLVPacket(0, FrameBuf, Dict::ul(MDD_DCTimedTextDescriptor), Ctx, HMAC);
291 //------------------------------------------------------------------------------------------
293 ASDCP::TimedText::MXFReader::MXFReader()
295 m_Reader = new h__Reader;
299 ASDCP::TimedText::MXFReader::~MXFReader()
303 // Open the file for reading. The file must exist. Returns error if the
304 // operation cannot be completed.
306 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
308 return m_Reader->OpenRead(filename);
311 // Fill the struct with the values from the file's header.
312 // Returns RESULT_INIT if the file is not open.
314 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
316 if ( m_Reader && m_Reader->m_File.IsOpen() )
318 TDesc = m_Reader->m_TDesc;
325 // Fill the struct with the values from the file's header.
326 // Returns RESULT_INIT if the file is not open.
328 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
330 if ( m_Reader && m_Reader->m_File.IsOpen() )
332 Info = m_Reader->m_Info;
341 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
343 FrameBuffer FrameBuf(2*Kumu::Megabyte);
345 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
347 if ( ASDCP_SUCCESS(result) )
348 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
355 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
356 AESDecContext* Ctx, HMACContext* HMAC) const
358 if ( m_Reader && m_Reader->m_File.IsOpen() )
359 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
366 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
367 AESDecContext* Ctx, HMACContext* HMAC) const
369 if ( m_Reader && m_Reader->m_File.IsOpen() )
370 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
378 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
380 if ( m_Reader->m_File.IsOpen() )
381 m_Reader->m_HeaderPart.Dump(stream);
387 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
389 if ( m_Reader->m_File.IsOpen() )
390 m_Reader->m_FooterPart.Dump(stream);
393 //------------------------------------------------------------------------------------------
397 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
400 TimedTextDescriptor m_TDesc;
401 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
402 ui32_t m_ResourceSID;
404 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
406 h__Writer() : m_ResourceSID(10) {
407 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
412 Result_t OpenWrite(const char*, ui32_t HeaderSize);
413 Result_t SetSourceStream(const TimedTextDescriptor&);
414 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
415 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
417 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
422 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
424 assert(m_EssenceDescriptor);
425 MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
427 TDescObj->SampleRate = TDesc.EditRate;
428 TDescObj->ContainerDuration = TDesc.ContainerDuration;
429 TDescObj->AssetID.Set(TDesc.AssetID);
430 TDescObj->RootNamespaceName = TDesc.NamespaceName;
431 TDescObj->UTFEncoding = TDesc.EncodingName;
438 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
440 if ( ! m_State.Test_BEGIN() )
443 Result_t result = m_File.OpenWrite(filename);
445 if ( ASDCP_SUCCESS(result) )
447 m_HeaderSize = HeaderSize;
448 m_EssenceDescriptor = new DCTimedTextDescriptor();
449 result = m_State.Goto_INIT();
457 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
459 if ( ! m_State.Test_INIT() )
463 ResourceList_t::const_iterator ri;
464 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
466 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
468 DCTimedTextResourceDescriptor* resourceSubdescriptor = new DCTimedTextResourceDescriptor;
469 GenRandomValue(resourceSubdescriptor->InstanceUID);
470 resourceSubdescriptor->ResourceID.Set((*ri).ResourceID);
471 resourceSubdescriptor->ResourceMIMEType = MIME2str((*ri).Type);
472 resourceSubdescriptor->ResourceSID = m_ResourceSID++;
473 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
474 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
479 if ( ASDCP_SUCCESS(result) )
482 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
483 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
485 AddEssenceDescriptor(UL(Dict::ul(MDD_DCTimedTextWrapping)));
487 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
489 if ( KM_SUCCESS(result) )
490 result = CreateBodyPart(m_TDesc.EditRate);
493 if ( ASDCP_SUCCESS(result) )
495 memcpy(m_EssenceUL, Dict::ul(MDD_DCTimedTextEssence), SMPTE_UL_LENGTH);
496 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
497 result = m_State.Goto_READY();
505 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
506 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
508 Result_t result = m_State.Goto_RUNNING();
510 if ( ASDCP_SUCCESS(result) )
512 // TODO: make sure it's XML
514 ui32_t str_size = XMLDoc.size();
515 FrameBuffer FrameBuf(str_size);
517 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
518 FrameBuf.Size(str_size);
520 IndexTableSegment::IndexEntry Entry;
521 Entry.StreamOffset = m_StreamOffset;
523 if ( ASDCP_SUCCESS(result) )
524 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
526 if ( ASDCP_SUCCESS(result) )
528 m_FooterPart.PushIndexEntry(Entry);
539 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
540 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
542 if ( ! m_State.Test_RUNNING() )
545 Kumu::fpos_t here = m_File.Tell();
547 // create generic stream partition header
548 MXF::Partition GSPart;
550 GSPart.ThisPartition = here;
551 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
552 GSPart.BodySID = m_ResourceSID;
553 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
555 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_ResourceSID++, here));
556 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_DCTimedTextEssence)));
557 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
558 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
560 if ( ASDCP_SUCCESS(result) )
561 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
569 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
571 if ( ! m_State.Test_RUNNING() )
574 m_FramesWritten = m_TDesc.ContainerDuration;
575 m_State.Goto_FINAL();
577 return WriteMXFFooter();
581 //------------------------------------------------------------------------------------------
583 ASDCP::TimedText::MXFWriter::MXFWriter()
587 ASDCP::TimedText::MXFWriter::~MXFWriter()
592 // Open the file for writing. The file must not exist. Returns error if
593 // the operation cannot be completed.
595 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
596 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
598 if ( Info.LabelSetType != LS_MXF_SMPTE )
600 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
601 return RESULT_FORMAT;
604 m_Writer = new h__Writer;
606 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
608 if ( ASDCP_SUCCESS(result) )
610 m_Writer->m_Info = Info;
611 result = m_Writer->SetSourceStream(TDesc);
614 if ( ASDCP_FAILURE(result) )
622 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
624 if ( m_Writer.empty() )
627 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
632 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
634 if ( m_Writer.empty() )
637 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
640 // Closes the MXF file, writing the index and other closing information.
642 ASDCP::TimedText::MXFWriter::Finalize()
644 if ( m_Writer.empty() )
647 return m_Writer->Finalize();
653 // end AS_DCP_timedText.cpp