removed nascent ST2052-1 support pending completion
[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          - 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, "%s file essence type is %s, (%d edit unit%s).\n",
330                 ( m_WriterInfo.LabelSetType == LS_MXF_SMPTE ? "SMPTE 429" : LS_MXF_INTEROP ? "Interop" : "Unknown" ),
331                 type_string, m_Desc.ContainerDuration, (m_Desc.ContainerDuration==1?"":"s"));
332
333         if ( Options.showheader_flag )
334           m_Reader.DumpHeaderMetadata(stream);
335
336         if ( Options.showid_flag )
337           WriterInfoDump(m_WriterInfo, stream);
338
339         if ( Options.showdescriptor_flag )
340           m_Desc.Dump(stream);
341
342         if ( Options.showindex_flag )
343           m_Reader.DumpIndex(stream);
344       }
345     else if ( result == RESULT_FORMAT && Options.showheader_flag )
346       {
347         m_Reader.DumpHeaderMetadata(stream);
348       }
349
350     return result;
351   }
352
353   //
354   void get_PictureEssenceCoding(FILE* stream = 0)
355   {
356     const Dictionary& Dict = DefaultCompositeDict();
357     MXF::RGBAEssenceDescriptor *descriptor = 0;
358
359     Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
360                                                               reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
361
362     if ( KM_SUCCESS(result) )
363       m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
364   }
365
366
367   //
368   void dump_PictureEssenceCoding(FILE* stream = 0)
369   {
370     char buf[64];
371
372     if ( m_PictureEssenceCoding.HasValue() )
373       {
374         const char *encoding_ul_type = "**UNKNOWN**";
375
376         if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
377           encoding_ul_type = "P-HFR-2K";
378         else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
379           encoding_ul_type = "**P-HFR-4K**";
380         else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
381           encoding_ul_type = "ST-429-4-2K";
382         else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
383           encoding_ul_type = "ST-429-4-4K";
384
385         fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
386       }
387   }
388
389   //
390   Result_t
391   test_rates(CommandOptions& Options, FILE* stream = 0)
392   {
393     static const double dci_max_bitrate = 250.0;
394     static const double p_hfr_max_bitrate = 400.0;
395
396     double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
397     ui32_t errors = 0;
398
399     if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
400       {
401         if ( m_Desc.StoredWidth > 2048 ) // 4k
402           {
403             fprintf(stream, "4k images marked as 2k HFR.\n");
404             ++errors;
405           }
406
407         if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
408           {
409             fprintf(stream, "HFR UL used for fps < 96.\n");
410             ++errors;
411           }
412
413         if ( ! Options.max_bitrate_flag )
414           max_bitrate = p_hfr_max_bitrate;
415       }
416     else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
417       {
418         fprintf(stream, "4k HFR support undefined.\n");
419         ++errors;
420
421         if ( m_Desc.StoredWidth <= 2048 ) // 2k
422           {
423             fprintf(stream, "2k images marked as 4k HFR.\n");
424             ++errors;
425           }
426       }
427     else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
428               && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
429       {
430         fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
431         ++errors;
432       }
433     else
434       {
435         if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
436           {
437             if ( m_Desc.StoredWidth > 2048 ) // 4k
438               {
439                 fprintf(stream, "4k images marked as 2k ST 429-4.\n");
440                 ++errors;
441               }
442           }
443         else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
444           {
445             if ( m_Desc.StoredWidth <= 2048 ) // 2k
446               {
447                 fprintf(stream, "2k images marked as 4k ST 429-4.\n");
448                 ++errors;
449               }
450           }
451       }
452
453     if ( m_MaxBitrate > max_bitrate )
454       {
455         fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
456         ++errors;
457       }
458
459     return errors ? RESULT_FAIL : RESULT_OK;
460   }
461
462   //
463   void
464   calc_Bitrate(FILE* stream = 0)
465   {
466     MXF::OPAtomIndexFooter& footer = m_Reader.OPAtomIndexFooter();
467     ui64_t total_frame_bytes = 0, last_stream_offset = 0;
468     ui32_t largest_frame = 0;
469     Result_t result = RESULT_OK;
470
471     for ( ui32_t i = 0; KM_SUCCESS(result) && i < m_Desc.ContainerDuration; ++i )
472       {
473         MXF::IndexTableSegment::IndexEntry entry;
474         result = footer.Lookup(i, entry);
475
476         if ( KM_SUCCESS(result) )
477           {
478             if ( last_stream_offset != 0 )
479               {
480                 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset;
481                 total_frame_bytes += this_frame_size;
482
483                 if ( this_frame_size > largest_frame )
484                   largest_frame = this_frame_size;
485               }
486
487             last_stream_offset = entry.StreamOffset;
488           }
489       }
490
491     // scale bytes to megabits
492     static const double mega_const = 1.0 / ( 1000000 / 8.0 );
493
494     // we did not accumulate the first or last frame, so duration -= 2
495     double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
496
497     m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
498     m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
499   }
500
501   //
502   void
503   dump_Bitrate(FILE* stream = 0)
504   {
505     fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
506     fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
507   }
508
509   //
510   void dump_WaveAudioDescriptor(FILE* stream = 0)
511   {
512     const Dictionary& Dict = DefaultCompositeDict();
513     MXF::WaveAudioDescriptor *descriptor = 0;
514
515     Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
516                                                               reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
517
518     if ( KM_SUCCESS(result) )
519       {
520         char buf[64];
521         fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
522       }
523   }
524
525 };
526
527
528 // Read header metadata from an ASDCP file
529 //
530 Result_t
531 show_file_info(CommandOptions& Options)
532 {
533   EssenceType_t EssenceType;
534   Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
535
536   if ( ASDCP_FAILURE(result) )
537     return result;
538
539   if ( EssenceType == ESS_MPEG2_VES )
540     {
541       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor> wrapper;
542       result = wrapper.file_info(Options, "MPEG2 video");
543
544       if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
545         wrapper.dump_Bitrate(stdout);
546     }
547   else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
548     {
549       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor> wrapper;
550       result = wrapper.file_info(Options, "PCM audio");
551
552       if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
553         wrapper.dump_WaveAudioDescriptor();
554     }
555   else if ( EssenceType == ESS_JPEG_2000 )
556     {
557       if ( Options.stereo_image_flag )
558         {
559           FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
560           result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
561
562           if ( KM_SUCCESS(result) )
563             {
564               wrapper.get_PictureEssenceCoding();
565               wrapper.calc_Bitrate();
566
567               if ( Options.showcoding_flag )
568                 wrapper.dump_PictureEssenceCoding(stdout);
569
570               if ( Options.showrate_flag )
571                 wrapper.dump_Bitrate(stdout);
572
573               result = wrapper.test_rates(Options, stdout);
574             }
575         }
576       else
577         {
578           FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
579           result = wrapper.file_info(Options, "JPEG 2000 pictures");
580
581           if ( KM_SUCCESS(result) )
582             {
583               wrapper.get_PictureEssenceCoding();
584               wrapper.calc_Bitrate();
585
586               if ( Options.showcoding_flag )
587                 wrapper.dump_PictureEssenceCoding(stdout);
588
589               if ( Options.showrate_flag )
590                 wrapper.dump_Bitrate(stdout);
591
592               result = wrapper.test_rates(Options, stdout);
593             }
594         }
595     }
596   else if ( EssenceType == ESS_JPEG_2000_S )
597     {
598       FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
599       result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
600
601       if ( KM_SUCCESS(result) )
602         {
603           wrapper.get_PictureEssenceCoding();
604           wrapper.calc_Bitrate();
605
606           if ( Options.showcoding_flag )
607             wrapper.dump_PictureEssenceCoding(stdout);
608
609           if ( Options.showrate_flag )
610             wrapper.dump_Bitrate(stdout);
611
612           result = wrapper.test_rates(Options, stdout);
613         }
614     }
615   else if ( EssenceType == ESS_TIMED_TEXT )
616     {
617       FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
618       result = wrapper.file_info(Options, "Timed Text");
619     }
620   else if ( EssenceType == ESS_DCDATA_UNKNOWN )
621     {
622       FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
623       result = wrapper.file_info(Options, "D-Cinema Generic Data");
624     }
625   else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
626     {
627       FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
628       result = wrapper.file_info(Options, "Dolby ATMOS");
629     }
630   else
631     {
632       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames.front().c_str());
633       Kumu::FileReader   Reader;
634       const Dictionary* Dict = &DefaultCompositeDict();
635       MXF::OP1aHeader TestHeader(Dict);
636
637       result = Reader.OpenRead(Options.filenames.front().c_str());
638
639       if ( ASDCP_SUCCESS(result) )
640         result = TestHeader.InitFromFile(Reader); // test UL and OP
641
642       if ( ASDCP_SUCCESS(result) )
643         {
644           TestHeader.Partition::Dump(stdout);
645
646           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
647             ID->Dump(stdout);
648           else
649             fputs("File contains no Identification object.\n", stdout);
650
651           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
652             SP->Dump(stdout);
653           else
654             fputs("File contains no SourcePackage object.\n", stdout);
655         }
656       else
657         {
658           fputs("File is not MXF.\n", stdout);
659         }
660     }
661
662   return result;
663 }
664
665 //
666 int
667 main(int argc, const char** argv)
668 {
669   Result_t result = RESULT_OK;
670   char     str_buf[64];
671   CommandOptions Options(argc, argv);
672
673   if ( Options.version_flag )
674     banner();
675
676   if ( Options.help_flag )
677     usage();
678
679   if ( Options.version_flag || Options.help_flag )
680     return 0;
681
682   if ( Options.error_flag )
683     {
684       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
685       return 3;
686     }
687
688   while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
689     {
690       result = show_file_info(Options);
691       Options.filenames.pop_front();
692     }
693
694   if ( ASDCP_FAILURE(result) )
695     {
696       fputs("Program stopped on error.\n", stderr);
697
698       if ( result == RESULT_SFORMAT )
699         {
700           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
701         }
702       else if ( result != RESULT_FAIL )
703         {
704           fputs(result, stderr);
705           fputc('\n', stderr);
706         }
707
708       return 1;
709     }
710
711   return 0;
712 }
713
714
715 //
716 // end asdcp-info.cpp
717 //