oops
[asdcplib.git] / src / AS_DCP_TimedText.cpp
index ccb7c3e49d96db35947d50f5c79e0d96be9df362..5299dd1b61fa3d6af2b4dd748c1ce5ed742a2255 100644 (file)
@@ -34,12 +34,50 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "AS_DCP_TimedText.h"
 #include "KM_xml.h"
 
-using namespace Kumu;
-using namespace ASDCP;
+static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 frame wrapping of D-Cinema Timed Text data";
+static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
 
 
 //------------------------------------------------------------------------------------------
 
+const char*
+MIME2str(TimedText::MIMEType_t m)
+{
+  if ( m == TimedText::MT_PNG )
+    return "image/png";
+
+  else if ( m == TimedText::MT_OPENTYPE )
+    return "application/x-opentype";
+
+  return "application/octet-stream";
+}
+
+//
+void
+ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
+{
+  if ( stream == 0 )
+    stream = stderr;
+
+  UUID TmpID(TDesc.AssetID);
+  char buf[64];
+
+  fprintf(stream, "         EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
+  fprintf(stream, "ContainerDuration: %u\n",    TDesc.ContainerDuration);
+  fprintf(stream, "          AssetID: %s\n",    TmpID.EncodeHex(buf, 64));
+  fprintf(stream, "    NamespaceName: %s\n", TDesc.NamespaceName.c_str());
+  fprintf(stream, "    ResourceCount: %lu\n", TDesc.ResourceList.size());
+
+  TimedText::ResourceList_t::const_iterator ri;
+  for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
+    {
+      TmpID.Set((*ri).ResourceID);
+      fprintf(stream, "    %s: %s\n",
+             TmpID.EncodeHex(buf, 64), 
+             MIME2str((*ri).Type));
+    }
+}
+
 //
 void
 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
@@ -49,7 +87,7 @@ ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
 
   UUID TmpID(m_AssetID);
   char buf[64];
-  fprintf(stream, "ID: %s (type %s)\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str());
+  fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
 
   if ( dump_len > 0 )
     Kumu::hexdump(m_Data, dump_len, stream);
@@ -57,22 +95,202 @@ ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
 
 //------------------------------------------------------------------------------------------
 
+typedef std::map<UUID, UUID> ResourceMap_t;
+
 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
 {
-  TimedTextDescriptor*        m_EssenceDescriptor;
+  TimedTextDescriptor*  m_EssenceDescriptor;
+  ResourceMap_t         m_ResourceMap;
 
   ASDCP_NO_COPY_CONSTRUCT(h__Reader);
 
 public:
   TimedTextDescriptor m_TDesc;    
 
-  h__Reader() : m_EssenceDescriptor(0) {}
+  h__Reader() : m_EssenceDescriptor(0) {
+    memset(&m_TDesc.AssetID, 0, UUIDlen);
+  }
+
   Result_t    OpenRead(const char*);
-  Result_t    ReadFrame(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
-  Result_t    ReadFrameGOPStart(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
-  Result_t    MD_to_TimedText_PDesc(TimedText::TimedTextDescriptor& TDesc);
+  Result_t    MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
+  Result_t    ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
+  Result_t    ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
 };
 
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
+{
+  assert(m_EssenceDescriptor);
+  memset(&m_TDesc.AssetID, 0, UUIDlen);
+  MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
+
+  TDesc.EditRate = TDescObj->SampleRate;
+  TDesc.ContainerDuration = TDescObj->ContainerDuration;
+  TDesc.NamespaceName = TDescObj->RootNamespaceName;
+  TDesc.EncodingName = TDescObj->UTFEncoding;
+
+  Batch<UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
+  DCTimedTextResourceDescriptor* DescObject = 0;
+  Result_t result = RESULT_OK;
+
+  for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
+    {
+      result = m_HeaderPart.GetMDObjectByID(*sdi, (InterchangeObject**)&DescObject);
+
+      if ( KM_SUCCESS(result) )
+       {
+         TimedTextResourceDescriptor TmpResource;
+         memcpy(TmpResource.ResourceID, DescObject->ResourcePackageID.Value(), UUIDlen);
+
+         if ( DescObject->ResourceMIMEType.find("font/") != std::string::npos )
+           TmpResource.Type = MT_OPENTYPE;
+
+         else if ( DescObject->ResourceMIMEType.find("image/png") != std::string::npos )
+           TmpResource.Type = MT_PNG;
+
+         else
+           TmpResource.Type = MT_BIN;
+
+         TDesc.ResourceList.push_back(TmpResource);
+         m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->ResourcePackageID, *sdi));
+       }
+      else
+       {
+         DefaultLogSink().Error("Broken sub-descriptor link\n");
+         return RESULT_FORMAT;
+       }
+    }
+
+  return RESULT_OK;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
+{
+  Result_t result = OpenMXFRead(filename);
+  
+  if( ASDCP_SUCCESS(result) )
+    {
+      if ( m_EssenceDescriptor == 0 )
+         m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor), (InterchangeObject**)&m_EssenceDescriptor);
+
+      result = MD_to_TimedText_TDesc(m_TDesc);
+    }
+
+  if( ASDCP_SUCCESS(result) )
+    result = InitMXFIndex();
+
+  if( ASDCP_SUCCESS(result) )
+    result = InitInfo();
+
+  if( ASDCP_SUCCESS(result) )
+    memcpy(m_TDesc.AssetID, m_Info.AssetUUID, UUIDlen);
+
+  return result;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
+                                                             AESDecContext* Ctx, HMACContext* HMAC)
+{
+  if ( ! m_File.IsOpen() )
+    return RESULT_INIT;
+
+  Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_DCTimedTextEssence), Ctx, HMAC);
+
+ if( ASDCP_SUCCESS(result) )
+   {
+     FrameBuf.AssetID(m_TDesc.AssetID);
+     FrameBuf.MIMEType("text/xml");
+   }
+
+ return result;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
+                                                             AESDecContext* Ctx, HMACContext* HMAC)
+{
+  KM_TEST_NULL_L(uuid);
+  UUID RID(uuid);
+
+  ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
+  if ( ri == m_ResourceMap.end() )
+    {
+      char buf[64];
+      DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
+      return RESULT_RANGE;
+    }
+
+  DCTimedTextResourceDescriptor* DescObject = 0;
+  // get the subdescriptor
+  Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, (InterchangeObject**)&DescObject);
+
+  if ( KM_SUCCESS(result) )
+    {
+      Array<RIP::Pair>::const_iterator pi;
+      RIP::Pair TmpPair;
+      ui32_t sequence = 1;
+
+      // Look up the partition start in the RIP using the SID.
+      // Count the sequence length in because this is the sequence
+      // value needed to  complete the HMAC.
+      for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
+       {
+         if ( (*pi).BodySID == DescObject->ResourceSID )
+           {
+             TmpPair = *pi;
+             break;
+           }
+       }
+
+      if ( TmpPair.ByteOffset == 0 )
+       {
+         DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->ResourceSID);
+         return RESULT_FORMAT;
+       }
+
+      if ( KM_SUCCESS(result) )
+       {
+         FrameBuf.AssetID(uuid);
+         FrameBuf.MIMEType(DescObject->ResourceMIMEType);
+
+         // seek tp the start of the partition
+         if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
+           {
+             m_LastPosition = TmpPair.ByteOffset;
+             result = m_File.Seek(TmpPair.ByteOffset);
+           }
+
+         // read the partition header
+         MXF::Partition GSPart;
+         result = GSPart.InitFromFile(m_File);
+
+         if( ASDCP_SUCCESS(result) )
+           {
+             // check the SID
+             if ( DescObject->ResourceSID != GSPart.BodySID )
+               {
+                 char buf[64];
+                 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
+                 return RESULT_FORMAT;
+               }
+
+             // read the essence packet
+             if( ASDCP_SUCCESS(result) )
+               result = ReadEKLVPacket(0, FrameBuf, Dict::ul(MDD_DCTimedTextDescriptor), Ctx, HMAC);
+           }
+       }
+    }
+
+  return result;
+}
+
+
 //------------------------------------------------------------------------------------------
 
 ASDCP::TimedText::MXFReader::MXFReader()
