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