o Added optional Generic Partition to IMF Aux Data prototype, used to carry global...
[asdcplib.git] / src / as-02-unwrap.cpp
index 42d48205f292f8fe0a6a3d1697b74da7b0914147..3c100b9a1e4abceaba47f24304ca15d4bb9979a1 100755 (executable)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
+Copyright (c) 2011-2016, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
 John Hurst
 
 All rights reserved.
@@ -69,7 +69,7 @@ banner(FILE* stream = stdout)
 {
   fprintf(stream, "\n\
 %s (asdcplib %s)\n\n\
-Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
+Copyright (c) 2011-2015, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
 asdcplib may be copied only under the terms of the license found at\n\
 the top of every file in the asdcplib distribution kit.\n\n\
 Specify the -h (help) option for further information about %s\n\n",
@@ -86,7 +86,7 @@ USAGE: %s [-h|-help] [-V]\n\
        %s [-1|-2] [-b <buffer-size>] [-d <duration>]\n\
        [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <size>] [-v] [-W]\n\
        [-w] <input-file> [<file-prefix>]\n\n",
-         PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
+         PROGRAM_NAME, PROGRAM_NAME);
 
   fprintf(stream, "\
 Options:\n\
@@ -97,7 +97,9 @@ Options:\n\
   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
                       Defaults to 4,194,304 (4MB)\n\
   -d <duration>     - Number of frames to process, default all\n\
+  -e <extension>    - Extension to use for aux data files. default \"bin\"\n\
   -f <start-frame>  - Starting frame number, default 0\n\
+  -g <filename>     - Extract global metadata to the named file.\n\
   -h | -help        - Show help\n\
   -k <key-string>   - Use key for ciphertext operations\n\
   -m                - verify HMAC values when reading\n\
@@ -144,7 +146,8 @@ public:
   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
   const char* input_filename;
-  std::string prefix_buffer;
+  const char* extension;
+  std::string global_metadata_filename, prefix_buffer;
 
   //
   CommandOptions(int argc, const char** argv) :
@@ -153,7 +156,7 @@ public:
     version_flag(false), help_flag(false), number_width(6),
     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
-    input_filename(0)
+    input_filename(0), extension("bin")
   {
     memset(key_value, 0, KeyLen);
     memset(key_id_value, 0, UUIDlen);
@@ -178,7 +181,7 @@ public:
 
              case 'b':
                TEST_EXTRA_ARG(i, 'b');
-               fb_size = abs(atoi(argv[i]));
+               fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
 
                if ( verbose_flag )
                  fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
@@ -188,12 +191,22 @@ public:
              case 'd':
                TEST_EXTRA_ARG(i, 'd');
                duration_flag = true;
-               duration = abs(atoi(argv[i]));
+               duration = Kumu::xabs(strtol(argv[i], 0, 10));
+               break;
+
+             case 'e':
+               TEST_EXTRA_ARG(i, 'e');
+               extension = argv[i];
                break;
 
              case 'f':
                TEST_EXTRA_ARG(i, 'f');
-               start_frame = abs(atoi(argv[i]));
+               start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
+               break;
+
+             case 'g':
+               TEST_EXTRA_ARG(i, 'g');
+               global_metadata_filename = argv[i];
                break;
 
              case 'h': help_flag = true; break;
@@ -201,12 +214,12 @@ public:
 
              case 'p':
                TEST_EXTRA_ARG(i, 'p');
-               picture_rate = abs(atoi(argv[i]));
+               picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
                break;
 
              case 's':
                TEST_EXTRA_ARG(i, 's');
-               fb_dump_size = abs(atoi(argv[i]));
+               fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
                break;
 
              case 'V': version_flag = true; break;
@@ -215,7 +228,7 @@ public:
 
              case 'w':
                TEST_EXTRA_ARG(i, 'w');
-               number_width = abs(atoi(argv[i]));
+               number_width = Kumu::xabs(strtol(argv[i], 0, 10));
                break;
 
              case 'Z': j2c_pedantic = false; break;
@@ -376,19 +389,34 @@ read_JP2K_file(CommandOptions& Options)
     {
       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
 
-      if ( ASDCP_SUCCESS(result) )
+      char filename[1024];
+      snprintf(filename, 1024, name_format, Options.file_prefix, i);
+
+      if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
+       {
+         printf("Frame %d, %d bytes", i, FrameBuffer.Size());
+
+         if ( ! Options.no_write_flag )
+           {
+             printf(" -> %s", filename);
+           }
+
+         printf("\n");
+       }
+
+      if ( ASDCP_SUCCESS(result)  && ( ! Options.no_write_flag ) )
        {
          Kumu::FileWriter OutFile;
-         char filename[256];
          ui32_t write_count;
-         snprintf(filename, 256, name_format, Options.file_prefix, i);
          result = OutFile.OpenWrite(filename);
 
          if ( ASDCP_SUCCESS(result) )
            result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
 
-         if ( Options.verbose_flag )
-           FrameBuffer.Dump(stderr, Options.fb_dump_size);
+         if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
+           {
+             FrameBuffer.Dump(stderr, Options.fb_dump_size);
+           }
        }
     }
 
@@ -420,43 +448,65 @@ read_PCM_file(CommandOptions& Options)
 
   Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
 
-  if ( ASDCP_SUCCESS(result) )
+  if ( KM_SUCCESS(result) )
     {
       if ( Options.verbose_flag )
        {
          fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
        }
+      
+      ASDCP::MXF::InterchangeObject* tmp_obj = 0;
 
-      result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
-                                                    reinterpret_cast<MXF::InterchangeObject**>(&wave_descriptor));
+      result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), &tmp_obj);
 
       if ( KM_SUCCESS(result) )
        {
-         assert(wave_descriptor);
-         last_frame = wave_descriptor->ContainerDuration;
+         wave_descriptor = dynamic_cast<ASDCP::MXF::WaveAudioDescriptor*>(tmp_obj);
 
+         if ( wave_descriptor == 0 )
+           {
+             fprintf(stderr, "File does not contain an essence descriptor.\n");
+             return RESULT_FAIL;
+           }
+      
          if ( Options.verbose_flag )
            {
              wave_descriptor->Dump();
            }
-       }
-      else
-       {
-         fprintf(stderr, "File does not contain an essence descriptor.\n");
-         last_frame = Reader.AS02IndexReader().GetDuration();
-       }
 
-      if ( last_frame == 0 )
-       {
-         fprintf(stderr, "Unable to determine file duration.\n");
-         return RESULT_FAIL;
-       }
+         if ( wave_descriptor->ContainerDuration.get() == 0 )
+           {
+             fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
+             last_frame = Reader.AS02IndexReader().GetDuration();
+           }
+         else
+           {
+             last_frame = wave_descriptor->ContainerDuration;
+           }
 
-      FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
+         if ( last_frame == 0 )
+           {
+             fprintf(stderr, "ContainerDuration not set in index, attempting to use Duration from SourceClip.\n");
+             result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_SourceClip), &tmp_obj);
+             if ( KM_SUCCESS(result))
+               {
+                 ASDCP::MXF::SourceClip *sourceClip = dynamic_cast<ASDCP::MXF::SourceClip*>(tmp_obj);
+                 if ( ! sourceClip->Duration.empty() )
+                   {
+                     last_frame = sourceClip->Duration;
+                   }
+               }
+           }
 