@@ -93,18 +311,6 @@ ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
   return m_Reader->OpenRead(filename);
 }
 
-//
-ASDCP::Result_t
-ASDCP::TimedText::MXFReader::ReadFrame(ui32_t FrameNum, FrameBuffer& FrameBuf,
-                                  AESDecContext* Ctx, HMACContext* HMAC) const
-{
-  if ( m_Reader && m_Reader->m_File.IsOpen() )
-    return m_Reader->ReadFrame(FrameNum, FrameBuf, Ctx, HMAC);
-
-  return RESULT_INIT;
-}
-
-
 // Fill the struct with the values from the file's header.
 // Returns RESULT_INIT if the file is not open.
 ASDCP::Result_t
@@ -119,7 +325,6 @@ ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDes
   return RESULT_INIT;
 }
 
-
 // Fill the struct with the values from the file's header.
 // Returns RESULT_INIT if the file is not open.
 ASDCP::Result_t
@@ -134,6 +339,43 @@ ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
   return RESULT_INIT;
 }
 
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
+{
+  FrameBuffer FrameBuf(2*Kumu::Megabyte);
+
+  Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
+
+  if ( ASDCP_SUCCESS(result) )
+    s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
+
+  return result;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
+                                                  AESDecContext* Ctx, HMACContext* HMAC) const
+{
+  if ( m_Reader && m_Reader->m_File.IsOpen() )
+    return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
+
+  return RESULT_INIT;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
+                                                  AESDecContext* Ctx, HMACContext* HMAC) const
+{
+  if ( m_Reader && m_Reader->m_File.IsOpen() )
+    return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
+
+  return RESULT_INIT;
+}
+
+
 //
 void
 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
