Implemented J2K desc to/from MD
[asdcplib.git] / src / asdcp-info.cpp
index 4cd755ad8fdab626169753eb30e8336b454b7712..b8876fed903326fb417d3fb478a8f121eddd935b 100755 (executable)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2003-2012, John Hurst
+Copyright (c) 2003-2014, John Hurst
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -25,7 +25,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 /*! \file    asdcp-info.cpp
-    \version $Id$       
+    \version $Id$
     \brief   AS-DCP file metadata utility
 
   This program provides metadata information about an AS-DCP file.
@@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <KM_fileio.h>
 #include <AS_DCP.h>
+#include <AS_02.h>
 #include <MXF.h>
 #include <Metadata.h>
 
@@ -75,7 +76,7 @@ banner(FILE* stream = stdout)
 {
   fprintf(stream, "\n\
 %s (asdcplib %s)\n\n\
-Copyright (c) 2003-2012 John Hurst\n\n\
+Copyright (c) 2003-2015 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",
@@ -92,15 +93,16 @@ USAGE:%s [-h|-help] [-V]\n\
        %s [options] <input-file>+\n\
 \n\
 Options:\n\
-  -3                - Force stereoscopic interpretation of a JP2K file\n\
-  -C                - Do not show essence coding UL\n\
-  -d                - Show essence descriptor info\n\
-  -h | -help        - Show help\n\
-  -H                - Show MXF header metadata\n\
-  -i                - Show identity info\n\
-  -n                - Show index\n\
-  -R                - Do not show bit-rate (Mb/s)\n\
-  -V                - Show version information\n\
+  -3          - Force stereoscopic interpretation of a JP2K file\n\
+  -c          - Show essence coding UL\n\
+  -d          - Show essence descriptor info\n\
+  -h | -help  - Show help\n\
+  -H          - Show MXF header metadata\n\
+  -i          - Show identity info\n\
+  -n          - Show index\n\
+  -r          - Show bit-rate (Mb/s)\n\
+  -t <int>    - Set high-bitrate threshold (Mb/s)\n\
+  -V          - Show version information\n\
 \n\
   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
          o All option arguments must be separated from the option by whitespace.\n\n",
@@ -122,17 +124,19 @@ public:
   bool   showindex_flag; // true if index is to be displayed
   bool   showheader_flag; // true if MXF file header is to be displayed
   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
-  bool   showid_flag;
-  bool   showdescriptor_flag;
-  bool   showcoding_flag;
-  bool   showrate_flag;
+  bool   showid_flag;          // if true, show file identity info (the WriterInfo struct)
+  bool   showdescriptor_flag;  // if true, show the essence descriptor
+  bool   showcoding_flag;      // if true, show the coding UL
+  bool   showrate_flag;        // if true and is image file, show bit rate
+  bool   max_bitrate_flag;     // true if -t option given
+  double max_bitrate;          // if true and is image file, max bit rate for rate test
 
   //
   CommandOptions(int argc, const char** argv) :
     error_flag(true), version_flag(false), help_flag(false), verbose_flag(false),
     showindex_flag(), showheader_flag(), stereo_image_flag(false),
-    showid_flag(false), showdescriptor_flag(false), showcoding_flag(true),
-    showrate_flag(true)
+    showid_flag(false), showdescriptor_flag(false), showcoding_flag(false),
+    showrate_flag(false), max_bitrate_flag(false), max_bitrate(0.0)
   {
     for ( int i = 1; i < argc; ++i )
       {
@@ -142,7 +146,7 @@ public:
            help_flag = true;
            continue;
          }
-         
+
        if ( argv[i][0] == '-'
             && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
             && argv[i][2] == 0 )
@@ -150,13 +154,20 @@ public:
            switch ( argv[i][1] )
              {
              case '3': stereo_image_flag = true; break;
-             case 'C': showcoding_flag = false; break;
+             case 'c': showcoding_flag = true; break;
              case 'd': showdescriptor_flag = true; break;
              case 'H': showheader_flag = true; break;
              case 'h': help_flag = true; break;
              case 'i': showid_flag = true; break;
              case 'n': showindex_flag = true; break;
-             case 'R': showrate_flag = false; break;
+             case 'r': showrate_flag = true; break;
+
+             case 't':
+               TEST_EXTRA_ARG(i, 't');
+               max_bitrate = Kumu::xabs(strtol(argv[i], 0, 10));
+               max_bitrate_flag = true;
+               break;
+
              case 'V': version_flag = true; break;
              case 'v': verbose_flag = true; break;
 
@@ -181,7 +192,7 @@ public:
 
     if ( help_flag || version_flag )
       return;
-    
+
     if ( filenames.empty() )
       {
        fputs("At least one filename argument is required.\n", stderr);
@@ -260,16 +271,45 @@ class MyTextDescriptor : public TimedText::TimedTextDescriptor
   }
 };
 
-// MSVC didn't like the function template, so now it's a static class method
+class MyDCDataDescriptor : public DCData::DCDataDescriptor
+{
+ public:
+  void FillDescriptor(DCData::MXFReader& Reader) {
+    Reader.FillDCDataDescriptor(*this);
+  }
+
+  void Dump(FILE* stream) {
+      DCData::DCDataDescriptorDump(*this, stream);
+  }
+};
+
+class MyAtmosDescriptor : public ATMOS::AtmosDescriptor
+{
+ public:
+  void FillDescriptor(ATMOS::MXFReader& Reader) {
+    Reader.FillAtmosDescriptor(*this);
+  }
+
+  void Dump(FILE* stream) {
+      ATMOS::AtmosDescriptorDump(*this, stream);
+  }
+};
+
+//
+//
 template<class ReaderT, class DescriptorT>
 class FileInfoWrapper
 {
   ReaderT  m_Reader;
   DescriptorT m_Desc;
+  WriterInfo m_WriterInfo;
+  double m_MaxBitrate, m_AvgBitrate;
+  UL m_PictureEssenceCoding;
+
   KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
 
 public:
-  FileInfoWrapper() {}
+  FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
   virtual ~FileInfoWrapper() {}
 
   Result_t
@@ -285,19 +325,17 @@ public:
     if ( ASDCP_SUCCESS(result) )
       {
        m_Desc.FillDescriptor(m_Reader);
+       m_Reader.FillWriterInfo(m_WriterInfo);
 
-       fprintf(stdout, "File essence type is %s, (%d frame%s).\n",
+       fprintf(stdout, "%s file essence type is %s, (%d edit unit%s).\n",
+               ( m_WriterInfo.LabelSetType == LS_MXF_SMPTE ? "SMPTE 429" : LS_MXF_INTEROP ? "Interop" : "Unknown" ),
                type_string, m_Desc.ContainerDuration, (m_Desc.ContainerDuration==1?"":"s"));
 
        if ( Options.showheader_flag )
          m_Reader.DumpHeaderMetadata(stream);
 
        if ( Options.showid_flag )
-         {
-           WriterInfo WI;
-           m_Reader.FillWriterInfo(WI);
-           WriterInfoDump(WI, stream);
-         }
+         WriterInfoDump(m_WriterInfo, stream);
 
        if ( Options.showdescriptor_flag )
          m_Desc.Dump(stream);
@@ -314,35 +352,117 @@ public:
   }
 
   //
-  void dump_PictureEssenceCoding(FILE* stream = 0)
+  void get_PictureEssenceCoding(FILE* stream = 0)
   {
     const Dictionary& Dict = DefaultCompositeDict();
     MXF::RGBAEssenceDescriptor *descriptor = 0;
 
-    Result_t result = m_Reader.OPAtomHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
-                                                               reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
-    
+    Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
+                                                             reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
+
     if ( KM_SUCCESS(result) )
+      m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
+  }
+
+
+  //
+  void dump_PictureEssenceCoding(FILE* stream = 0)
+  {
+    char buf[64];
+
+    if ( m_PictureEssenceCoding.HasValue() )
       {
        const char *encoding_ul_type = "**UNKNOWN**";
 
-       if ( descriptor->PictureEssenceCoding == UL(P_HFR_UL_2K) )
+       if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
          encoding_ul_type = "P-HFR-2K";
-       else if ( descriptor->PictureEssenceCoding == UL(P_HFR_UL_4K) )
+       else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
          encoding_ul_type = "**P-HFR-4K**";
-       else if ( descriptor->PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
+       else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
          encoding_ul_type = "ST-429-4-2K";
-       else if ( descriptor->PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
+       else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
          encoding_ul_type = "ST-429-4-4K";
 
-       char buf[64];
-       fprintf(stream, "PictureEssenceCoding: %s (%s)\n", descriptor->PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
+       fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
       }
   }
 
+  //
+  Result_t
+  test_rates(CommandOptions& Options, FILE* stream = 0)
+  {
+    static const double dci_max_bitrate = 250.0;
+    static const double p_hfr_max_bitrate = 400.0;
+
+    double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
+    ui32_t errors = 0;
+
+    if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
+      {
+       if ( m_Desc.StoredWidth > 2048 ) // 4k
+         {
+           fprintf(stream, "4k images marked as 2k HFR.\n");
+           ++errors;
+         }
+
+       if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
+         {
+           fprintf(stream, "HFR UL used for fps < 96.\n");
+           ++errors;
+         }
+
+       if ( ! Options.max_bitrate_flag )
+         max_bitrate = p_hfr_max_bitrate;
+      }
+    else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
+      {
+       fprintf(stream, "4k HFR support undefined.\n");
+       ++errors;
+
+       if ( m_Desc.StoredWidth <= 2048 ) // 2k
+         {
+           fprintf(stream, "2k images marked as 4k HFR.\n");
+           ++errors;
+         }
+      }
+    else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
+             && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
+      {
+       fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
+       ++errors;
+      }
+    else
+      {
+       if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
+         {
+           if ( m_Desc.StoredWidth > 2048 ) // 4k
+             {
+               fprintf(stream, "4k images marked as 2k ST 429-4.\n");
+               ++errors;
+             }
+         }
+       else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
+         {
+           if ( m_Desc.StoredWidth <= 2048 ) // 2k
+             {
+               fprintf(stream, "2k images marked as 4k ST 429-4.\n");
+               ++errors;
+             }
+         }
+      }
+
+    if ( m_MaxBitrate > max_bitrate )
+      {
+       fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
+       ++errors;
+      }
+
+    return errors ? RESULT_FAIL : RESULT_OK;
+  }
+
   //
   void
-  dump_Bitrate(FILE* stream = 0)
+  calc_Bitrate(FILE* stream = 0)
   {
     MXF::OPAtomIndexFooter& footer = m_Reader.OPAtomIndexFooter();
     ui64_t total_frame_bytes = 0, last_stream_offset = 0;
@@ -358,7 +478,7 @@ public:
          {
            if ( last_stream_offset != 0 )
              {
-               ui64_t this_frame_size = entry.StreamOffset - last_stream_offset;
+               ui64_t this_frame_size = entry.StreamOffset - last_stream_offset - 20; // do not count the bytes that represent the KLV wrapping 
                total_frame_bytes += this_frame_size;
 
                if ( this_frame_size > largest_frame )
@@ -369,13 +489,25 @@ public:
          }
       }
 
-    // we did not test the first or last frame; scale to return bits when the input is bytes
-    static const double mega_const = 1 / ( 1024.0 * 1024.0 / 8.0 );
-    double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
-    double avg_mbits_second = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
+    if ( KM_SUCCESS(result) )
+      {
+       // scale bytes to megabits
+       static const double mega_const = 1.0 / ( 1000000 / 8.0 );
+
+       // we did not accumulate the first or last frame, so duration -= 2
+       double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
 
-    fprintf(stream, "Max BitRate: %0.2f Mb/s\n", largest_frame * mega_const * m_Desc.EditRate.Quotient());
-    fprintf(stream, "Average BitRate: %0.2f Mb/s\n", avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient());
+       m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
+       m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
+      }
+  }
+
+  //
+  void
+  dump_Bitrate(FILE* stream = 0)
+  {
+    fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
+    fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
   }
 
   //
@@ -384,13 +516,13 @@ public:
     const Dictionary& Dict = DefaultCompositeDict();
     MXF::WaveAudioDescriptor *descriptor = 0;
 
-    Result_t result = m_Reader.OPAtomHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
-                                                               reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
-    
+    Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
+                                                             reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
+
     if ( KM_SUCCESS(result) )
       {
        char buf[64];
-       fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.EncodeString(buf, 64));
+       fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
       }
   }
 
@@ -431,46 +563,87 @@ show_file_info(CommandOptions& Options)
          FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
          result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
 
-         if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
-           wrapper.dump_PictureEssenceCoding(stdout);
+         if ( KM_SUCCESS(result) )
+           {
+             wrapper.get_PictureEssenceCoding();
+             wrapper.calc_Bitrate();
 
-         if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
-           wrapper.dump_Bitrate(stdout);
+             if ( Options.showcoding_flag )
+               wrapper.dump_PictureEssenceCoding(stdout);
+
+             if ( Options.showrate_flag )
+               wrapper.dump_Bitrate(stdout);
+
+             result = wrapper.test_rates(Options, stdout);
+           }
        }
       else
        {
          FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
          result = wrapper.file_info(Options, "JPEG 2000 pictures");
 
-         if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
-           wrapper.dump_PictureEssenceCoding(stdout);
+         if ( KM_SUCCESS(result) )
+           {
+             wrapper.get_PictureEssenceCoding();
+             wrapper.calc_Bitrate();
 
-         if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
-           wrapper.dump_Bitrate(stdout);
+             if ( Options.showcoding_flag )
+               wrapper.dump_PictureEssenceCoding(stdout);
+
+             if ( Options.showrate_flag )
+               wrapper.dump_Bitrate(stdout);
+
+             result = wrapper.test_rates(Options, stdout);
+           }
        }
     }
   else if ( EssenceType == ESS_JPEG_2000_S )
     {
       FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
       result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
-      
-      if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
-       wrapper.dump_PictureEssenceCoding(stdout);
 
-      if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
-       wrapper.dump_Bitrate(stdout);
+      if ( KM_SUCCESS(result) )
+       {
+         wrapper.get_PictureEssenceCoding();
+         wrapper.calc_Bitrate();
+
+         if ( Options.showcoding_flag )
+           wrapper.dump_PictureEssenceCoding(stdout);
+
+         if ( Options.showrate_flag )
+           wrapper.dump_Bitrate(stdout);
+
+         result = wrapper.test_rates(Options, stdout);
+       }
     }
   else if ( EssenceType == ESS_TIMED_TEXT )
     {
       FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
       result = wrapper.file_info(Options, "Timed Text");
     }
+  else if ( EssenceType == ESS_DCDATA_UNKNOWN )
+    {
+      FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
+      result = wrapper.file_info(Options, "D-Cinema Generic Data");
+    }
+  else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
+    {
+      FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
+      result = wrapper.file_info(Options, "Dolby ATMOS");
+    }
+  else if ( EssenceType == ESS_AS02_PCM_24b_48k
+           || EssenceType == ESS_AS02_PCM_24b_96k
+           || EssenceType == ESS_AS02_JPEG_2000
+           || EssenceType == ESS_AS02_TIMED_TEXT )
+    {
+      fprintf(stderr, "File is AS-02. Inspection in not supported by this command.\n");
+    }
   else
     {
       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames.front().c_str());
       Kumu::FileReader   Reader;
       const Dictionary* Dict = &DefaultCompositeDict();
-      MXF::OPAtomHeader TestHeader(Dict);
+      MXF::OP1aHeader TestHeader(Dict);
 
       result = Reader.OpenRead(Options.filenames.front().c_str());