-      if ( Options.verbose_flag )
-       {
-         wave_descriptor->Dump();
+         if ( last_frame == 0 )
+           {
+             fprintf(stderr, "Unable to determine file duration.\n");
+             return RESULT_FAIL;
+           }
+
+         assert(wave_descriptor);
+         FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
+         last_frame = AS_02::MXF::CalcFramesFromDurationInSamples(last_frame, *wave_descriptor, Options.edit_rate);
        }
     }
 
@@ -522,7 +572,17 @@ read_PCM_file(CommandOptions& Options)
       if ( ASDCP_SUCCESS(result) )
        {
          if ( Options.verbose_flag )
-           FrameBuffer.Dump(stderr, Options.fb_dump_size);
+           {
+             FrameBuffer.FrameNumber(i);
+             FrameBuffer.Dump(stderr, Options.fb_dump_size);
+           }
+
+         if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
+           {
+             fprintf(stderr, "Last frame is incomplete, padding with zeros.\n");
+             // actually, it has already been zeroed for us, we just need to recognize the appropriate size
+             FrameBuffer.Size(FrameBuffer.Capacity());
+           }
 
          result = OutWave.WriteFrame(FrameBuffer);
        }
@@ -532,6 +592,189 @@ read_PCM_file(CommandOptions& Options)
 }
 
 
