2 Copyright (c) 2003-2012, 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>
42 using namespace ASDCP;
44 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
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
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
56 //------------------------------------------------------------------------------------------
58 // command line option parser class
60 static const char* PROGRAM_NAME = "asdcp-info"; // program name for messages
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)); \
74 banner(FILE* stream = stdout)
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);
87 usage(FILE* stream = stdout)
90 USAGE:%s [-h|-help] [-V]\n\
92 %s [options] <input-file>+\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\
102 -r - Show bit-rate (Mb/s)\n\
103 -t <int> - Set high-bitrate threshold (Mb/s)\n\
104 -V - Show version information\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);
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
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)
140 for ( int i = 1; i < argc; ++i )
143 if ( (strcmp( argv[i], "-help") == 0) )
149 if ( argv[i][0] == '-'
150 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
153 switch ( argv[i][1] )
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;
165 TEST_EXTRA_ARG(i, 't');
166 max_bitrate = abs(atoi(argv[i]));
167 max_bitrate_flag = true;
170 case 'V': version_flag = true; break;
171 case 'v': verbose_flag = true; break;
174 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
180 if ( argv[i][0] != '-' )
182 filenames.push_back(argv[i]);
186 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
192 if ( help_flag || version_flag )
195 if ( filenames.empty() )
197 fputs("At least one filename argument is required.\n", stderr);
205 //------------------------------------------------------------------------------------------
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()
213 class MyVideoDescriptor : public MPEG2::VideoDescriptor
216 void FillDescriptor(MPEG2::MXFReader& Reader) {
217 Reader.FillVideoDescriptor(*this);
220 void Dump(FILE* stream) {
221 MPEG2::VideoDescriptorDump(*this, stream);
225 class MyPictureDescriptor : public JP2K::PictureDescriptor
228 void FillDescriptor(JP2K::MXFReader& Reader) {
229 Reader.FillPictureDescriptor(*this);
232 void Dump(FILE* stream) {
233 JP2K::PictureDescriptorDump(*this, stream);
237 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
240 void FillDescriptor(JP2K::MXFSReader& Reader) {
241 Reader.FillPictureDescriptor(*this);
244 void Dump(FILE* stream) {
245 JP2K::PictureDescriptorDump(*this, stream);
249 class MyAudioDescriptor : public PCM::AudioDescriptor
252 void FillDescriptor(PCM::MXFReader& Reader) {
253 Reader.FillAudioDescriptor(*this);
256 void Dump(FILE* stream) {
257 PCM::AudioDescriptorDump(*this, stream);
261 class MyTextDescriptor : public TimedText::TimedTextDescriptor
264 void FillDescriptor(TimedText::MXFReader& Reader) {
265 Reader.FillTimedTextDescriptor(*this);
268 void Dump(FILE* stream) {
269 TimedText::DescriptorDump(*this, stream);
273 class MyDCDataDescriptor : public DCData::DCDataDescriptor
276 void FillDescriptor(DCData::MXFReader& Reader) {
277 Reader.FillDCDataDescriptor(*this);
280 void Dump(FILE* stream) {
281 DCData::DCDataDescriptorDump(*this, stream);
285 class MyAtmosDescriptor : public ATMOS::AtmosDescriptor
288 void FillDescriptor(ATMOS::MXFReader& Reader) {
289 Reader.FillAtmosDescriptor(*this);
292 void Dump(FILE* stream) {
293 ATMOS::AtmosDescriptorDump(*this, stream);
299 template<class ReaderT, class DescriptorT>
300 class FileInfoWrapper
304 WriterInfo m_WriterInfo;
305 double m_MaxBitrate, m_AvgBitrate;
306 UL m_PictureEssenceCoding;
308 KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
311 FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
312 virtual ~FileInfoWrapper() {}
315 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
321 Result_t result = RESULT_OK;
322 result = m_Reader.OpenRead(Options.filenames.front().c_str());
324 if ( ASDCP_SUCCESS(result) )
326 m_Desc.FillDescriptor(m_Reader);
327 m_Reader.FillWriterInfo(m_WriterInfo);
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"));
333 if ( Options.showheader_flag )
334 m_Reader.DumpHeaderMetadata(stream);
336 if ( Options.showid_flag )
337 WriterInfoDump(m_WriterInfo, stream);
339 if ( Options.showdescriptor_flag )
342 if ( Options.showindex_flag )
343 m_Reader.DumpIndex(stream);
345 else if ( result == RESULT_FORMAT && Options.showheader_flag )
347 m_Reader.DumpHeaderMetadata(stream);
354 void get_PictureEssenceCoding(FILE* stream = 0)
356 const Dictionary& Dict = DefaultCompositeDict();
357 MXF::RGBAEssenceDescriptor *descriptor = 0;
359 Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
360 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
362 if ( KM_SUCCESS(result) )
363 m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
368 void dump_PictureEssenceCoding(FILE* stream = 0)
372 if ( m_PictureEssenceCoding.HasValue() )
374 const char *encoding_ul_type = "**UNKNOWN**";
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";
385 fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
391 test_rates(CommandOptions& Options, FILE* stream = 0)
393 static const double dci_max_bitrate = 250.0;
394 static const double p_hfr_max_bitrate = 400.0;
396 double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
399 if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
401 if ( m_Desc.StoredWidth > 2048 ) // 4k
403 fprintf(stream, "4k images marked as 2k HFR.\n");
407 if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
409 fprintf(stream, "HFR UL used for fps < 96.\n");
413 if ( ! Options.max_bitrate_flag )
414 max_bitrate = p_hfr_max_bitrate;
416 else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
418 fprintf(stream, "4k HFR support undefined.\n");
421 if ( m_Desc.StoredWidth <= 2048 ) // 2k
423 fprintf(stream, "2k images marked as 4k HFR.\n");
427 else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
428 && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
430 fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
435 if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
437 if ( m_Desc.StoredWidth > 2048 ) // 4k
439 fprintf(stream, "4k images marked as 2k ST 429-4.\n");
443 else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
445 if ( m_Desc.StoredWidth <= 2048 ) // 2k
447 fprintf(stream, "2k images marked as 4k ST 429-4.\n");
453 if ( m_MaxBitrate > max_bitrate )
455 fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
459 return errors ? RESULT_FAIL : RESULT_OK;
464 calc_Bitrate(FILE* stream = 0)
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;
471 for ( ui32_t i = 0; KM_SUCCESS(result) && i < m_Desc.ContainerDuration; ++i )
473 MXF::IndexTableSegment::IndexEntry entry;
474 result = footer.Lookup(i, entry);
476 if ( KM_SUCCESS(result) )
478 if ( last_stream_offset != 0 )
480 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset;
481 total_frame_bytes += this_frame_size;
483 if ( this_frame_size > largest_frame )
484 largest_frame = this_frame_size;
487 last_stream_offset = entry.StreamOffset;
491 // scale bytes to megabits
492 static const double mega_const = 1.0 / ( 1000000 / 8.0 );
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 );
497 m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
498 m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
503 dump_Bitrate(FILE* stream = 0)
505 fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
506 fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
510 void dump_WaveAudioDescriptor(FILE* stream = 0)
512 const Dictionary& Dict = DefaultCompositeDict();
513 MXF::WaveAudioDescriptor *descriptor = 0;
515 Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
516 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
518 if ( KM_SUCCESS(result) )
521 fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
528 // Read header metadata from an ASDCP file
531 show_file_info(CommandOptions& Options)
533 EssenceType_t EssenceType;
534 Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
536 if ( ASDCP_FAILURE(result) )
539 if ( EssenceType == ESS_MPEG2_VES )
541 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor> wrapper;
542 result = wrapper.file_info(Options, "MPEG2 video");
544 if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
545 wrapper.dump_Bitrate(stdout);
547 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
549 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor> wrapper;
550 result = wrapper.file_info(Options, "PCM audio");
552 if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
553 wrapper.dump_WaveAudioDescriptor();
555 else if ( EssenceType == ESS_JPEG_2000 )
557 if ( Options.stereo_image_flag )
559 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
560 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
562 if ( KM_SUCCESS(result) )
564 wrapper.get_PictureEssenceCoding();
565 wrapper.calc_Bitrate();
567 if ( Options.showcoding_flag )
568 wrapper.dump_PictureEssenceCoding(stdout);
570 if ( Options.showrate_flag )
571 wrapper.dump_Bitrate(stdout);
573 result = wrapper.test_rates(Options, stdout);
578 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
579 result = wrapper.file_info(Options, "JPEG 2000 pictures");
581 if ( KM_SUCCESS(result) )
583 wrapper.get_PictureEssenceCoding();
584 wrapper.calc_Bitrate();
586 if ( Options.showcoding_flag )
587 wrapper.dump_PictureEssenceCoding(stdout);
589 if ( Options.showrate_flag )
590 wrapper.dump_Bitrate(stdout);
592 result = wrapper.test_rates(Options, stdout);
596 else if ( EssenceType == ESS_JPEG_2000_S )
598 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
599 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
601 if ( KM_SUCCESS(result) )
603 wrapper.get_PictureEssenceCoding();
604 wrapper.calc_Bitrate();
606 if ( Options.showcoding_flag )
607 wrapper.dump_PictureEssenceCoding(stdout);
609 if ( Options.showrate_flag )
610 wrapper.dump_Bitrate(stdout);
612 result = wrapper.test_rates(Options, stdout);
615 else if ( EssenceType == ESS_TIMED_TEXT )
617 FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
618 result = wrapper.file_info(Options, "Timed Text");
620 else if ( EssenceType == ESS_DCDATA_UNKNOWN )
622 FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
623 result = wrapper.file_info(Options, "D-Cinema Generic Data");
625 else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
627 FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
628 result = wrapper.file_info(Options, "Dolby ATMOS");
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);
637 result = Reader.OpenRead(Options.filenames.front().c_str());
639 if ( ASDCP_SUCCESS(result) )
640 result = TestHeader.InitFromFile(Reader); // test UL and OP
642 if ( ASDCP_SUCCESS(result) )
644 TestHeader.Partition::Dump(stdout);
646 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
649 fputs("File contains no Identification object.\n", stdout);
651 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
654 fputs("File contains no SourcePackage object.\n", stdout);
658 fputs("File is not MXF.\n", stdout);
667 main(int argc, const char** argv)
669 Result_t result = RESULT_OK;
671 CommandOptions Options(argc, argv);
673 if ( Options.version_flag )
676 if ( Options.help_flag )
679 if ( Options.version_flag || Options.help_flag )
682 if ( Options.error_flag )
684 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
688 while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
690 result = show_file_info(Options);
691 Options.filenames.pop_front();
694 if ( ASDCP_FAILURE(result) )
696 fputs("Program stopped on error.\n", stderr);
698 if ( result == RESULT_SFORMAT )
700 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
702 else if ( result != RESULT_FAIL )
704 fputs(result, stderr);
716 // end asdcp-info.cpp