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 - 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\
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, "File essence type is %s, (%d edit unit%s).\n",
330 type_string, m_Desc.ContainerDuration, (m_Desc.ContainerDuration==1?"":"s"));
332 if ( Options.showheader_flag )
333 m_Reader.DumpHeaderMetadata(stream);
335 if ( Options.showid_flag )
336 WriterInfoDump(m_WriterInfo, stream);
338 if ( Options.showdescriptor_flag )
341 if ( Options.showindex_flag )
342 m_Reader.DumpIndex(stream);
344 else if ( result == RESULT_FORMAT && Options.showheader_flag )
346 m_Reader.DumpHeaderMetadata(stream);
353 void get_PictureEssenceCoding(FILE* stream = 0)
355 const Dictionary& Dict = DefaultCompositeDict();
356 MXF::RGBAEssenceDescriptor *descriptor = 0;
358 Result_t result = m_Reader.OPAtomHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
359 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
361 if ( KM_SUCCESS(result) )
362 m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
367 void dump_PictureEssenceCoding(FILE* stream = 0)
371 if ( m_PictureEssenceCoding.HasValue() )
373 const char *encoding_ul_type = "**UNKNOWN**";
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";
384 fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
390 test_rates(CommandOptions& Options, FILE* stream = 0)
392 static const double dci_max_bitrate = 250.0;
393 static const double p_hfr_max_bitrate = 400.0;
395 double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
398 if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
400 if ( m_Desc.StoredWidth > 2048 ) // 4k
402 fprintf(stream, "4k images marked as 2k HFR.\n");
406 if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
408 fprintf(stream, "HFR UL used for fps < 96.\n");
412 if ( ! Options.max_bitrate_flag )
413 max_bitrate = p_hfr_max_bitrate;
415 else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
417 fprintf(stream, "4k HFR support undefined.\n");
420 if ( m_Desc.StoredWidth <= 2048 ) // 2k
422 fprintf(stream, "2k images marked as 4k HFR.\n");
426 else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
427 && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
429 fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
434 if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
436 if ( m_Desc.StoredWidth > 2048 ) // 4k
438 fprintf(stream, "4k images marked as 2k ST 429-4.\n");
442 else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
444 if ( m_Desc.StoredWidth <= 2048 ) // 2k
446 fprintf(stream, "2k images marked as 4k ST 429-4.\n");
452 if ( m_MaxBitrate > max_bitrate )
454 fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
458 return errors ? RESULT_FAIL : RESULT_OK;
463 calc_Bitrate(FILE* stream = 0)
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;
470 for ( ui32_t i = 0; KM_SUCCESS(result) && i < m_Desc.ContainerDuration; ++i )
472 MXF::IndexTableSegment::IndexEntry entry;
473 result = footer.Lookup(i, entry);
475 if ( KM_SUCCESS(result) )
477 if ( last_stream_offset != 0 )
479 ui64_t this_frame_size = entry.StreamOffset - last_stream_offset;
480 total_frame_bytes += this_frame_size;
482 if ( this_frame_size > largest_frame )
483 largest_frame = this_frame_size;
486 last_stream_offset = entry.StreamOffset;
490 // scale bytes to megabits
491 static const double mega_const = 1 / ( 1024.0 * 1024.0 / 8.0 );
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 );
496 m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
497 m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
502 dump_Bitrate(FILE* stream = 0)
504 fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
505 fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
509 void dump_WaveAudioDescriptor(FILE* stream = 0)
511 const Dictionary& Dict = DefaultCompositeDict();
512 MXF::WaveAudioDescriptor *descriptor = 0;
514 Result_t result = m_Reader.OPAtomHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
515 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
517 if ( KM_SUCCESS(result) )
520 fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.EncodeString(buf, 64));
527 // Read header metadata from an ASDCP file
530 show_file_info(CommandOptions& Options)
532 EssenceType_t EssenceType;
533 Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
535 if ( ASDCP_FAILURE(result) )
538 if ( EssenceType == ESS_MPEG2_VES )
540 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor> wrapper;
541 result = wrapper.file_info(Options, "MPEG2 video");
543 if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
544 wrapper.dump_Bitrate(stdout);
546 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
548 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor> wrapper;
549 result = wrapper.file_info(Options, "PCM audio");
551 if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
552 wrapper.dump_WaveAudioDescriptor();
554 else if ( EssenceType == ESS_JPEG_2000 )
556 if ( Options.stereo_image_flag )
558 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
559 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
561 if ( KM_SUCCESS(result) )
563 wrapper.get_PictureEssenceCoding();
564 wrapper.calc_Bitrate();
566 if ( Options.showcoding_flag )
567 wrapper.dump_PictureEssenceCoding(stdout);
569 if ( Options.showrate_flag )
570 wrapper.dump_Bitrate(stdout);
572 result = wrapper.test_rates(Options, stdout);
577 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
578 result = wrapper.file_info(Options, "JPEG 2000 pictures");
580 if ( KM_SUCCESS(result) )
582 wrapper.get_PictureEssenceCoding();
583 wrapper.calc_Bitrate();
585 if ( Options.showcoding_flag )
586 wrapper.dump_PictureEssenceCoding(stdout);
588 if ( Options.showrate_flag )
589 wrapper.dump_Bitrate(stdout);
591 result = wrapper.test_rates(Options, stdout);
595 else if ( EssenceType == ESS_JPEG_2000_S )
597 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
598 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
600 if ( KM_SUCCESS(result) )
602 wrapper.get_PictureEssenceCoding();
603 wrapper.calc_Bitrate();
605 if ( Options.showcoding_flag )
606 wrapper.dump_PictureEssenceCoding(stdout);
608 if ( Options.showrate_flag )
609 wrapper.dump_Bitrate(stdout);
611 result = wrapper.test_rates(Options, stdout);
614 else if ( EssenceType == ESS_TIMED_TEXT )
616 FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
617 result = wrapper.file_info(Options, "Timed Text");
619 else if ( EssenceType == ESS_DCDATA_UNKNOWN )
621 FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
622 result = wrapper.file_info(Options, "D-Cinema Generic Data");
624 else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
626 FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
627 result = wrapper.file_info(Options, "Dolby ATMOS");
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);
636 result = Reader.OpenRead(Options.filenames.front().c_str());
638 if ( ASDCP_SUCCESS(result) )
639 result = TestHeader.InitFromFile(Reader); // test UL and OP
641 if ( ASDCP_SUCCESS(result) )
643 TestHeader.Partition::Dump(stdout);
645 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
648 fputs("File contains no Identification object.\n", stdout);
650 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
653 fputs("File contains no SourcePackage object.\n", stdout);
657 fputs("File is not MXF.\n", stdout);
666 main(int argc, const char** argv)
668 Result_t result = RESULT_OK;
670 CommandOptions Options(argc, argv);
672 if ( Options.version_flag )
675 if ( Options.help_flag )
678 if ( Options.version_flag || Options.help_flag )
681 if ( Options.error_flag )
683 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
687 while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
689 result = show_file_info(Options);
690 Options.filenames.pop_front();
693 if ( ASDCP_FAILURE(result) )
695 fputs("Program stopped on error.\n", stderr);
697 if ( result == RESULT_SFORMAT )
699 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
701 else if ( result != RESULT_FAIL )
703 fputs(result, stderr);
715 // end asdcp-info.cpp