2 Copyright (c) 2003-2014, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
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.
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.
27 /*! \file asdcp-info.cpp
29 \brief AS-DCP file metadata utility
31 This program provides metadata information about an AS-DCP file.
33 For more information about asdcplib, please refer to the header file AS_DCP.h
36 #include <KM_fileio.h>
43 using namespace ASDCP;
45 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
47 const byte_t P_HFR_UL_2K[16] = {
48 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
49 0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03
52 const byte_t P_HFR_UL_4K[16] = {
53 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
54 0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x04
57 //------------------------------------------------------------------------------------------
59 // command line option parser class
61 static const char* PROGRAM_NAME = "asdcp-info"; // program name for messages
64 // Increment the iterator, test for an additional non-option command line argument.
65 // Causes the caller to return if there are no remaining arguments or if the next
66 // argument begins with '-'.
67 #define TEST_EXTRA_ARG(i,c) \
68 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
69 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
75 banner(FILE* stream = stdout)
79 Copyright (c) 2003-2015 John Hurst\n\n\
80 asdcplib may be copied only under the terms of the license found at\n\
81 the top of every file in the asdcplib distribution kit.\n\n\
82 Specify the -h (help) option for further information about %s\n\n",
83 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
88 usage(FILE* stream = stdout)
91 USAGE:%s [-h|-help] [-V]\n\
93 %s [options] <input-file>+\n\
96 -3 - Force stereoscopic interpretation of a JP2K file\n\
97 -c - Show essence coding UL\n\
98 -d - Show essence descriptor info\n\
99 -h | -help - Show help\n\
100 -H - Show MXF header metadata\n\
101 -i - Show identity info\n\
103 -r - Show bit-rate (Mb/s)\n\
104 -t <int> - Set high-bitrate threshold (Mb/s)\n\
105 -V - Show version information\n\
107 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
108 o All option arguments must be separated from the option by whitespace.\n\n",
109 PROGRAM_NAME, PROGRAM_NAME);
119 bool error_flag; // true if the given options are in error or not complete
120 bool version_flag; // true if the version display option was selected
121 bool help_flag; // true if the help display option was selected
122 bool verbose_flag; // true if the verbose option was selected
123 PathList_t filenames; // list of filenames to be processed
124 bool showindex_flag; // true if index is to be displayed
125 bool showheader_flag; // true if MXF file header is to be displayed
126 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
127 bool showid_flag; // if true, show file identity info (the WriterInfo struct)
128 bool showdescriptor_flag; // if true, show the essence descriptor
129 bool showcoding_flag; // if true, show the coding UL
130 bool showrate_flag; // if true and is image file, show bit rate
131 bool max_bitrate_flag; // true if -t option given
132 double max_bitrate; // if true and is image file, max bit rate for rate test
135 CommandOptions(int argc, const char** argv) :
136 error_flag(true), version_flag(false), help_flag(false), verbose_flag(false),
137 showindex_flag(), showheader_flag(), stereo_image_flag(false),
138 showid_flag(false), showdescriptor_flag(false), showcoding_flag(false),
139 showrate_flag(false), max_bitrate_flag(false), max_bitrate(0.0)
141 for ( int i = 1; i < argc; ++i )
144 if ( (strcmp( argv[i], "-help") == 0) )
150 if ( argv[i][0] == '-'
151 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
154 switch ( argv[i][1] )
156 case '3': stereo_image_flag = true; break;
157 case 'c': showcoding_flag = true; break;
158 case 'd': showdescriptor_flag = true; break;
159 case 'H': showheader_flag = true; break;
160 case 'h': help_flag = true; break;
161 case 'i': showid_flag = true; break;
162 case 'n': showindex_flag = true; break;
163 case 'r': showrate_flag = true; break;
166 TEST_EXTRA_ARG(i, 't');
167 max_bitrate = Kumu::xabs(strtol(argv[i], 0, 10));
168 max_bitrate_flag = true;
171 case 'V': version_flag = true; break;
172 case 'v': verbose_flag = true; break;
175 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
181 if ( argv[i][0] != '-' )
183 filenames.push_back(argv[i]);
187 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
193 if ( help_flag || version_flag )
196 if ( filenames.empty() )
198 fputs("At least one filename argument is required.\n", stderr);
206 //------------------------------------------------------------------------------------------
210 // These classes wrap the irregular names in the asdcplib API
211 // so that I can use a template to simplify the implementation
212 // of show_file_info()
214 class MyVideoDescriptor : public MPEG2::VideoDescriptor
217 void FillDescriptor(MPEG2::MXFReader& Reader) {
218 Reader.FillVideoDescriptor(*this);
221 void Dump(FILE* stream) {
222 MPEG2::VideoDescriptorDump(*this, stream);
226 class MyPictureDescriptor : public JP2K::PictureDescriptor
229 void FillDescriptor(JP2K::MXFReader& Reader) {
230 Reader.FillPictureDescriptor(*this);
233 void Dump(FILE* stream) {
234 JP2K::PictureDescriptorDump(*this, stream);
238 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
241 void FillDescriptor(JP2K::MXFSReader& Reader) {
242 Reader.FillPictureDescriptor(*this);
245 void Dump(FILE* stream) {
246 JP2K::PictureDescriptorDump(*this, stream);
250 class MyAudioDescriptor : public PCM::AudioDescriptor
253 void FillDescriptor(PCM::MXFReader& Reader) {
254 Reader.FillAudioDescriptor(*this);
257 void Dump(FILE* stream) {
258 PCM::AudioDescriptorDump(*this, stream);
262 class MyTextDescriptor : public TimedText::TimedTextDescriptor
265 void FillDescriptor(TimedText::MXFReader& Reader) {
266 Reader.FillTimedTextDescriptor(*this);
269 void Dump(FILE* stream) {
270 TimedText::DescriptorDump(*this, stream);
274 class MyDCDataDescriptor : public DCData::DCDataDescriptor
277 void FillDescriptor(DCData::MXFReader& Reader) {
278 Reader.FillDCDataDescriptor(*this);
281 void Dump(FILE* stream) {
282 DCData::DCDataDescriptorDump(*this, stream);
286 class MyAtmosDescriptor : public ATMOS::AtmosDescriptor
289 void FillDescriptor(ATMOS::MXFReader& Reader) {
290 Reader.FillAtmosDescriptor(*this);
293 void Dump(FILE* stream) {
294 ATMOS::AtmosDescriptorDump(*this, stream);
300 template<class ReaderT, class DescriptorT>
301 class FileInfoWrapper
305 WriterInfo m_WriterInfo;
306 double m_MaxBitrate, m_AvgBitrate;
307 UL m_PictureEssenceCoding;
309 KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
312 FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
313 virtual ~FileInfoWrapper() {}
316 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
322 Result_t result = RESULT_OK;
323 result = m_Reader.OpenRead(Options.filenames.front().c_str());
325 if ( ASDCP_SUCCESS(result) )
327 m_Desc.FillDescriptor(m_Reader);
328 m_Reader.FillWriterInfo(m_WriterInfo);
330 fprintf(stdout, "%s file essence type is %s, (%d edit unit%s).\n",
331 ( m_WriterInfo.LabelSetType == LS_MXF_SMPTE ? "SMPTE 429" : LS_MXF_INTEROP ? "Interop" : "Unknown" ),
332 type_string, m_Desc.ContainerDuration, (m_Desc.ContainerDuration==1?"":"s"));
334 if ( Options.showheader_flag )
335 m_Reader.DumpHeaderMetadata(stream);
337 if ( Options.showid_flag )
338 WriterInfoDump(m_WriterInfo, stream);
340 if ( Options.showdescriptor_flag )
343 if ( Options.showindex_flag )
344 m_Reader.DumpIndex(stream);
346 else if ( result == RESULT_FORMAT && Options.showheader_flag )
348 m_Reader.DumpHeaderMetadata(stream);
355 void get_PictureEssenceCoding(FILE* stream = 0)
357 const Dictionary& Dict = DefaultCompositeDict();
358 MXF::RGBAEssenceDescriptor *descriptor = 0;
360 Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
361 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
363 if ( KM_SUCCESS(result) )
364 m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
369 void dump_PictureEssenceCoding(FILE* stream = 0)
373 if ( m_PictureEssenceCoding.HasValue() )
375 const char *encoding_ul_type = "**UNKNOWN**";
377 if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
378 encoding_ul_type = "P-HFR-2K";
379 else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
380 encoding_ul_type = "**P-HFR-4K**";
381 else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
382 encoding_ul_type = "ST-429-4-2K";
383 else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
384 encoding_ul_type = "ST-429-4-4K";
386 fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
392 test_rates(CommandOptions& Options, FILE* stream = 0)
394 static const double dci_max_bitrate = 250.0;
395 static const double p_hfr_max_bitrate = 400.0;
397 double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
400 if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
402 if ( m_Desc.StoredWidth > 2048 ) // 4k
404 fprintf(stream, "4k images marked as 2k HFR.\n");
408 if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
410 fprintf(stream, "HFR UL used for fps < 96.\n");
414 if ( ! Options.max_bitrate_flag )
415 max_bitrate = p_hfr_max_bitrate;
417 else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
419 fprintf(stream, "4k HFR support undefined.\n");
422 if ( m_Desc.StoredWidth <= 2048 ) // 2k
424 fprintf(stream, "2k images marked as 4k HFR.\n");
428 else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
429 && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
431 fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
436 if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
438 if ( m_Desc.StoredWidth > 2048 ) // 4k
440 fprintf(stream, "4k images marked as 2k ST 429-4.\n");
444 else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
446 if ( m_Desc.StoredWidth <= 2048 ) // 2k
448 fprintf(stream, "2k images marked as 4k ST 429-4.\n");
454 if ( m_MaxBitrate > max_bitrate )
456 fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
460 return errors ? RESULT_FAIL : RESULT_OK;
465 calc_Bitrate(FILE* stream = 0)
467 MXF::OPAtomIndexFooter& footer = m_Reader.OPAtomIndexFooter();
468 ui64_t total_frame_bytes = 0, last_stream_offset = 0;
469 ui32_t largest_frame = 0;
470 Result_t result = RESULT_OK;
472 for ( ui32_t i = 0; KM_SUCCESS(result) && i < m_Desc.ContainerDuration; ++i )
474 MXF::IndexTableSegment::IndexEntry entry;
475 result = footer.Lookup(i, entry);
477 if ( KM_SUCCESS(result) )
479 if ( last_stream_offset != 0 )
481 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset - 20; // do not count the bytes that represent the KLV wrapping
482 total_frame_bytes += this_frame_size;
484 if ( this_frame_size > largest_frame )
485 largest_frame = this_frame_size;
488 last_stream_offset = entry.StreamOffset;
492 if ( KM_SUCCESS(result) )
494 // scale bytes to megabits
495 static const double mega_const = 1.0 / ( 1000000 / 8.0 );
497 // we did not accumulate the first or last frame, so duration -= 2
498 double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
500 m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
501 m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
507 dump_Bitrate(FILE* stream = 0)
509 fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
510 fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
514 void dump_WaveAudioDescriptor(FILE* stream = 0)
516 const Dictionary& Dict = DefaultCompositeDict();
517 MXF::WaveAudioDescriptor *descriptor = 0;
519 Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
520 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
522 if ( KM_SUCCESS(result) )
525 fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
532 // Read header metadata from an ASDCP file
535 show_file_info(CommandOptions& Options)
537 EssenceType_t EssenceType;
538 Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
540 if ( ASDCP_FAILURE(result) )
543 if ( EssenceType == ESS_MPEG2_VES )
545 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor> wrapper;
546 result = wrapper.file_info(Options, "MPEG2 video");
548 if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
549 wrapper.dump_Bitrate(stdout);
551 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
553 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor> wrapper;
554 result = wrapper.file_info(Options, "PCM audio");
556 if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
557 wrapper.dump_WaveAudioDescriptor();
559 else if ( EssenceType == ESS_JPEG_2000 )
561 if ( Options.stereo_image_flag )
563 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
564 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
566 if ( KM_SUCCESS(result) )
568 wrapper.get_PictureEssenceCoding();
569 wrapper.calc_Bitrate();
571 if ( Options.showcoding_flag )
572 wrapper.dump_PictureEssenceCoding(stdout);
574 if ( Options.showrate_flag )
575 wrapper.dump_Bitrate(stdout);
577 result = wrapper.test_rates(Options, stdout);
582 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
583 result = wrapper.file_info(Options, "JPEG 2000 pictures");
585 if ( KM_SUCCESS(result) )
587 wrapper.get_PictureEssenceCoding();
588 wrapper.calc_Bitrate();
590 if ( Options.showcoding_flag )
591 wrapper.dump_PictureEssenceCoding(stdout);
593 if ( Options.showrate_flag )
594 wrapper.dump_Bitrate(stdout);
596 result = wrapper.test_rates(Options, stdout);
600 else if ( EssenceType == ESS_JPEG_2000_S )
602 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
603 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
605 if ( KM_SUCCESS(result) )
607 wrapper.get_PictureEssenceCoding();
608 wrapper.calc_Bitrate();
610 if ( Options.showcoding_flag )
611 wrapper.dump_PictureEssenceCoding(stdout);
613 if ( Options.showrate_flag )
614 wrapper.dump_Bitrate(stdout);
616 result = wrapper.test_rates(Options, stdout);
619 else if ( EssenceType == ESS_TIMED_TEXT )
621 FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
622 result = wrapper.file_info(Options, "Timed Text");
624 else if ( EssenceType == ESS_DCDATA_UNKNOWN )
626 FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
627 result = wrapper.file_info(Options, "D-Cinema Generic Data");
629 else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
631 FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
632 result = wrapper.file_info(Options, "Dolby ATMOS");
634 else if ( EssenceType == ESS_AS02_PCM_24b_48k
635 || EssenceType == ESS_AS02_PCM_24b_96k
636 || EssenceType == ESS_AS02_JPEG_2000
637 || EssenceType == ESS_AS02_TIMED_TEXT )
639 fprintf(stderr, "File is AS-02. Inspection in not supported by this command.\n");
643 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames.front().c_str());
644 Kumu::FileReader Reader;
645 const Dictionary* Dict = &DefaultCompositeDict();
646 MXF::OP1aHeader TestHeader(Dict);
648 result = Reader.OpenRead(Options.filenames.front().c_str());
650 if ( ASDCP_SUCCESS(result) )
651 result = TestHeader.InitFromFile(Reader); // test UL and OP
653 if ( ASDCP_SUCCESS(result) )
655 TestHeader.Partition::Dump(stdout);
657 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
660 fputs("File contains no Identification object.\n", stdout);
662 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
665 fputs("File contains no SourcePackage object.\n", stdout);
669 fputs("File is not MXF.\n", stdout);
678 main(int argc, const char** argv)
680 Result_t result = RESULT_OK;
682 CommandOptions Options(argc, argv);
684 if ( Options.version_flag )
687 if ( Options.help_flag )
690 if ( Options.version_flag || Options.help_flag )
693 if ( Options.error_flag )
695 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
699 while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
701 result = show_file_info(Options);
702 Options.filenames.pop_front();
705 if ( ASDCP_FAILURE(result) )
707 fputs("Program stopped on error.\n", stderr);
709 if ( result == RESULT_SFORMAT )
711 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
713 else if ( result != RESULT_FAIL )
715 fputs(result, stderr);
727 // end asdcp-info.cpp