@@ -160,10 +402,11 @@ class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
 public:
   TimedTextDescriptor m_TDesc;
   byte_t              m_EssenceUL[SMPTE_UL_LENGTH];
+  ui32_t              m_ResourceSID;
 
   ASDCP_NO_COPY_CONSTRUCT(h__Writer);
 
-  h__Writer() {
+  h__Writer() : m_ResourceSID(10) {
     memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
   }
 
@@ -171,11 +414,174 @@ public:
 
   Result_t OpenWrite(const char*, ui32_t HeaderSize);
   Result_t SetSourceStream(const TimedTextDescriptor&);
-  Result_t WriteFrame(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
+  Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
+  Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
   Result_t Finalize();
-  Result_t TimedText_PDesc_to_MD(TimedText::TimedTextDescriptor& PDesc);
+  Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
 };
 
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
+{
+  assert(m_EssenceDescriptor);
+  MXF::DCTimedTextDescriptor* TDescObj = (MXF::DCTimedTextDescriptor*)m_EssenceDescriptor;
+
+  TDescObj->SampleRate = TDesc.EditRate;
+  TDescObj->ContainerDuration = TDesc.ContainerDuration;
+  TDescObj->RootNamespaceName = TDesc.NamespaceName;
+  TDescObj->UTFEncoding = TDesc.EncodingName;
+
+  return RESULT_OK;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
+{
+  if ( ! m_State.Test_BEGIN() )
+    return RESULT_STATE;
+
+  Result_t result = m_File.OpenWrite(filename);
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      m_HeaderSize = HeaderSize;
+      m_EssenceDescriptor = new DCTimedTextDescriptor();
+      result = m_State.Goto_INIT();
+    }
+
+  return result;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
+{
+  if ( ! m_State.Test_INIT() )
+    return RESULT_STATE;
+
+  m_TDesc = TDesc;
+  ResourceList_t::const_iterator ri;
+  Result_t result = TimedText_TDesc_to_MD(m_TDesc);
+
+  for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
+    {
+      DCTimedTextResourceDescriptor* resourceSubdescriptor = new DCTimedTextResourceDescriptor;
+      GenRandomValue(resourceSubdescriptor->InstanceUID);
+      resourceSubdescriptor->ResourcePackageID.Set((*ri).ResourceID);
+      resourceSubdescriptor->ResourceMIMEType = MIME2str((*ri).Type);
+      resourceSubdescriptor->ResourceSID = m_ResourceSID++;
+      m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
+      m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
+    }
+
+  m_ResourceSID = 10;
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      UMID SourcePackageUMID;
+      SourcePackageUMID.MakeUMID(0x0f, m_TDesc.AssetID);
+
+      InitHeader();
+      AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
+                  UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL, SourcePackageUMID);
+
+      AddEssenceDescriptor(UL(Dict::ul(MDD_DCTimedTextWrapping)));
+
+      result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
+      
+      if ( KM_SUCCESS(result) )
+       result = CreateBodyPart(m_TDesc.EditRate);
+    }
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      memcpy(m_EssenceUL, Dict::ul(MDD_DCTimedTextEssence), SMPTE_UL_LENGTH);
+      m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
+      result = m_State.Goto_READY();
+    }
+
+  return result;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
+                                                              ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
+{
+  Result_t result = m_State.Goto_RUNNING();
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      // TODO: make sure it's XML
+
+      ui32_t str_size = XMLDoc.size();
+      FrameBuffer FrameBuf(str_size);
+      
+      memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
+      FrameBuf.Size(str_size);
+
+      IndexTableSegment::IndexEntry Entry;
+      Entry.StreamOffset = m_StreamOffset;
+      
+      if ( ASDCP_SUCCESS(result) )
+       result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
+
+      if ( ASDCP_SUCCESS(result) )
+       {
+         m_FooterPart.PushIndexEntry(Entry);
+         m_FramesWritten++;
+       }
+    }
+
+  return result;
+}
+
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
+                                                              ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
+{
+  if ( ! m_State.Test_RUNNING() )
+    return RESULT_STATE;
+
+  Kumu::fpos_t here = m_File.Tell();
+
+  // create generic stream partition header
+  MXF::Partition GSPart;
+
+  GSPart.ThisPartition = here;
+  GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
+  GSPart.BodySID = m_ResourceSID;
+  GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
+
+  m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_ResourceSID++, here));
+  GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_DCTimedTextEssence)));
+  UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
+  Result_t result = GSPart.WriteToFile(m_File, TmpUL);
+
+  if ( ASDCP_SUCCESS(result) )
+    result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
+
+ m_FramesWritten++;
+  return result;
+}
+
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
+{
+  if ( ! m_State.Test_RUNNING() )
+    return RESULT_STATE;
+
+  m_FramesWritten = m_TDesc.ContainerDuration;
+  m_State.Goto_FINAL();
+
+  return WriteMXFFooter();
+}
+
 
 //------------------------------------------------------------------------------------------
 
@@ -194,6 +600,12 @@ ASDCP::Result_t
 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
                                       const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
 {
+  if ( Info.LabelSetType != LS_MXF_SMPTE )
+    {
+      DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
+      return RESULT_FORMAT;
+    }
+
   m_Writer = new h__Writer;
   
   Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
@@ -210,18 +622,24 @@ ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& I
   return result;
 }
 
+//
+ASDCP::Result_t
+ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
+{
+  if ( m_Writer.empty() )
+    return RESULT_INIT;
+
+  return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
+}
 
-// Writes a frame of essence to the MXF file. If the optional AESEncContext
-// argument is present, the essence is encrypted prior to writing.
-// Fails if the file is not open, is finalized, or an operating system
-// error occurs.
+//
 ASDCP::Result_t
-ASDCP::TimedText::MXFWriter::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
+ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
 {
   if ( m_Writer.empty() )
     return RESULT_INIT;
 
-  return m_Writer->WriteFrame(FrameBuf, Ctx, HMAC);
+  return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
 }
 
 // Closes the MXF file, writing the index and other closing information.