Added atmos support and new ULs per SMPTE 429-2:2013 - see README for deets.
[asdcplib.git] / src / asdcp-info.cpp
1 /*
2 Copyright (c) 2003-2012, John Hurst
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    asdcp-info.cpp
28     \version $Id$
29     \brief   AS-DCP file metadata utility
30
31   This program provides metadata information about an AS-DCP 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 <AS_DCP.h>
38 #include <MXF.h>
39 #include <Metadata.h>
40
41 using namespace Kumu;
42 using namespace ASDCP;
43
44 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
45
46 const byte_t P_HFR_UL_2K[16] = {
47   0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
48   0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03
49 };
50
51 const byte_t P_HFR_UL_4K[16] = {
52   0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
53   0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x04
54 };
55
56 //------------------------------------------------------------------------------------------
57 //
58 // command line option parser class
59
60 static const char* PROGRAM_NAME = "asdcp-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-2012 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   -3          - Force stereoscopic interpretation of a JP2K file\n\
96   -C          - Do not show essence coding UL\n\
97   -d          - Show essence descriptor info\n\
98   -h | -help  - Show help\n\
99   -H          - Show MXF header metadata\n\
100   -i          - Show identity info\n\
101   -n          - Show index\n\
102   -r          - Show bit-rate (Mb/s)\n\
103   -t <int>    - Set high-bitrate threshold (Mb/s)\n\
104   -V          - Show version information\n\
105 \n\
106   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
107          o All option arguments must be separated from the option by whitespace.\n\n",
108           PROGRAM_NAME, PROGRAM_NAME);
109
110 }
111
112 //
113 class CommandOptions
114 {
115   CommandOptions();
116
117 public:
118   bool   error_flag;     // true if the given options are in error or not complete
119   bool   version_flag;   // true if the version display option was selected
120   bool   help_flag;      // true if the help display option was selected
121   bool   verbose_flag;   // true if the verbose option was selected
122   PathList_t filenames;  // list of filenames to be processed
123   bool   showindex_flag; // true if index is to be displayed
124   bool   showheader_flag; // true if MXF file header is to be displayed
125   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
126   bool   showid_flag;          // if true, show file identity info (the WriterInfo struct)
127   bool   showdescriptor_flag;  // if true, show the essence descriptor
128   bool   showcoding_flag;      // if true, show the coding UL
129   bool   showrate_flag;        // if true and is image file, show bit rate
130   bool   max_bitrate_flag;     // true if -t option given
131   double max_bitrate;          // if true and is image file, max bit rate for rate test
132
133   //
134   CommandOptions(int argc, const char** argv) :
135     error_flag(true), version_flag(false), help_flag(false), verbose_flag(false),
136     showindex_flag(), showheader_flag(), stereo_image_flag(false),
137     showid_flag(false), showdescriptor_flag(false), showcoding_flag(false),
138     showrate_flag(false), max_bitrate_flag(false), max_bitrate(0.0)
139   {
140     for ( int i = 1; i < argc; ++i )
141       {
142
143         if ( (strcmp( argv[i], "-help") == 0) )
144           {
145             help_flag = true;
146             continue;
147           }
148
149         if ( argv[i][0] == '-'
150              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
151              && argv[i][2] == 0 )
152           {
153             switch ( argv[i][1] )
154               {
155               case '3': stereo_image_flag = true; break;
156               case 'c': showcoding_flag = true; break;
157               case 'd': showdescriptor_flag = true; break;
158               case 'H': showheader_flag = true; break;
159               case 'h': help_flag = true; break;
160               case 'i': showid_flag = true; break;
161               case 'n': showindex_flag = true; break;
162               case 'r': showrate_flag = true; break;
163
164               case 't':
165                 TEST_EXTRA_ARG(i, 't');
166                 max_bitrate = abs(atoi(argv[i]));
167                 max_bitrate_flag = true;
168                 break;
169
170               case 'V': version_flag = true; break;
171               case 'v': verbose_flag = true; break;
172
173               default:
174                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
175                 return;
176               }
177           }
178         else
179           {
180             if ( argv[i][0] != '-' )
181               {
182                 filenames.push_back(argv[i]);
183               }
184             else
185               {
186                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
187                 return;
188               }
189           }
190       }
191
192     if ( help_flag || version_flag )
193       return;
194
195     if ( filenames.empty() )
196       {
197         fputs("At least one filename argument is required.\n", stderr);
198         return;
199       }
200
201     error_flag = false;
202   }
203 };
204
205 //------------------------------------------------------------------------------------------
206 //
207
208 //
209 // These classes wrap the irregular names in the asdcplib API
210 // so that I can use a template to simplify the implementation
211 // of show_file_info()
212
213 class MyVideoDescriptor : public MPEG2::VideoDescriptor
214 {
215  public:
216   void FillDescriptor(MPEG2::MXFReader& Reader) {
217     Reader.FillVideoDescriptor(*this);
218   }
219
220   void Dump(FILE* stream) {
221     MPEG2::VideoDescriptorDump(*this, stream);
222   }
223 };
224
225 class MyPictureDescriptor : public JP2K::PictureDescriptor
226 {
227  public:
228   void FillDescriptor(JP2K::MXFReader& Reader) {
229     Reader.FillPictureDescriptor(*this);
230   }
231
232   void Dump(FILE* stream) {
233     JP2K::PictureDescriptorDump(*this, stream);
234   }
235 };
236
237 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
238 {
239  public:
240   void FillDescriptor(JP2K::MXFSReader& Reader) {
241     Reader.FillPictureDescriptor(*this);
242   }
243
244   void Dump(FILE* stream) {
245     JP2K::PictureDescriptorDump(*this, stream);
246   }
247 };
248
249 class MyAudioDescriptor : public PCM::AudioDescriptor
250 {
251  public:
252   void FillDescriptor(PCM::MXFReader& Reader) {
253     Reader.FillAudioDescriptor(*this);
254   }
255
256   void Dump(FILE* stream) {
257     PCM::AudioDescriptorDump(*this, stream);
258   }
259 };
260
261 class MyTextDescriptor : public TimedText::TimedTextDescriptor
262 {
263  public:
264   void FillDescriptor(TimedText::MXFReader& Reader) {
265     Reader.FillTimedTextDescriptor(*this);
266   }
267
268   void Dump(FILE* stream) {
269     TimedText::DescriptorDump(*this, stream);
270   }
271 };
272
273 class MyDCDataDescriptor : public DCData::DCDataDescriptor
274 {
275  public:
276   void FillDescriptor(DCData::MXFReader& Reader) {
277     Reader.FillDCDataDescriptor(*this);
278   }
279
280   void Dump(FILE* stream) {
281       DCData::DCDataDescriptorDump(*this, stream);
282   }
283 };
284
285 class MyAtmosDescriptor : public ATMOS::AtmosDescriptor
286 {
287  public:
288   void FillDescriptor(ATMOS::MXFReader& Reader) {
289     Reader.FillAtmosDescriptor(*this);
290   }
291
292   void Dump(FILE* stream) {
293       ATMOS::AtmosDescriptorDump(*this, stream);
294   }
295 };
296
297 //
298 //
299 template<class ReaderT, class DescriptorT>
300 class FileInfoWrapper
301 {
302   ReaderT  m_Reader;
303   DescriptorT m_Desc;
304   WriterInfo m_WriterInfo;
305   double m_MaxBitrate, m_AvgBitrate;
306   UL m_PictureEssenceCoding;
307
308   KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
309
310 public:
311   FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
312   virtual ~FileInfoWrapper() {}
313
314   Result_t
315   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
316   {
317     assert(type_string);
318     if ( stream == 0 )
319       stream = stdout;
320
321     Result_t result = RESULT_OK;
322     result = m_Reader.OpenRead(Options.filenames.front().c_str());
323
324     if ( ASDCP_SUCCESS(result) )
325       {
326         m_Desc.FillDescriptor(m_Reader);
327         m_Reader.FillWriterInfo(m_WriterInfo);
328
329         fprintf(stdout, "File essence type is %s, (%d edit unit%s).\n",
330                 type_string, m_Desc.ContainerDuration, (m_Desc.ContainerDuration==1?"":"s"));
331
332         if ( Options.showheader_flag )
333           m_Reader.DumpHeaderMetadata(stream);
334
335         if ( Options.showid_flag )
336           WriterInfoDump(m_WriterInfo, stream);
337
338         if ( Options.showdescriptor_flag )
339           m_Desc.Dump(stream);
340
341         if ( Options.showindex_flag )
342           m_Reader.DumpIndex(stream);
343       }
344     else if ( result == RESULT_FORMAT && Options.showheader_flag )
345       {
346         m_Reader.DumpHeaderMetadata(stream);
347       }
348
349     return result;
350   }
351
352   //
353   void get_PictureEssenceCoding(FILE* stream = 0)
354   {
355     const Dictionary& Dict = DefaultCompositeDict();
356     MXF::RGBAEssenceDescriptor *descriptor = 0;
357
358     Result_t result = m_Reader.OPAtomHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
359                                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
360
361     if ( KM_SUCCESS(result) )
362       m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
363   }
364
365
366   //
367   void dump_PictureEssenceCoding(FILE* stream = 0)
368   {
369     char buf[64];
370
371     if ( m_PictureEssenceCoding.HasValue() )
372       {
373         const char *encoding_ul_type = "**UNKNOWN**";
374
375         if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
376           encoding_ul_type = "P-HFR-2K";
377         else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
378           encoding_ul_type = "**P-HFR-4K**";
379         else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
380           encoding_ul_type = "ST-429-4-2K";
381         else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
382           encoding_ul_type = "ST-429-4-4K";
383
384         fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
385       }
386   }
387
388   //
389   Result_t
390   test_rates(CommandOptions& Options, FILE* stream = 0)
391   {
392     static const double dci_max_bitrate = 250.0;
393     static const double p_hfr_max_bitrate = 400.0;
394
395     double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
396     ui32_t errors = 0;
397
398     if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
399       {
400         if ( m_Desc.StoredWidth > 2048 ) // 4k
401           {
402             fprintf(stream, "4k images marked as 2k HFR.\n");
403             ++errors;
404           }
405
406         if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
407           {
408             fprintf(stream, "HFR UL used for fps < 96.\n");
409             ++errors;
410           }
411
412         if ( ! Options.max_bitrate_flag )
413           max_bitrate = p_hfr_max_bitrate;
414       }
415     else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
416       {
417         fprintf(stream, "4k HFR support undefined.\n");
418         ++errors;
419
420         if ( m_Desc.StoredWidth <= 2048 ) // 2k
421           {
422             fprintf(stream, "2k images marked as 4k HFR.\n");
423             ++errors;
424           }
425       }
426     else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
427               && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
428       {
429         fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
430         ++errors;
431       }
432     else
433       {
434         if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
435           {
436             if ( m_Desc.StoredWidth > 2048 ) // 4k
437               {
438                 fprintf(stream, "4k images marked as 2k ST 429-4.\n");
439                 ++errors;
440               }
441           }
442         else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
443           {
444             if ( m_Desc.StoredWidth <= 2048 ) // 2k
445               {
446                 fprintf(stream, "2k images marked as 4k ST 429-4.\n");
447                 ++errors;
448               }
449           }
450       }
451
452     if ( m_MaxBitrate > max_bitrate )
453       {
454         fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
455         ++errors;
456       }
457
458     return errors ? RESULT_FAIL : RESULT_OK;
459   }
460
461   //
462   void
463   calc_Bitrate(FILE* stream = 0)
464   {
465     MXF::OPAtomIndexFooter& footer = m_Reader.OPAtomIndexFooter();
466     ui64_t total_frame_bytes = 0, last_stream_offset = 0;
467     ui32_t largest_frame = 0;
468     Result_t result = RESULT_OK;
469
470     for ( ui32_t i = 0; KM_SUCCESS(result) && i < m_Desc.ContainerDuration; ++i )
471       {
472         MXF::IndexTableSegment::IndexEntry entry;
473         result = footer.Lookup(i, entry);
474
475         if ( KM_SUCCESS(result) )
476           {
477             if ( last_stream_offset != 0 )
478               {
479                 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset;
480                 total_frame_bytes += this_frame_size;
481
482                 if ( this_frame_size > largest_frame )
483                   largest_frame = this_frame_size;
484               }
485
486             last_stream_offset = entry.StreamOffset;
487           }
488       }
489
490     // scale bytes to megabits
491     static const double mega_const = 1 / ( 1024.0 * 1024.0 / 8.0 );
492
493     // we did not accumulate the first or last frame, so duration -= 2
494     double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
495
496     m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
497     m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
498   }
499
500   //
501   void
502   dump_Bitrate(FILE* stream = 0)
503   {
504     fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
505     fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
506   }
507
508   //
509   void dump_WaveAudioDescriptor(FILE* stream = 0)
510   {
511     const Dictionary& Dict = DefaultCompositeDict();
512     MXF::WaveAudioDescriptor *descriptor = 0;
513
514     Result_t result = m_Reader.OPAtomHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
515                                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
516
517     if ( KM_SUCCESS(result) )
518       {
519         char buf[64];
520         fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.EncodeString(buf, 64));
521       }
522   }
523
524 };
525
526
527 // Read header metadata from an ASDCP file
528 //
529 Result_t
530 show_file_info(CommandOptions& Options)
531 {
532   EssenceType_t EssenceType;
533   Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
534
535   if ( ASDCP_FAILURE(result) )
536     return result;
537
538   if ( EssenceType == ESS_MPEG2_VES )
539     {
540       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor> wrapper;
541       result = wrapper.file_info(Options, "MPEG2 video");
542
543       if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
544         wrapper.dump_Bitrate(stdout);
545     }
546   else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
547     {
548       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor> wrapper;
549       result = wrapper.file_info(Options, "PCM audio");
550
551       if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
552         wrapper.dump_WaveAudioDescriptor();
553     }
554   else if ( EssenceType == ESS_JPEG_2000 )
555     {
556       if ( Options.stereo_image_flag )
557         {
558           FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
559           result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
560
561           if ( KM_SUCCESS(result) )
562             {
563               wrapper.get_PictureEssenceCoding();
564               wrapper.calc_Bitrate();
565
566               if ( Options.showcoding_flag )
567                 wrapper.dump_PictureEssenceCoding(stdout);
568
569               if ( Options.showrate_flag )
570                 wrapper.dump_Bitrate(stdout);
571
572               result = wrapper.test_rates(Options, stdout);
573             }
574         }
575       else
576         {
577           FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
578           result = wrapper.file_info(Options, "JPEG 2000 pictures");
579
580           if ( KM_SUCCESS(result) )
581             {
582               wrapper.get_PictureEssenceCoding();
583               wrapper.calc_Bitrate();
584
585               if ( Options.showcoding_flag )
586                 wrapper.dump_PictureEssenceCoding(stdout);
587
588               if ( Options.showrate_flag )
589                 wrapper.dump_Bitrate(stdout);
590
591               result = wrapper.test_rates(Options, stdout);
592             }
593         }
594     }
595   else if ( EssenceType == ESS_JPEG_2000_S )
596     {
597       FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
598       result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
599
600       if ( KM_SUCCESS(result) )
601         {
602           wrapper.get_PictureEssenceCoding();
603           wrapper.calc_Bitrate();
604
605           if ( Options.showcoding_flag )
606             wrapper.dump_PictureEssenceCoding(stdout);
607
608           if ( Options.showrate_flag )
609             wrapper.dump_Bitrate(stdout);
610
611           result = wrapper.test_rates(Options, stdout);
612         }
613     }
614   else if ( EssenceType == ESS_TIMED_TEXT )
615     {
616       FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
617       result = wrapper.file_info(Options, "Timed Text");
618     }
619   else if ( EssenceType == ESS_DCDATA_UNKNOWN )
620     {
621       FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
622       result = wrapper.file_info(Options, "D-Cinema Generic Data");
623     }
624   else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
625     {
626       FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
627       result = wrapper.file_info(Options, "Dolby ATMOS");
628     }
629   else
630     {
631       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames.front().c_str());
632       Kumu::FileReader   Reader;
633       const Dictionary* Dict = &DefaultCompositeDict();
634       MXF::OPAtomHeader TestHeader(Dict);
635
636       result = Reader.OpenRead(Options.filenames.front().c_str());
637
638       if ( ASDCP_SUCCESS(result) )
639         result = TestHeader.InitFromFile(Reader); // test UL and OP
640
641       if ( ASDCP_SUCCESS(result) )
642         {
643           TestHeader.Partition::Dump(stdout);
644
645           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
646             ID->Dump(stdout);
647           else
648             fputs("File contains no Identification object.\n", stdout);
649
650           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
651             SP->Dump(stdout);
652           else
653             fputs("File contains no SourcePackage object.\n", stdout);
654         }
655       else
656         {
657           fputs("File is not MXF.\n", stdout);
658         }
659     }
660
661   return result;
662 }
663
664 //
665 int
666 main(int argc, const char** argv)
667 {
668   Result_t result = RESULT_OK;
669   char     str_buf[64];
670   CommandOptions Options(argc, argv);
671
672   if ( Options.version_flag )
673     banner();
674
675   if ( Options.help_flag )
676     usage();
677
678   if ( Options.version_flag || Options.help_flag )
679     return 0;
680
681   if ( Options.error_flag )
682     {
683       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
684       return 3;
685     }
686
687   while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
688     {
689       result = show_file_info(Options);
690       Options.filenames.pop_front();
691     }
692
693   if ( ASDCP_FAILURE(result) )
694     {
695       fputs("Program stopped on error.\n", stderr);
696
697       if ( result == RESULT_SFORMAT )
698         {
699           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
700         }
701       else if ( result != RESULT_FAIL )
702         {
703           fputs(result, stderr);
704           fputc('\n', stderr);
705         }
706
707       return 1;
708     }
709
710   return 0;
711 }
712
713
714 //
715 // end asdcp-info.cpp
716 //