7c68c41890c5946ffbd449bf66a8c809c4da2af3
[asdcplib.git] / src / as-02-info.cpp
1 /*
2 Copyright (c) 2003-2016, John Hurst, Wolfgang Ruppel
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-02-info.cpp
28     \version $Id$
29     \brief   AS-02 file metadata utility
30
31   This program provides metadata information about an AS-02 file.
32
33   For more information about asdcplib, please refer to the header file AS_DCP.h
34 */
35
36 #include <KM_fileio.h>
37 #include <KM_log.h>
38 #include <AS_DCP.h>
39 #include <AS_02.h>
40 #include <JP2K.h>
41 #include <MXF.h>
42 #include <Metadata.h>
43 #include <cfloat>
44
45 using namespace Kumu;
46 using namespace ASDCP;
47
48 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
49
50 //------------------------------------------------------------------------------------------
51 //
52 // command line option parser class
53
54 static const char* PROGRAM_NAME = "as-02-info";  // program name for messages
55
56
57 // Increment the iterator, test for an additional non-option command line argument.
58 // Causes the caller to return if there are no remaining arguments or if the next
59 // argument begins with '-'.
60 #define TEST_EXTRA_ARG(i,c)                                             \
61   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
62     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
63     return;                                                             \
64   }
65
66 //
67 void
68 banner(FILE* stream = stdout)
69 {
70   fprintf(stream, "\n\
71 %s (asdcplib %s)\n\n\
72 Copyright (c) 2003-2015 John Hurst\n\n\
73 asdcplib may be copied only under the terms of the license found at\n\
74 the top of every file in the asdcplib distribution kit.\n\n\
75 Specify the -h (help) option for further information about %s\n\n",
76           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
77 }
78
79 //
80 void
81 usage(FILE* stream = stdout)
82 {
83   fprintf(stream, "\
84 USAGE:%s [-h|-help] [-V]\n\
85 \n\
86        %s [options] <input-file>+\n\
87 \n\
88 Options:\n\
89   -c          - Show essence coding UL\n\
90   -d          - Show essence descriptor info\n\
91   -h | -help  - Show help\n\
92   -H          - Show MXF header metadata\n\
93   -i          - Show identity info\n\
94   -n          - Show index\n\
95   -r          - Show bit-rate (Mb/s)\n\
96   -t <int>    - Set high-bitrate threshold (Mb/s)\n\
97   -V          - Show version information\n\
98 \n\
99   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
100          o All option arguments must be separated from the option by whitespace.\n\n",
101           PROGRAM_NAME, PROGRAM_NAME);
102
103 }
104
105 //
106 class CommandOptions
107 {
108   CommandOptions();
109
110 public:
111   bool   error_flag;     // true if the given options are in error or not complete
112   bool   version_flag;   // true if the version display option was selected
113   bool   help_flag;      // true if the help display option was selected
114   bool   verbose_flag;   // true if the verbose option was selected
115   PathList_t filenames;  // list of filenames to be processed
116   bool   showindex_flag; // true if index is to be displayed
117   bool   showheader_flag; // true if MXF file header is to be displayed
118   bool   showid_flag;          // if true, show file identity info (the WriterInfo struct)
119   bool   showdescriptor_flag;  // if true, show the essence descriptor
120   bool   showcoding_flag;      // if true, show the coding UL
121   bool   showrate_flag;        // if true and is image file, show bit rate
122   bool   max_bitrate_flag;     // true if -t option given
123   double max_bitrate;          // if true and is image file, max bit rate for rate test
124
125   //
126   CommandOptions(int argc, const char** argv) :
127     error_flag(true), version_flag(false), help_flag(false), verbose_flag(false),
128     showindex_flag(false), showheader_flag(false),
129     showid_flag(false), showdescriptor_flag(false), showcoding_flag(false),
130     showrate_flag(false), max_bitrate_flag(false), max_bitrate(0.0)
131   {
132     for ( int i = 1; i < argc; ++i )
133       {
134
135         if ( (strcmp( argv[i], "-help") == 0) )
136           {
137             help_flag = true;
138             continue;
139           }
140
141         if ( argv[i][0] == '-'
142              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
143              && argv[i][2] == 0 )
144           {
145             switch ( argv[i][1] )
146               {
147               case 'c': showcoding_flag = true; break;
148               case 'd': showdescriptor_flag = true; break;
149               case 'H': showheader_flag = true; break;
150               case 'h': help_flag = true; break;
151               case 'i': showid_flag = true; break;
152               case 'n': showindex_flag = true; break;
153               case 'r': showrate_flag = true; break;
154
155               case 't':
156                 TEST_EXTRA_ARG(i, 't');
157                 max_bitrate = Kumu::xabs(strtol(argv[i], 0, 10));
158                 max_bitrate_flag = true;
159                 break;
160
161               case 'V': version_flag = true; break;
162               case 'v': verbose_flag = true; break;
163
164               default:
165                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
166                 return;
167               }
168           }
169         else
170           {
171             if ( argv[i][0] != '-' )
172               {
173                 filenames.push_back(argv[i]);
174               }
175             else
176               {
177                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
178                 return;
179               }
180           }
181       }
182
183     if ( help_flag || version_flag )
184       return;
185
186     if ( filenames.empty() )
187       {
188         fputs("At least one filename argument is required.\n", stderr);
189         return;
190       }
191
192     error_flag = false;
193   }
194 };
195
196 //------------------------------------------------------------------------------------------
197 //
198
199 //
200 // These classes wrap the irregular names in the asdcplib API
201 // so that I can use a template to simplify the implementation
202 // of show_file_info()
203
204 static int s_exp_lookup[16] = { 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,2048, 4096, 8192, 16384, 32768 };
205
206 using namespace ASDCP::MXF;
207
208 template <class ReaderType, class DescriptorType>
209 DescriptorType *get_descriptor_by_type(ReaderType& reader, const UL& type_ul)
210 {
211   InterchangeObject *obj = 0;
212   reader.OP1aHeader().GetMDObjectByType(type_ul.Value(), &obj);
213   return dynamic_cast<DescriptorType*>(obj);
214 }
215
216 class MyPictureDescriptor : public JP2K::PictureDescriptor
217 {
218   RGBAEssenceDescriptor *m_RGBADescriptor;
219   CDCIEssenceDescriptor *m_CDCIDescriptor;
220   JPEG2000PictureSubDescriptor *m_JP2KSubDescriptor;
221
222  public:
223   MyPictureDescriptor() :
224     m_RGBADescriptor(0),
225     m_CDCIDescriptor(0),
226     m_JP2KSubDescriptor(0) {}
227
228   void FillDescriptor(AS_02::JP2K::MXFReader& Reader)
229   {
230     m_CDCIDescriptor = get_descriptor_by_type<AS_02::JP2K::MXFReader, CDCIEssenceDescriptor>
231       (Reader, DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor));
232
233     m_RGBADescriptor = get_descriptor_by_type<AS_02::JP2K::MXFReader, RGBAEssenceDescriptor>
234       (Reader, DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor));
235
236     if ( m_RGBADescriptor != 0 )
237       {
238         SampleRate = m_RGBADescriptor->SampleRate;
239         ContainerDuration = m_RGBADescriptor->ContainerDuration;
240       }
241     else if ( m_CDCIDescriptor != 0 )
242       {
243         SampleRate = m_CDCIDescriptor->SampleRate;
244         ContainerDuration = m_CDCIDescriptor->ContainerDuration;
245       }
246     else
247       {
248         DefaultLogSink().Error("Picture descriptor not found.\n");
249       }
250
251     m_JP2KSubDescriptor = get_descriptor_by_type<AS_02::JP2K::MXFReader, JPEG2000PictureSubDescriptor>
252       (Reader, DefaultCompositeDict().ul(MDD_JPEG2000PictureSubDescriptor));
253
254     if ( m_JP2KSubDescriptor == 0 )
255       {
256         DefaultLogSink().Error("JPEG2000PictureSubDescriptor not found.\n");
257       }
258
259     std::list<InterchangeObject*> ObjectList;
260     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_Track), ObjectList);
261     
262     if ( ObjectList.empty() )
263       {
264         DefaultLogSink().Error("MXF Metadata contains no Track Sets.\n");
265       }
266
267     EditRate = ((Track*)ObjectList.front())->EditRate;
268   }
269
270   void MyDump(FILE* stream) {
271     if ( stream == 0 )
272       {
273         stream = stderr;
274       }
275
276     if ( m_CDCIDescriptor != 0 )
277       {
278         m_CDCIDescriptor->Dump(stream);
279       }
280     else if ( m_RGBADescriptor != 0 )
281       {
282         m_RGBADescriptor->Dump(stream);
283       }
284     else
285       {
286         return;
287       }
288
289     if ( m_JP2KSubDescriptor != 0 )
290       {
291         m_JP2KSubDescriptor->Dump(stream);
292
293         fprintf(stream, "    ImageComponents: (max=%d)\n", JP2K::MaxComponents);
294
295         //
296         ui32_t component_sizing = m_JP2KSubDescriptor->PictureComponentSizing.const_get().Length();
297         JP2K::ImageComponent_t image_components[JP2K::MaxComponents];
298
299         if ( component_sizing == 17 ) // ( 2 * sizeof(ui32_t) ) + 3 components * 3 byte each
300           {
301             memcpy(&image_components,
302                    m_JP2KSubDescriptor->PictureComponentSizing.const_get().RoData() + 8,
303                    component_sizing - 8);
304           }
305         else
306           {
307             DefaultLogSink().Warn("Unexpected PictureComponentSizing size: %u, should be 17.\n", component_sizing);
308           }
309
310         fprintf(stream, "  bits  h-sep v-sep\n");
311
312         for ( int i = 0; i < m_JP2KSubDescriptor->Csize && i < JP2K::MaxComponents; i++ )
313           {
314             fprintf(stream, "  %4d  %5d %5d\n",
315                     image_components[i].Ssize + 1, // See ISO 15444-1, Table A11, for the origin of '+1'
316                     image_components[i].XRsize,
317                     image_components[i].YRsize
318                     );
319           }
320
321         //
322         JP2K::CodingStyleDefault_t coding_style_default;
323
324         memcpy(&coding_style_default,
325                m_JP2KSubDescriptor->CodingStyleDefault.const_get().RoData(),
326                m_JP2KSubDescriptor->CodingStyleDefault.const_get().Length());
327
328         fprintf(stream, "               Scod: %hhu\n", coding_style_default.Scod);
329         fprintf(stream, "   ProgressionOrder: %hhu\n", coding_style_default.SGcod.ProgressionOrder);
330         fprintf(stream, "     NumberOfLayers: %hd\n",
331                 KM_i16_BE(Kumu::cp2i<ui16_t>(coding_style_default.SGcod.NumberOfLayers)));
332     
333         fprintf(stream, " MultiCompTransform: %hhu\n", coding_style_default.SGcod.MultiCompTransform);
334         fprintf(stream, "DecompositionLevels: %hhu\n", coding_style_default.SPcod.DecompositionLevels);
335         fprintf(stream, "     CodeblockWidth: %hhu\n", coding_style_default.SPcod.CodeblockWidth);
336         fprintf(stream, "    CodeblockHeight: %hhu\n", coding_style_default.SPcod.CodeblockHeight);
337         fprintf(stream, "     CodeblockStyle: %hhu\n", coding_style_default.SPcod.CodeblockStyle);
338         fprintf(stream, "     Transformation: %hhu\n", coding_style_default.SPcod.Transformation);
339     
340         ui32_t precinct_set_size = 0;
341
342         for ( int i = 0; coding_style_default.SPcod.PrecinctSize[i] != 0 && i < JP2K::MaxPrecincts; ++i )
343           {
344             ++precinct_set_size;
345           }
346
347         fprintf(stream, "          Precincts: %u\n", precinct_set_size);
348         fprintf(stream, "precinct dimensions:\n");
349
350         for ( int i = 0; i < precinct_set_size && i < JP2K::MaxPrecincts; i++ )
351           fprintf(stream, "    %d: %d x %d\n", i + 1,
352                   s_exp_lookup[coding_style_default.SPcod.PrecinctSize[i]&0x0f],
353                   s_exp_lookup[(coding_style_default.SPcod.PrecinctSize[i]>>4)&0x0f]
354                   );
355       }
356   }
357 };
358
359 class MyAudioDescriptor : public PCM::AudioDescriptor
360 {
361   WaveAudioDescriptor *m_WaveAudioDescriptor;
362   std::list<MCALabelSubDescriptor*> m_ChannelDescriptorList;
363
364  public:
365   MyAudioDescriptor() : m_WaveAudioDescriptor(0) {}
366   void FillDescriptor(AS_02::PCM::MXFReader& Reader)
367   {
368     m_WaveAudioDescriptor = get_descriptor_by_type<AS_02::PCM::MXFReader, WaveAudioDescriptor>
369       (Reader, DefaultCompositeDict().ul(MDD_WaveAudioDescriptor));
370
371     if ( m_WaveAudioDescriptor != 0 )
372       {
373         AudioSamplingRate = m_WaveAudioDescriptor->SampleRate;
374       }
375     else
376       {
377         DefaultLogSink().Error("Audio descriptor not found.\n");
378       }
379
380     std::list<InterchangeObject*> object_list;
381     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_AudioChannelLabelSubDescriptor), object_list);
382     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_SoundfieldGroupLabelSubDescriptor), object_list);
383     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor), object_list);
384
385     std::list<InterchangeObject*>::iterator i = object_list.begin();
386     for ( ; i != object_list.end(); ++i )
387       {
388         MCALabelSubDescriptor *p = dynamic_cast<MCALabelSubDescriptor*>(*i);
389
390         if ( p )
391           {
392             m_ChannelDescriptorList.push_back(p);
393           }
394         else
395           {
396             char buf[64];
397             DefaultLogSink().Error("Audio sub-descriptor type error.\n", (**i).InstanceUID.EncodeHex(buf, 64));
398           }
399       }
400
401     object_list.clear();
402     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_Track), object_list);
403     
404     if ( object_list.empty() )
405       {
406         DefaultLogSink().Error("MXF Metadata contains no Track Sets.\n");
407       }
408
409     EditRate = ((Track*)object_list.front())->EditRate;
410   }
411
412   void MyDump(FILE* stream) {
413     if ( stream == 0 )
414       {
415         stream = stderr;
416       }
417
418     if ( m_WaveAudioDescriptor != 0 )
419       {
420         m_WaveAudioDescriptor->Dump(stream);
421       }
422
423     if ( ! m_ChannelDescriptorList.empty() )
424       {
425         fprintf(stream, "Audio Channel Subdescriptors:\n");
426
427         std::list<MCALabelSubDescriptor*>::const_iterator i = m_ChannelDescriptorList.begin();
428         for ( ; i != m_ChannelDescriptorList.end(); ++i )
429           {
430             (**i).Dump(stream);
431           }
432       }
433   }
434 };
435
436 class MyTextDescriptor : public TimedText::TimedTextDescriptor
437 {
438  public:
439   void FillDescriptor(TimedText::MXFReader& Reader) {
440     Reader.FillTimedTextDescriptor(*this);
441   }
442
443   void Dump(FILE* stream) {
444     TimedText::DescriptorDump(*this, stream);
445   }
446 };
447
448 struct RateInfo
449 {
450   UL ul;
451   double bitrate;
452   std::string label;
453
454   RateInfo(const UL& u, const double& b, const std::string& l) {
455     ul = u; bitrate = b; label = l;
456   }
457
458 };
459
460 static const double dci_max_bitrate = 250.0;
461 static const double p_hfr_max_bitrate = 400.0;
462 typedef std::map<const UL, const RateInfo> rate_info_map;
463 static rate_info_map g_rate_info;
464
465 //
466 void
467 init_rate_info()
468 {
469   UL rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_1);
470   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 200.0, "ISO/IEC 15444-1 Amendment 3 Level 1")));
471
472   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_2);
473   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 200.0, "ISO/IEC 15444-1 Amendment 3 Level 2")));
474
475   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_3);
476   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 200.0, "ISO/IEC 15444-1 Amendment 3 Level 3")));
477
478   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_4);
479   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 400.0, "ISO/IEC 15444-1 Amendment 3 Level 4")));
480
481   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_5);
482   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 800.0, "ISO/IEC 15444-1 Amendment 3 Level 5")));
483
484   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_6);
485   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 1600.0, "ISO/IEC 15444-1 Amendment 3 Level 6")));
486
487   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_7);
488   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, DBL_MAX, "ISO/IEC 15444-1 Amendment 3 Level 7")));
489 }
490
491
492 //
493 //
494 template<class ReaderT, class DescriptorT>
495 class FileInfoWrapper
496 {
497   ReaderT  m_Reader;
498   DescriptorT m_Desc;
499   WriterInfo m_WriterInfo;
500   double m_MaxBitrate, m_AvgBitrate;
501   UL m_PictureEssenceCoding;
502
503   KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
504
505   template <class T>
506   Result_t OpenRead(const T& m, const CommandOptions& Options)
507   {
508         return m.OpenRead(Options.filenames.front().c_str());
509   };
510   Result_t OpenRead(const AS_02::PCM::MXFReader& m, const CommandOptions& Options)
511   {
512         return m.OpenRead(Options.filenames.front().c_str(), EditRate_24);
513         //Result_t OpenRead(const std::string& filename, const ASDCP::Rational& EditRate);
514   };
515
516 public:
517   FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
518   virtual ~FileInfoWrapper() {}
519
520   Result_t
521   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
522   {
523     assert(type_string);
524     if ( stream == 0 )
525       {
526         stream = stdout;
527       }
528
529     Result_t result = RESULT_OK;
530     result = OpenRead(m_Reader, Options);
531
532     if ( ASDCP_SUCCESS(result) )
533       {
534         m_Desc.FillDescriptor(m_Reader);
535         m_Reader.FillWriterInfo(m_WriterInfo);
536
537         fprintf(stdout, "%s file essence type is %s, (%d edit unit%s).\n",
538                 ( m_WriterInfo.LabelSetType == LS_MXF_SMPTE ? "SMPTE 2067-5" : "Unknown" ),
539                 type_string,
540                 (m_Desc.ContainerDuration != 0 ? m_Desc.ContainerDuration : m_Reader.AS02IndexReader().GetDuration()),
541                 (m_Desc.ContainerDuration == 1 ? "":"s"));
542
543         if ( Options.showheader_flag )
544           {
545             m_Reader.DumpHeaderMetadata(stream);
546           }
547
548         if ( Options.showid_flag )
549           {
550             WriterInfoDump(m_WriterInfo, stream);
551           }
552
553         if ( Options.showdescriptor_flag )
554           {
555             m_Desc.MyDump(stream);
556           }
557
558         if ( Options.showindex_flag )
559           {
560             m_Reader.DumpIndex(stream);
561           }
562       }
563     else if ( result == RESULT_FORMAT && Options.showheader_flag )
564       {
565         m_Reader.DumpHeaderMetadata(stream);
566       }
567
568     return result;
569   }
570
571   //
572   void get_PictureEssenceCoding(FILE* stream = 0)
573   {
574     const Dictionary& Dict = DefaultCompositeDict();
575     MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
576     MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
577
578     Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
579                                                               reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
580
581     if ( KM_SUCCESS(result) && rgba_descriptor)
582       m_PictureEssenceCoding = rgba_descriptor->PictureEssenceCoding;
583     else{
584         result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
585                                                                       reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
586         if ( KM_SUCCESS(result) && cdci_descriptor)
587           m_PictureEssenceCoding = cdci_descriptor->PictureEssenceCoding;
588     }
589   }
590
591
592   //
593   void dump_PictureEssenceCoding(FILE* stream = 0)
594   {
595     char buf[64];
596
597     if ( m_PictureEssenceCoding.HasValue() )
598       {
599         std::string encoding_ul_type = "**UNKNOWN**";
600
601         rate_info_map::const_iterator rate_i = g_rate_info.find(m_PictureEssenceCoding);
602         if ( rate_i == g_rate_info.end() )
603           {
604             fprintf(stderr, "Unknown PictureEssenceCoding UL: %s\n", m_PictureEssenceCoding.EncodeString(buf, 64));
605           }
606         else
607           {
608             encoding_ul_type = rate_i->second.label;
609           }
610
611         fprintf(stream, "PictureEssenceCoding: %s (%s)\n",
612                 m_PictureEssenceCoding.EncodeString(buf, 64),
613                 encoding_ul_type.c_str());
614       }
615   }
616
617   //
618   Result_t
619   test_rates(CommandOptions& Options, FILE* stream = 0)
620   {
621     double max_bitrate = 0; //Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
622     ui32_t errors = 0;
623     char buf[64];
624
625     rate_info_map::const_iterator rate_i = g_rate_info.find(m_PictureEssenceCoding);
626     if ( rate_i == g_rate_info.end() )
627       {
628         fprintf(stderr, "Unknown PictureEssenceCoding UL: %s\n", m_PictureEssenceCoding.EncodeString(buf, 64));
629       }
630     else
631       {
632         max_bitrate = rate_i->second.bitrate;
633       }
634
635     max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : max_bitrate;
636
637     if ( m_MaxBitrate > max_bitrate )
638       {
639         fprintf(stream, "Bitrate %0.0f Mb/s exceeds maximum %0.0f Mb/s\n", m_MaxBitrate, max_bitrate);
640         ++errors;
641       }
642
643     return errors ? RESULT_FAIL : RESULT_OK;
644   }
645
646   //
647   void
648   calc_Bitrate(FILE* stream = 0)
649   {
650     //MXF::OP1aHeader& footer = m_Reader.OP1aHeader();
651     AS_02::MXF::AS02IndexReader& footer = m_Reader.AS02IndexReader();
652     ui64_t total_frame_bytes = 0, last_stream_offset = 0;
653     ui32_t largest_frame = 0;
654     Result_t result = RESULT_OK;
655     ui64_t duration = 0;
656
657     if ( m_Desc.EditRate.Numerator == 0 || m_Desc.EditRate.Denominator == 0 )
658       {
659         fprintf(stderr, "Broken edit rate, unable to calculate essence bitrate.\n");
660         return;
661       }
662
663     duration = m_Desc.ContainerDuration;
664     if ( duration == 0 )
665       {
666         fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
667         duration = m_Reader.AS02IndexReader().GetDuration();
668       }
669
670     for ( ui32_t i = 0; KM_SUCCESS(result) && i < duration; ++i )
671       {
672         MXF::IndexTableSegment::IndexEntry entry;
673         result = footer.Lookup(i, entry);
674
675         if ( KM_SUCCESS(result) )
676           {
677             if ( last_stream_offset != 0 )
678               {
679                 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset - 20; // do not count the bytes that represent the KLV wrapping
680                 total_frame_bytes += this_frame_size;
681
682                 if ( this_frame_size > largest_frame )
683                   largest_frame = this_frame_size;
684               }
685
686             last_stream_offset = entry.StreamOffset;
687           }
688       }
689
690     if ( KM_SUCCESS(result) )
691       {
692         // scale bytes to megabits
693         static const double mega_const = 1.0 / ( 1000000 / 8.0 );
694
695         // we did not accumulate the last, so duration -= 1
696         double avg_bytes_frame = total_frame_bytes / ( duration - 1 );
697
698         m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
699         m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
700       }
701   }
702
703   //
704   void
705   dump_Bitrate(FILE* stream = 0)
706   {
707     fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
708     fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
709   }
710
711   //
712   void dump_WaveAudioDescriptor(FILE* stream = 0)
713   {
714     const Dictionary& Dict = DefaultCompositeDict();
715     MXF::WaveAudioDescriptor *descriptor = 0;
716
717     Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
718                                                               reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
719
720     if ( KM_SUCCESS(result) )
721       {
722         char buf[64];
723         fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
724       }
725   }
726
727 };
728
729
730 // Read header metadata from an ASDCP file
731 //
732 Result_t
733 show_file_info(CommandOptions& Options)
734 {
735   EssenceType_t EssenceType;
736   Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
737
738   if ( ASDCP_FAILURE(result) )
739     return result;
740
741   if ( EssenceType == ESS_AS02_JPEG_2000 )
742     {
743           FileInfoWrapper<AS_02::JP2K::MXFReader, MyPictureDescriptor> wrapper;
744           result = wrapper.file_info(Options, "JPEG 2000 pictures");
745
746           if ( KM_SUCCESS(result) )
747             {
748               wrapper.get_PictureEssenceCoding();
749               wrapper.calc_Bitrate(stdout);
750
751               if ( Options.showcoding_flag )
752                 {
753                   wrapper.dump_PictureEssenceCoding(stdout);
754                 }
755
756               if ( Options.showrate_flag )
757                 {
758                   wrapper.dump_Bitrate(stdout);
759                 }
760
761               result = wrapper.test_rates(Options, stdout);
762             }
763     }
764
765   else if ( EssenceType == ESS_AS02_PCM_24b_48k || EssenceType == ESS_AS02_PCM_24b_96k )
766     {
767       FileInfoWrapper<AS_02::PCM::MXFReader, MyAudioDescriptor> wrapper;
768       result = wrapper.file_info(Options, "PCM audio");
769
770       if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
771         wrapper.dump_WaveAudioDescriptor(stdout);
772     }
773   else
774     {
775       fprintf(stderr, "Unknown/unsupported essence type: %s\n", Options.filenames.front().c_str());
776       Kumu::FileReader   Reader;
777       const Dictionary* Dict = &DefaultCompositeDict();
778       MXF::OP1aHeader TestHeader(Dict);
779
780       result = Reader.OpenRead(Options.filenames.front().c_str());
781
782       if ( ASDCP_SUCCESS(result) )
783         result = TestHeader.InitFromFile(Reader); // test UL and OP
784
785       if ( ASDCP_SUCCESS(result) )
786         {
787           TestHeader.Partition::Dump(stdout);
788
789           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
790             ID->Dump(stdout);
791           else
792             fputs("File contains no Identification object.\n", stdout);
793
794           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
795             SP->Dump(stdout);
796           else
797             fputs("File contains no SourcePackage object.\n", stdout);
798         }
799       else
800         {
801           fputs("File is not MXF.\n", stdout);
802         }
803     }
804
805   return result;
806 }
807
808 //
809 int
810 main(int argc, const char** argv)
811 {
812   Result_t result = RESULT_OK;
813   char     str_buf[64];
814   CommandOptions Options(argc, argv);
815
816   if ( Options.version_flag )
817     banner();
818
819   if ( Options.help_flag )
820     usage();
821
822   if ( Options.version_flag || Options.help_flag )
823     return 0;
824
825   if ( Options.error_flag )
826     {
827       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
828       return 3;
829     }
830
831   init_rate_info();
832
833   while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
834     {
835       result = show_file_info(Options);
836       Options.filenames.pop_front();
837     }
838
839   if ( ASDCP_FAILURE(result) )
840     {
841       fputs("Program stopped on error.\n", stderr);
842
843       if ( result != RESULT_FAIL )
844         {
845           fputs(result, stderr);
846           fputc('\n', stderr);
847         }
848
849       return 1;
850     }
851
852   return 0;
853 }
854
855
856 //
857 // end as-02-info.cpp
858 //