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