6e34a5d8bcae2c884c0563cc9ea7966967b26f5b
[asdcplib.git] / src / AS_DCP_TimedText.cpp
1 /*
2 Copyright (c) 2008-2016, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
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.
15
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.
26 */
27 /*! \file    AS_DCP_TimedText.cpp
28     \version $Id$       
29     \brief   AS-DCP library, PCM essence reader and writer implementation
30 */
31
32
33 #include "AS_DCP_internal.h"
34 #include "KM_xml.h"
35 #include <iostream>
36 #include <iomanip>
37
38 using Kumu::GenRandomValue;
39
40 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 clip wrapping of D-Cinema Timed Text data";
41 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
42
43
44 //------------------------------------------------------------------------------------------
45
46 //
47 static const char*
48 MIME2str(TimedText::MIMEType_t m)
49 {
50   if ( m == TimedText::MT_PNG )
51     return "image/png";
52
53   else if( m == TimedText::MT_OPENTYPE )
54     return "application/x-font-opentype";
55   
56   return "application/octet-stream";
57 }
58
59 //
60 std::ostream&
61 ASDCP::TimedText::operator << (std::ostream& strm, const TimedTextDescriptor& TDesc)
62 {
63   UUID TmpID(TDesc.AssetID);
64   char buf[64];
65
66   strm << "         EditRate: " << (unsigned) TDesc.EditRate.Numerator << "/" << (unsigned) TDesc.EditRate.Denominator << std::endl;
67   strm << "ContainerDuration: " << (unsigned) TDesc.ContainerDuration << std::endl;
68   strm << "          AssetID: " << TmpID.EncodeHex(buf, 64) << std::endl;
69   strm << "    NamespaceName: " << TDesc.NamespaceName << std::endl;
70   strm << "    ResourceCount: " << (unsigned long) TDesc.ResourceList.size() << std::endl;
71
72   TimedText::ResourceList_t::const_iterator ri;
73   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
74     {
75       TmpID.Set((*ri).ResourceID);
76       strm << "    " << TmpID.EncodeHex(buf, 64) << ": " << MIME2str((*ri).Type) << std::endl;
77     }
78
79   return strm;
80 }
81
82 //
83 void
84 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
85 {
86   if ( stream == 0 )
87     stream = stderr;
88
89   UUID TmpID(TDesc.AssetID);
90   char buf[64];
91
92   fprintf(stream, "         EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
93   fprintf(stream, "ContainerDuration: %u\n",    TDesc.ContainerDuration);
94   fprintf(stream, "          AssetID: %s\n",    TmpID.EncodeHex(buf, 64));
95   fprintf(stream, "    NamespaceName: %s\n",    TDesc.NamespaceName.c_str());
96   fprintf(stream, "    ResourceCount: %zu\n",   TDesc.ResourceList.size());
97
98   TimedText::ResourceList_t::const_iterator ri;
99   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
100     {
101       TmpID.Set((*ri).ResourceID);
102       fprintf(stream, "    %s: %s\n",
103               TmpID.EncodeHex(buf, 64), 
104               MIME2str((*ri).Type));
105     }
106 }
107
108 //
109 void
110 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
111 {
112   if ( stream == 0 )
113     stream = stderr;
114
115   UUID TmpID(m_AssetID);
116   char buf[64];
117   fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
118
119   if ( dump_len > 0 )
120     Kumu::hexdump(m_Data, dump_len, stream);
121 }
122
123 //------------------------------------------------------------------------------------------
124
125 typedef std::map<UUID, UUID> ResourceMap_t;
126
127 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__ASDCPReader
128 {
129   MXF::TimedTextDescriptor* m_EssenceDescriptor;
130   ResourceMap_t             m_ResourceMap;
131
132   ASDCP_NO_COPY_CONSTRUCT(h__Reader);
133
134 public:
135   TimedTextDescriptor m_TDesc;    
136
137   h__Reader(const Dictionary& d) : ASDCP::h__ASDCPReader(d), m_EssenceDescriptor(0) {
138     memset(&m_TDesc.AssetID, 0, UUIDlen);
139   }
140
141   virtual ~h__Reader() {}
142
143   Result_t    OpenRead(const std::string&);
144   Result_t    MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
145   Result_t    ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
146   Result_t    ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
147 };
148
149 //
150 ASDCP::Result_t
151 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
152 {
153   assert(m_EssenceDescriptor);
154   memset(&m_TDesc.AssetID, 0, UUIDlen);
155   MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
156
157   TDesc.EditRate = TDescObj->SampleRate;
158   assert(TDescObj->ContainerDuration <= 0xFFFFFFFFL);
159   TDesc.ContainerDuration = (ui32_t) TDescObj->ContainerDuration;
160   memcpy(TDesc.AssetID, TDescObj->ResourceID.Value(), UUIDlen);
161   TDesc.NamespaceName = TDescObj->NamespaceURI;
162   TDesc.EncodingName = TDescObj->UCSEncoding;
163
164   Array<UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
165   TimedTextResourceSubDescriptor* DescObject = 0;
166   Result_t result = RESULT_OK;
167
168   for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
169     {
170       InterchangeObject* tmp_iobj = 0;
171       result = m_HeaderPart.GetMDObjectByID(*sdi, &tmp_iobj);
172       DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
173
174       if ( KM_SUCCESS(result) )
175         {
176           TimedTextResourceDescriptor TmpResource;
177           memcpy(TmpResource.ResourceID, DescObject->AncillaryResourceID.Value(), UUIDlen);
178
179           if ( DescObject->MIMEMediaType.find("application/x-font-opentype") != std::string::npos
180                || DescObject->MIMEMediaType.find("application/x-opentype") != std::string::npos
181                || DescObject->MIMEMediaType.find("font/opentype") != std::string::npos )
182             TmpResource.Type = MT_OPENTYPE;
183
184           else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
185             TmpResource.Type = MT_PNG;
186
187           else
188             TmpResource.Type = MT_BIN;
189
190           TDesc.ResourceList.push_back(TmpResource);
191           m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
192         }
193       else
194         {
195           DefaultLogSink().Error("Broken sub-descriptor link\n");
196           return RESULT_FORMAT;
197         }
198     }
199
200   return result;
201 }
202
203 //
204 ASDCP::Result_t
205 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(const std::string& filename)
206 {
207   Result_t result = OpenMXFRead(filename);
208   
209   if( ASDCP_SUCCESS(result) )
210     {
211       if ( m_EssenceDescriptor == 0 )
212         {
213           InterchangeObject* tmp_iobj = 0;
214           result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
215           m_EssenceDescriptor = static_cast<MXF::TimedTextDescriptor*>(tmp_iobj);
216         }
217
218       if( ASDCP_SUCCESS(result) )
219         result = MD_to_TimedText_TDesc(m_TDesc);
220     }
221
222   return result;
223 }
224
225 //
226 ASDCP::Result_t
227 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
228                                                               AESDecContext* Ctx, HMACContext* HMAC)
229 {
230   if ( ! m_File.IsOpen() )
231     return RESULT_INIT;
232
233   assert(m_Dict);
234   Result_t result = ReadEKLVFrame(0, FrameBuf, m_Dict->ul(MDD_TimedTextEssence), Ctx, HMAC);
235
236  if( ASDCP_SUCCESS(result) )
237    {
238      FrameBuf.AssetID(m_TDesc.AssetID);
239      FrameBuf.MIMEType("text/xml");
240    }
241
242  return result;
243 }
244
245 //
246 ASDCP::Result_t
247 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
248                                                               AESDecContext* Ctx, HMACContext* HMAC)
249 {
250   KM_TEST_NULL_L(uuid);
251   UUID RID(uuid);
252
253   ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
254   if ( ri == m_ResourceMap.end() )
255     {
256       char buf[64];
257       DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
258       return RESULT_RANGE;
259     }
260
261   TimedTextResourceSubDescriptor* DescObject = 0;
262   // get the subdescriptor
263   InterchangeObject* tmp_iobj = 0;
264   Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
265   DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
266
267   if ( KM_SUCCESS(result) )
268     {
269       RIP::const_pair_iterator pi;
270       RIP::PartitionPair TmpPair;
271       ui32_t sequence = 0;
272
273       // Look up the partition start in the RIP using the SID.
274       // Count the sequence length in because this is the sequence
275       // value needed to  complete the HMAC.
276       for ( pi = m_RIP.PairArray.begin(); pi != m_RIP.PairArray.end(); ++pi, ++sequence )
277         {
278           if ( (*pi).BodySID == DescObject->EssenceStreamID )
279             {
280               TmpPair = *pi;
281               break;
282             }
283         }
284
285       if ( TmpPair.ByteOffset == 0 )
286         {
287           DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
288           return RESULT_FORMAT;
289         }
290
291       if ( KM_SUCCESS(result) )
292         {
293           FrameBuf.AssetID(uuid);
294           FrameBuf.MIMEType(DescObject->MIMEMediaType);
295
296           // seek tp the start of the partition
297           if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
298             {
299               m_LastPosition = TmpPair.ByteOffset;
300               result = m_File.Seek(TmpPair.ByteOffset);
301             }
302
303           // read the partition header
304           MXF::Partition GSPart(m_Dict);
305           result = GSPart.InitFromFile(m_File);
306
307           if( ASDCP_SUCCESS(result) )
308             {
309               // check the SID
310               if ( DescObject->EssenceStreamID != GSPart.BodySID )
311                 {
312                   char buf[64];
313                   DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
314                   return RESULT_FORMAT;
315                 }
316
317               // read the essence packet
318               assert(m_Dict);
319               if( ASDCP_SUCCESS(result) )
320                 result = ReadEKLVPacket(0, sequence, FrameBuf, m_Dict->ul(MDD_GenericStream_DataElement), Ctx, HMAC);
321             }
322         }
323     }
324
325   return result;
326 }
327
328
329 //------------------------------------------------------------------------------------------
330
331 ASDCP::TimedText::MXFReader::MXFReader()
332 {
333   m_Reader = new h__Reader(DefaultSMPTEDict());
334 }
335
336
337 ASDCP::TimedText::MXFReader::~MXFReader()
338 {
339 }
340
341 // Warning: direct manipulation of MXF structures can interfere
342 // with the normal operation of the wrapper.  Caveat emptor!
343 //
344 ASDCP::MXF::OP1aHeader&
345 ASDCP::TimedText::MXFReader::OP1aHeader()
346 {
347   if ( m_Reader.empty() )
348     {
349       assert(g_OP1aHeader);
350       return *g_OP1aHeader;
351     }
352
353   return m_Reader->m_HeaderPart;
354 }
355
356 // Warning: direct manipulation of MXF structures can interfere
357 // with the normal operation of the wrapper.  Caveat emptor!
358 //
359 ASDCP::MXF::OPAtomIndexFooter&
360 ASDCP::TimedText::MXFReader::OPAtomIndexFooter()
361 {
362   if ( m_Reader.empty() )
363     {
364       assert(g_OPAtomIndexFooter);
365       return *g_OPAtomIndexFooter;
366     }
367
368   return m_Reader->m_IndexAccess;
369 }
370
371 // Warning: direct manipulation of MXF structures can interfere
372 // with the normal operation of the wrapper.  Caveat emptor!
373 //
374 ASDCP::MXF::RIP&
375 ASDCP::TimedText::MXFReader::RIP()
376 {
377   if ( m_Reader.empty() )
378     {
379       assert(g_RIP);
380       return *g_RIP;
381     }
382
383   return m_Reader->m_RIP;
384 }
385
386 // Open the file for reading. The file must exist. Returns error if the
387 // operation cannot be completed.
388 ASDCP::Result_t
389 ASDCP::TimedText::MXFReader::OpenRead(const std::string& filename) const
390 {
391   return m_Reader->OpenRead(filename);
392 }
393
394 // Fill the struct with the values from the file's header.
395 // Returns RESULT_INIT if the file is not open.
396 ASDCP::Result_t
397 ASDCP::TimedText::MXFReader::FillTimedTextDescriptor(TimedText::TimedTextDescriptor& TDesc) const
398 {
399   if ( m_Reader && m_Reader->m_File.IsOpen() )
400     {
401       TDesc = m_Reader->m_TDesc;
402       return RESULT_OK;
403     }
404
405   return RESULT_INIT;
406 }
407
408 // Fill the struct with the values from the file's header.
409 // Returns RESULT_INIT if the file is not open.
410 ASDCP::Result_t
411 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
412 {
413   if ( m_Reader && m_Reader->m_File.IsOpen() )
414     {
415       Info = m_Reader->m_Info;
416       return RESULT_OK;
417     }
418
419   return RESULT_INIT;
420 }
421
422 //
423 ASDCP::Result_t
424 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
425 {
426   FrameBuffer FrameBuf(2*Kumu::Megabyte);
427
428   Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
429
430   if ( ASDCP_SUCCESS(result) )
431     s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
432
433   return result;
434 }
435
436 //
437 ASDCP::Result_t
438 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
439                                                    AESDecContext* Ctx, HMACContext* HMAC) const
440 {
441   if ( m_Reader && m_Reader->m_File.IsOpen() )
442     return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
443
444   return RESULT_INIT;
445 }
446
447 //
448 ASDCP::Result_t
449 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
450                                                    AESDecContext* Ctx, HMACContext* HMAC) const
451 {
452   if ( m_Reader && m_Reader->m_File.IsOpen() )
453     return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
454
455   return RESULT_INIT;
456 }
457
458
459 //
460 void
461 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
462 {
463   if ( m_Reader->m_File.IsOpen() )
464     m_Reader->m_HeaderPart.Dump(stream);
465 }
466
467
468 //
469 void
470 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
471 {
472   if ( m_Reader->m_File.IsOpen() )
473     m_Reader->m_IndexAccess.Dump(stream);
474 }
475
476 //
477 ASDCP::Result_t
478 ASDCP::TimedText::MXFReader::Close() const
479 {
480   if ( m_Reader && m_Reader->m_File.IsOpen() )
481     {
482       m_Reader->Close();
483       return RESULT_OK;
484     }
485
486   return RESULT_INIT;
487 }
488
489
490 //------------------------------------------------------------------------------------------
491
492
493 //
494 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__ASDCPWriter
495 {
496   ASDCP_NO_COPY_CONSTRUCT(h__Writer);
497   h__Writer();
498
499 public:
500   TimedTextDescriptor m_TDesc;
501   byte_t              m_EssenceUL[SMPTE_UL_LENGTH];
502   ui32_t              m_EssenceStreamID;
503
504   h__Writer(const Dictionary& d) : ASDCP::h__ASDCPWriter(d), m_EssenceStreamID(10) {
505     memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
506   }
507
508   virtual ~h__Writer() {}
509
510   Result_t OpenWrite(const std::string&, ui32_t HeaderSize);
511   Result_t SetSourceStream(const TimedTextDescriptor&);
512   Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
513   Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
514   Result_t Finalize();
515   Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
516 };
517
518 //
519 ASDCP::Result_t
520 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
521 {
522   assert(m_EssenceDescriptor);
523   MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
524
525   TDescObj->SampleRate = TDesc.EditRate;
526   TDescObj->ContainerDuration = TDesc.ContainerDuration;
527   TDescObj->ResourceID.Set(TDesc.AssetID);
528   TDescObj->NamespaceURI = TDesc.NamespaceName;
529   TDescObj->UCSEncoding = TDesc.EncodingName;
530
531   return RESULT_OK;
532 }
533
534 //
535 ASDCP::Result_t
536 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(const std::string& filename, ui32_t HeaderSize)
537 {
538   if ( ! m_State.Test_BEGIN() )
539     return RESULT_STATE;
540
541   Result_t result = m_File.OpenWrite(filename);
542
543   if ( ASDCP_SUCCESS(result) )
544     {
545       m_HeaderSize = HeaderSize;
546       m_EssenceDescriptor = new MXF::TimedTextDescriptor(m_Dict);
547       result = m_State.Goto_INIT();
548     }
549
550   return result;
551 }
552
553 //
554 ASDCP::Result_t
555 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
556 {
557   if ( ! m_State.Test_INIT() )
558     return RESULT_STATE;
559
560   m_TDesc = TDesc;
561   ResourceList_t::const_iterator ri;
562   Result_t result = TimedText_TDesc_to_MD(m_TDesc);
563
564   for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
565     {
566       TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor(m_Dict);
567       GenRandomValue(resourceSubdescriptor->InstanceUID);
568       resourceSubdescriptor->AncillaryResourceID.Set((*ri).ResourceID);
569       resourceSubdescriptor->MIMEMediaType = MIME2str((*ri).Type);
570       resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
571       m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
572       m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
573
574       // 72 == sizeof K, L, instanceuid, uuid + sizeof int32 + tag/len * 4
575       m_HeaderSize += ( resourceSubdescriptor->MIMEMediaType.ArchiveLength() * 2 /*ArchiveLength is broken*/ ) + 72;
576     }
577
578   m_EssenceStreamID = 10;
579   assert(m_Dict);
580
581   if ( ASDCP_SUCCESS(result) )
582     {
583       InitHeader(MXFVersion_2004);
584
585       // First RIP Entry
586       if ( m_Info.LabelSetType == LS_MXF_SMPTE )  // ERK
587         {
588           m_RIP.PairArray.push_back(RIP::PartitionPair(0, 0)); // 3-part, no essence in header
589         }
590       else
591         {
592           DefaultLogSink().Error("Unable to write Interop timed-text MXF file.  Use SMPTE DCP options instead.\n");
593           return RESULT_FORMAT;
594         }
595
596       // timecode rate and essence rate are the same
597       AddSourceClip(m_TDesc.EditRate, m_TDesc.EditRate, derive_timecode_rate_from_edit_rate(m_TDesc.EditRate),
598                     TIMED_TEXT_DEF_LABEL, m_EssenceUL, UL(m_Dict->ul(MDD_DataDataDef)), TIMED_TEXT_PACKAGE_LABEL);
599
600       AddEssenceDescriptor(UL(m_Dict->ul(MDD_TimedTextWrappingClip)));
601       result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
602       
603       if ( KM_SUCCESS(result) )
604         result = CreateBodyPart(m_TDesc.EditRate);
605     }
606
607   if ( ASDCP_SUCCESS(result) )
608     {
609       memcpy(m_EssenceUL, m_Dict->ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
610       m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
611       result = m_State.Goto_READY();
612     }
613
614   return result;
615 }
616
617 //
618 ASDCP::Result_t
619 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
620                                                                ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
621 {
622   Result_t result = m_State.Goto_RUNNING();
623
624   if ( ASDCP_SUCCESS(result) )
625     {
626       // TODO: make sure it's XML
627
628       ui32_t str_size = XMLDoc.size();
629       FrameBuffer FrameBuf(str_size);
630       
631       memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
632       FrameBuf.Size(str_size);
633
634       IndexTableSegment::IndexEntry Entry;
635       Entry.StreamOffset = m_StreamOffset;
636       result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
637
638       if ( ASDCP_SUCCESS(result) )
639         {
640           m_FooterPart.PushIndexEntry(Entry);
641           m_FramesWritten++;
642         }
643     }
644
645   return result;
646 }
647
648
649 //
650 ASDCP::Result_t
651 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
652                                                                ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
653 {
654   if ( ! m_State.Test_RUNNING() )
655     return RESULT_STATE;
656
657   Kumu::fpos_t here = m_File.Tell();
658   assert(m_Dict);
659
660   // create generic stream partition header
661   static UL GenericStream_DataElement(m_Dict->ul(MDD_GenericStream_DataElement));
662   MXF::Partition GSPart(m_Dict);
663
664   GSPart.ThisPartition = here;
665   GSPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
666   GSPart.BodySID = m_EssenceStreamID;
667   GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
668
669   m_RIP.PairArray.push_back(RIP::PartitionPair(m_EssenceStreamID++, here));
670   GSPart.EssenceContainers = m_HeaderPart.EssenceContainers;
671   UL TmpUL(m_Dict->ul(MDD_GenericStreamPartition));
672   Result_t result = GSPart.WriteToFile(m_File, TmpUL);
673
674   if ( ASDCP_SUCCESS(result) )
675     result = WriteEKLVPacket(FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
676
677   m_FramesWritten++;
678   return result;
679 }
680
681 //
682 ASDCP::Result_t
683 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
684 {
685   if ( ! m_State.Test_RUNNING() )
686     return RESULT_STATE;
687
688   m_FramesWritten = m_TDesc.ContainerDuration;
689   m_State.Goto_FINAL();
690
691   return WriteASDCPFooter();
692 }
693
694
695 //------------------------------------------------------------------------------------------
696
697 ASDCP::TimedText::MXFWriter::MXFWriter()
698 {
699 }
700
701 ASDCP::TimedText::MXFWriter::~MXFWriter()
702 {
703 }
704
705 // Warning: direct manipulation of MXF structures can interfere
706 // with the normal operation of the wrapper.  Caveat emptor!
707 //
708 ASDCP::MXF::OP1aHeader&
709 ASDCP::TimedText::MXFWriter::OP1aHeader()
710 {
711   if ( m_Writer.empty() )
712     {
713       assert(g_OP1aHeader);
714       return *g_OP1aHeader;
715     }
716
717   return m_Writer->m_HeaderPart;
718 }
719
720 // Warning: direct manipulation of MXF structures can interfere
721 // with the normal operation of the wrapper.  Caveat emptor!
722 //
723 ASDCP::MXF::OPAtomIndexFooter&
724 ASDCP::TimedText::MXFWriter::OPAtomIndexFooter()
725 {
726   if ( m_Writer.empty() )
727     {
728       assert(g_OPAtomIndexFooter);
729       return *g_OPAtomIndexFooter;
730     }
731
732   return m_Writer->m_FooterPart;
733 }
734
735 // Warning: direct manipulation of MXF structures can interfere
736 // with the normal operation of the wrapper.  Caveat emptor!
737 //
738 ASDCP::MXF::RIP&
739 ASDCP::TimedText::MXFWriter::RIP()
740 {
741   if ( m_Writer.empty() )
742     {
743       assert(g_RIP);
744       return *g_RIP;
745     }
746
747   return m_Writer->m_RIP;
748 }
749
750 // Open the file for writing. The file must not exist. Returns error if
751 // the operation cannot be completed.
752 ASDCP::Result_t
753 ASDCP::TimedText::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info,
754                                        const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
755 {
756   if ( Info.LabelSetType != LS_MXF_SMPTE )
757     {
758       DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
759       return RESULT_FORMAT;
760     }
761
762   m_Writer = new h__Writer(DefaultSMPTEDict());
763   m_Writer->m_Info = Info;
764   
765   Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
766
767   if ( ASDCP_SUCCESS(result) )
768     result = m_Writer->SetSourceStream(TDesc);
769
770   if ( ASDCP_FAILURE(result) )
771     m_Writer.release();
772
773   return result;
774 }
775
776 //
777 ASDCP::Result_t
778 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
779 {
780   if ( m_Writer.empty() )
781     return RESULT_INIT;
782
783   return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
784 }
785
786 //
787 ASDCP::Result_t
788 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
789 {
790   if ( m_Writer.empty() )
791     return RESULT_INIT;
792
793   return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
794 }
795
796 // Closes the MXF file, writing the index and other closing information.
797 ASDCP::Result_t
798 ASDCP::TimedText::MXFWriter::Finalize()
799 {
800   if ( m_Writer.empty() )
801     return RESULT_INIT;
802
803   return m_Writer->Finalize();
804 }
805
806
807
808 //
809 // end AS_DCP_timedText.cpp
810 //