ISXD CLI touchups, ISXDDataEssenceDescriptor.NamespaceURI is now correct
[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 ( unsigned 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         ContainerDuration = m_WaveAudioDescriptor->ContainerDuration;
375       }
376     else
377       {
378         DefaultLogSink().Error("Audio descriptor not found.\n");
379       }
380
381     std::list<InterchangeObject*> object_list;
382     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_AudioChannelLabelSubDescriptor), object_list);
383     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_SoundfieldGroupLabelSubDescriptor), object_list);
384     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor), object_list);
385
386     std::list<InterchangeObject*>::iterator i = object_list.begin();
387     for ( ; i != object_list.end(); ++i )
388       {
389         MCALabelSubDescriptor *p = dynamic_cast<MCALabelSubDescriptor*>(*i);
390
391         if ( p )
392           {
393             m_ChannelDescriptorList.push_back(p);
394           }
395         else
396           {
397             char buf[64];
398             DefaultLogSink().Error("Audio sub-descriptor type error.\n", (**i).InstanceUID.EncodeHex(buf, 64));
399           }
400       }
401
402     object_list.clear();
403     Reader.OP1aHeader().GetMDObjectsByType(DefaultCompositeDict().ul(MDD_Track), object_list);
404     
405     if ( object_list.empty() )
406       {
407         DefaultLogSink().Error("MXF Metadata contains no Track Sets.\n");
408       }
409
410     EditRate = ((Track*)object_list.front())->EditRate;
411   }
412
413   void MyDump(FILE* stream) {
414     if ( stream == 0 )
415       {
416         stream = stderr;
417       }
418
419     if ( m_WaveAudioDescriptor != 0 )
420       {
421         m_WaveAudioDescriptor->Dump(stream);
422       }
423
424     if ( ! m_ChannelDescriptorList.empty() )
425       {
426         fprintf(stream, "Audio Channel Subdescriptors:\n");
427
428         std::list<MCALabelSubDescriptor*>::const_iterator i = m_ChannelDescriptorList.begin();
429         for ( ; i != m_ChannelDescriptorList.end(); ++i )
430           {
431             (**i).Dump(stream);
432           }
433       }
434   }
435 };
436
437 class MyTextDescriptor : public TimedText::TimedTextDescriptor
438 {
439  public:
440   void FillDescriptor(TimedText::MXFReader& Reader) {
441     Reader.FillTimedTextDescriptor(*this);
442   }
443
444   void Dump(FILE* stream) {
445     TimedText::DescriptorDump(*this, stream);
446   }
447 };
448
449 struct RateInfo
450 {
451   UL ul;
452   double bitrate;
453   std::string label;
454
455   RateInfo(const UL& u, const double& b, const std::string& l) {
456     ul = u; bitrate = b; label = l;
457   }
458
459 };
460
461 static const double dci_max_bitrate = 250.0;
462 static const double p_hfr_max_bitrate = 400.0;
463 typedef std::map<const UL, const RateInfo> rate_info_map;
464 static rate_info_map g_rate_info;
465
466 //
467 void
468 init_rate_info()
469 {
470   UL rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_1);
471   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 200.0, "ISO/IEC 15444-1 Amendment 3 Level 1")));
472
473   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_2);
474   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 200.0, "ISO/IEC 15444-1 Amendment 3 Level 2")));
475
476   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_3);
477   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 200.0, "ISO/IEC 15444-1 Amendment 3 Level 3")));
478
479   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_4);
480   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 400.0, "ISO/IEC 15444-1 Amendment 3 Level 4")));
481
482   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_5);
483   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 800.0, "ISO/IEC 15444-1 Amendment 3 Level 5")));
484
485   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_6);
486   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, 1600.0, "ISO/IEC 15444-1 Amendment 3 Level 6")));
487
488   rate_ul = DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_BroadcastProfile_7);
489   g_rate_info.insert(rate_info_map::value_type(rate_ul, RateInfo(rate_ul, DBL_MAX, "ISO/IEC 15444-1 Amendment 3 Level 7")));
490 }
491
492
493 //
494 //
495 template<class ReaderT, class DescriptorT>
496 class FileInfoWrapper
497 {
498   ReaderT  m_Reader;
499   DescriptorT m_Desc;
500   WriterInfo m_WriterInfo;
501   double m_MaxBitrate, m_AvgBitrate;
502   UL m_PictureEssenceCoding;
503
504   KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
505
506   template <class T>
507   Result_t OpenRead(const T& m, const CommandOptions& Options)
508   {
509         return m.OpenRead(Options.filenames.front().c_str());
510   };
511   Result_t OpenRead(const AS_02::PCM::MXFReader& m, const CommandOptions& Options)
512   {
513         return m.OpenRead(Options.filenames.front().c_str(), EditRate_24);
514         //Result_t OpenRead(const std::string& filename, const ASDCP::Rational& EditRate);
515   };
516
517 public:
518   FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
519   virtual ~FileInfoWrapper() {}
520
521   Result_t
522   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
523   {
524     assert(type_string);
525     if ( stream == 0 )
526       {
527         stream = stdout;
528       }
529
530     Result_t result = RESULT_OK;
531     result = OpenRead(m_Reader, Options);
532
533     if ( ASDCP_SUCCESS(result) )
534       {
535         m_Desc.FillDescriptor(m_Reader);
536         m_Reader.FillWriterInfo(m_WriterInfo);
537
538         fprintf(stdout, "%s file essence type is %s, (%d edit unit%s).\n",
539                 ( m_WriterInfo.LabelSetType == LS_MXF_SMPTE ? "SMPTE 2067-5" : "Unknown" ),
540                 type_string,
541                 (m_Desc.ContainerDuration != 0 ? m_Desc.ContainerDuration : m_Reader.AS02IndexReader().GetDuration()),
542                 (m_Desc.ContainerDuration == (ui64_t)1 ? "":"s"));
543
544         if ( Options.showheader_flag )
545           {
546             m_Reader.DumpHeaderMetadata(stream);
547           }
548
549         if ( Options.showid_flag )
550           {
551             WriterInfoDump(m_WriterInfo, stream);
552           }
553
554         if ( Options.showdescriptor_flag )
555           {
556             m_Desc.MyDump(stream);
557           }
558
559         if ( Options.showindex_flag )
560           {
561             m_Reader.DumpIndex(stream);
562           }
563       }
564     else if ( result == RESULT_FORMAT && Options.showheader_flag )
565       {
566         m_Reader.DumpHeaderMetadata(stream);
567       }
568
569     return result;
570   }
571
572   //
573   void get_PictureEssenceCoding(FILE* stream = 0)
574   {
575     const Dictionary& Dict = DefaultCompositeDict();
576     MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
577     MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
578
579     Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
580                                                               reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
581
582     if ( KM_SUCCESS(result) && rgba_descriptor)
583       m_PictureEssenceCoding = rgba_descriptor->PictureEssenceCoding;
584     else{
585         result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
586                                                                       reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
587         if ( KM_SUCCESS(result) && cdci_descriptor)
588           m_PictureEssenceCoding = cdci_descriptor->PictureEssenceCoding;
589     }
590   }
591
592
593   //
594   void dump_PictureEssenceCoding(FILE* stream = 0)
595   {
596     char buf[64];
597
598     if ( m_PictureEssenceCoding.HasValue() )
599       {
600         std::string encoding_ul_type = "**UNKNOWN**";
601
602         rate_info_map::const_iterator rate_i = g_rate_info.find(m_PictureEssenceCoding);
603         if ( rate_i == g_rate_info.end() )
604           {
605             fprintf(stderr, "Unknown PictureEssenceCoding UL: %s\n", m_PictureEssenceCoding.EncodeString(buf, 64));
606           }
607         else
608           {
609             encoding_ul_type = rate_i->second.label;
610           }
611
612         fprintf(stream, "PictureEssenceCoding: %s (%s)\n",
613                 m_PictureEssenceCoding.EncodeString(buf, 64),
614                 encoding_ul_type.c_str());
615       }
616   }
617
618   //
619   Result_t
620   test_rates(CommandOptions& Options, FILE* stream = 0)
621   {
622     double max_bitrate = 0; //Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
623     ui32_t errors = 0;
624     char buf[64];
625
626     rate_info_map::const_iterator rate_i = g_rate_info.find(m_PictureEssenceCoding);
627     if ( rate_i == g_rate_info.end() )
628       {
629         fprintf(stderr, "Unknown PictureEssenceCoding UL: %s\n", m_PictureEssenceCoding.EncodeString(buf, 64));
630       }
631     else
632       {
633         max_bitrate = rate_i->second.bitrate;
634       }
635
636     max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : max_bitrate;
637
638     if ( m_MaxBitrate > max_bitrate )
639       {
640         fprintf(stream, "Bitrate %0.0f Mb/s exceeds maximum %0.0f Mb/s\n", m_MaxBitrate, max_bitrate);
641         ++errors;
642       }
643
644     return errors ? RESULT_FAIL : RESULT_OK;
645   }
646
647   //
648   void
649   calc_Bitrate(FILE* stream = 0)
650   {
651     //MXF::OP1aHeader& footer = m_Reader.OP1aHeader();
652     AS_02::MXF::AS02IndexReader& footer = m_Reader.AS02IndexReader();
653     ui64_t total_frame_bytes = 0, last_stream_offset = 0;
654     ui32_t largest_frame = 0;
655     Result_t result = RESULT_OK;
656     ui64_t duration = 0;
657
658     if ( m_Desc.EditRate.Numerator == 0 || m_Desc.EditRate.Denominator == 0 )
659       {
660         fprintf(stderr, "Broken edit rate, unable to calculate essence bitrate.\n");
661         return;
662       }
663
664     duration = m_Desc.ContainerDuration;
665     if ( duration == 0 )
666       {
667         fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
668         duration = m_Reader.AS02IndexReader().GetDuration();
669       }
670
671     for ( ui32_t i = 0; KM_SUCCESS(result) && i < duration; ++i )
672       {
673         MXF::IndexTableSegment::IndexEntry entry;
674         result = footer.Lookup(i, entry);
675
676         if ( KM_SUCCESS(result) )
677           {
678             if ( last_stream_offset != 0 )
679               {
680                 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset - 20; // do not count the bytes that represent the KLV wrapping
681                 total_frame_bytes += this_frame_size;
682
683                 if ( this_frame_size > largest_frame )
684                   largest_frame = (ui32_t)this_frame_size;
685               }
686
687             last_stream_offset = entry.StreamOffset;
688           }
689       }
690
691     if ( KM_SUCCESS(result) )
692       {
693         // scale bytes to megabits
694         static const double mega_const = 1.0 / ( 1000000 / 8.0 );
695
696         // we did not accumulate the last, so duration -= 1
697         double avg_bytes_frame = (double)(total_frame_bytes / ( duration - 1 ));
698
699         m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
700         m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
701       }
702   }
703
704   //
705   void
706   dump_Bitrate(FILE* stream = 0)
707   {
708     fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
709     fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
710   }
711
712   //
713   void dump_WaveAudioDescriptor(FILE* stream = 0)
714   {
715     const Dictionary& Dict = DefaultCompositeDict();
716     MXF::WaveAudioDescriptor *descriptor = 0;
717
718     Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
719                                                               reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
720
721     if ( KM_SUCCESS(result) )
722       {
723         char buf[64];
724         fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
725       }
726   }
727
728 };
729
730
731 // Read header metadata from an ASDCP file
732 //
733 Result_t
734 show_file_info(CommandOptions& Options)
735 {
736   EssenceType_t EssenceType;
737   Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
738
739   if ( ASDCP_FAILURE(result) )
740     return result;
741
742   if ( EssenceType == ESS_AS02_JPEG_2000 )
743     {
744           FileInfoWrapper<AS_02::JP2K::MXFReader, MyPictureDescriptor> wrapper;
745           result = wrapper.file_info(Options, "JPEG 2000 pictures");
746
747           if ( KM_SUCCESS(result) )
748             {
749               wrapper.get_PictureEssenceCoding();
750               wrapper.calc_Bitrate(stdout);
751
752               if ( Options.showcoding_flag )
753                 {
754                   wrapper.dump_PictureEssenceCoding(stdout);
755                 }
756
757               if ( Options.showrate_flag )
758                 {
759                   wrapper.dump_Bitrate(stdout);
760                 }
761
762               result = wrapper.test_rates(Options, stdout);
763             }
764     }
765
766   else if ( EssenceType == ESS_AS02_PCM_24b_48k || EssenceType == ESS_AS02_PCM_24b_96k )
767     {
768       FileInfoWrapper<AS_02::PCM::MXFReader, MyAudioDescriptor> wrapper;
769       result = wrapper.file_info(Options, "PCM audio");
770
771       if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
772         wrapper.dump_WaveAudioDescriptor(stdout);
773     }
774   else
775     {
776       fprintf(stderr, "Unknown/unsupported essence type: %s\n", Options.filenames.front().c_str());
777       Kumu::FileReader   Reader;
778       const Dictionary* Dict = &DefaultCompositeDict();
779       MXF::OP1aHeader TestHeader(Dict);
780
781       result = Reader.OpenRead(Options.filenames.front().c_str());
782
783       if ( ASDCP_SUCCESS(result) )
784         result = TestHeader.InitFromFile(Reader); // test UL and OP
785
786       if ( ASDCP_SUCCESS(result) )
787         {
788           TestHeader.Partition::Dump(stdout);
789
790           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
791             ID->Dump(stdout);
792           else
793             fputs("File contains no Identification object.\n", stdout);
794
795           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
796             SP->Dump(stdout);
797           else
798             fputs("File contains no SourcePackage object.\n", stdout);
799         }
800       else
801         {
802           fputs("File is not MXF.\n", stdout);
803         }
804     }
805
806   return result;
807 }
808
809 //
810 int
811 main(int argc, const char** argv)
812 {
813   Result_t result = RESULT_OK;
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 //