+//------------------------------------------------------------------------------------------
+// TimedText essence
+
+// Read one or more timed text streams from a plaintext AS-02 file
+//
+Result_t
+read_timed_text_file(CommandOptions& Options)
+{
+  AESDecContext*     Context = 0;
+  HMACContext*       HMAC = 0;
+  AS_02::TimedText::MXFReader     Reader;
+  TimedText::FrameBuffer   FrameBuffer(Options.fb_size);
+  //ASDCP::TimedText::FrameBuffer   FrameBuffer(Options.fb_size);
+  AS_02::TimedText::TimedTextDescriptor TDesc;
+  ASDCP::MXF::TimedTextDescriptor *tt_descriptor = 0;
+
+  Result_t result = Reader.OpenRead(Options.input_filename);
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_TimedTextDescriptor),
+                                                    reinterpret_cast<MXF::InterchangeObject**>(&tt_descriptor));
+    if ( Options.verbose_flag ) {
+       tt_descriptor->Dump();
+    }
+
+
+  if ( ASDCP_FAILURE(result) )
+    return result;
+
+  std::string XMLDoc;
+  std::string out_path = Kumu::PathDirname(Options.file_prefix);
+  ui32_t write_count;
+  char buf[64];
+  TimedText::ResourceList_t::const_iterator ri;
+
+  result = Reader.ReadTimedTextResource(XMLDoc);
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      Reader.FillTimedTextDescriptor(TDesc);
+      FrameBuffer.Capacity(Options.fb_size);
+
+      if ( Options.verbose_flag )
+       TimedText::DescriptorDump(TDesc);
+    }
+
+  if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
+    {
+      Kumu::FileWriter Writer;
+      result = Writer.OpenWrite(Options.file_prefix);
+
+      if ( ASDCP_SUCCESS(result) )
+       result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
+    }
+
+  for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
+    {
+      result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
+
+      if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
+       {
+         Kumu::FileWriter Writer;
+         if (out_path != "") {
+                 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
+         } else {
+                 // Workaround for a bug in Kumu::PathJoin
+                 result = Writer.OpenWrite(Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64));
+         }
+
+         if ( ASDCP_SUCCESS(result) )
+           result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
+
+             if ( Options.verbose_flag )
+               FrameBuffer.Dump(stderr, Options.fb_dump_size);
+       }
+    }
+    }
+  return result;
+}
+
+// Read one or more plaintext DCData bytestreams from a plaintext ASDCP file
+// Read one or more plaintext DCData bytestreams from a ciphertext ASDCP file
+// Read one or more ciphertext DCData byestreams from a ciphertext ASDCP file
+//
+Result_t
+read_aux_data_file(CommandOptions& Options)
+{
+  AESDecContext*     Context = 0;
+  HMACContext*       HMAC = 0;
+  AS_02::AuxData::MXFReader Reader;
+  DCData::FrameBuffer FrameBuffer(Options.fb_size);
+  ui32_t             frame_count = 0;
+
+  ASDCP::FrameBuffer global_metadata;
+  Result_t result = Reader.OpenRead(Options.input_filename, global_metadata);
+
+  if ( ASDCP_SUCCESS(result)
+       && global_metadata.Size()
+       && ! Options.global_metadata_filename.empty() )
+    {
+      ui32_t write_count = 0;
+      Kumu::FileWriter Writer;
+
+      result = Writer.OpenWrite(Options.global_metadata_filename);
+
+      if ( ASDCP_SUCCESS(result) )
+       {
+         result = Writer.Write(global_metadata.RoData(), global_metadata.Size(), &write_count);
+       }
+
+      if ( ASDCP_SUCCESS(result) && global_metadata.Size() != write_count) 
+       {
+         return RESULT_WRITEFAIL;
+       }
+    }
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      frame_count = Reader.AS02IndexReader().GetDuration();
+
+      if ( Options.verbose_flag )
+       {
+         fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
+       }
+    }
+
+  if ( ASDCP_SUCCESS(result) && Options.key_flag )
+    {
+      Context = new AESDecContext;
+      result = Context->InitKey(Options.key_value);
+
+      if ( ASDCP_SUCCESS(result) && Options.read_hmac )
+       {
+         WriterInfo Info;
+         Reader.FillWriterInfo(Info);
+
+         if ( Info.UsesHMAC )
+           {
+             HMAC = new HMACContext;
+             result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
+           }
+         else
+           {
+             fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
+           }
+       }
+    }
+
+  ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
+  if ( last_frame > frame_count )
+    last_frame = frame_count;
+
+  char name_format[64];
+  snprintf(name_format,  64, "%%s%%0%du.%s", Options.number_width, Options.extension);
+
+  for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
+    {
+      result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
+
+      if ( ASDCP_SUCCESS(result) )
+       {
+         if ( ! Options.no_write_flag )
+           {
+         Kumu::FileWriter OutFile;
+         char filename[256];
+         ui32_t write_count;
+         snprintf(filename, 256, name_format, Options.file_prefix, i);
+         result = OutFile.OpenWrite(filename);
+
+         if ( ASDCP_SUCCESS(result) )
+           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+        }
+
+         if ( Options.verbose_flag )
+           FrameBuffer.Dump(stderr, Options.fb_dump_size);
+       }
+    }
+
+  return result;
+}
+
+
 //
 int
 main(int argc, const char** argv)
@@ -561,17 +804,25 @@ main(int argc, const char** argv)
     {
       switch ( EssenceType )
        {
-       case ESS_JPEG_2000:
+       case ESS_AS02_JPEG_2000:
          result = read_JP2K_file(Options);
          break;
 
-       case ESS_PCM_24b_48k:
-       case ESS_PCM_24b_96k:
+       case ESS_AS02_PCM_24b_48k:
+       case ESS_AS02_PCM_24b_96k:
          result = read_PCM_file(Options);
          break;
 
+       case ESS_AS02_TIMED_TEXT:
+         result = read_timed_text_file(Options);
+         break;
+
+        case ESS_DCDATA_UNKNOWN:
+          result = read_aux_data_file(Options);
+          break;
+
        default:
-         fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.input_filename);
+         fprintf(stderr, "%s: Unknown file type, not AS-02 essence.\n", Options.input_filename);
          return 5;
        }
     }
@@ -580,11 +831,7 @@ main(int argc, const char** argv)
     {
       fputs("Program stopped on error.\n", stderr);
 
-      if ( result == RESULT_SFORMAT )
-       {
-         fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
-       }
-      else if ( result != RESULT_FAIL )
+      if ( result != RESULT_FAIL )
        {
          fputs(result, stderr);
          fputc('\n', stderr);