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>
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-2014 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 - 20; // do not count the bytes that represent the KLV wrapping
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 if ( KM_SUCCESS(result) )
493 // scale bytes to megabits
494 static const double mega_const = 1.0 / ( 1000000 / 8.0 );
496 // we did not accumulate the first or last frame, so duration -= 2
497 double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
499 m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
500 m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
506 dump_Bitrate(FILE* stream = 0)
508 fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
509 fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
513 void dump_WaveAudioDescriptor(FILE* stream = 0)
515 const Dictionary& Dict = DefaultCompositeDict();
516 MXF::WaveAudioDescriptor *descriptor = 0;
518 Result_t result = m_Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
519 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
521 if ( KM_SUCCESS(result) )
524 fprintf(stream, "ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
531 // Read header metadata from an ASDCP file
534 show_file_info(CommandOptions& Options)
536 EssenceType_t EssenceType;
537 Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
539 if ( ASDCP_FAILURE(result) )
542 if ( EssenceType == ESS_MPEG2_VES )
544 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor> wrapper;
545 result = wrapper.file_info(Options, "MPEG2 video");
547 if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
548 wrapper.dump_Bitrate(stdout);
550 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
552 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor> wrapper;
553 result = wrapper.file_info(Options, "PCM audio");
555 if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
556 wrapper.dump_WaveAudioDescriptor();
558 else if ( EssenceType == ESS_JPEG_2000 )
560 if ( Options.stereo_image_flag )
562 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
563 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
565 if ( KM_SUCCESS(result) )
567 wrapper.get_PictureEssenceCoding();
568 wrapper.calc_Bitrate();
570 if ( Options.showcoding_flag )
571 wrapper.dump_PictureEssenceCoding(stdout);
573 if ( Options.showrate_flag )
574 wrapper.dump_Bitrate(stdout);
576 result = wrapper.test_rates(Options, stdout);
581 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
582 result = wrapper.file_info(Options, "JPEG 2000 pictures");
584 if ( KM_SUCCESS(result) )
586 wrapper.get_PictureEssenceCoding();
587 wrapper.calc_Bitrate();
589 if ( Options.showcoding_flag )
590 wrapper.dump_PictureEssenceCoding(stdout);
592 if ( Options.showrate_flag )
593 wrapper.dump_Bitrate(stdout);
595 result = wrapper.test_rates(Options, stdout);
599 else if ( EssenceType == ESS_JPEG_2000_S )
601 FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
602 result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
604 if ( KM_SUCCESS(result) )
606 wrapper.get_PictureEssenceCoding();
607 wrapper.calc_Bitrate();
609 if ( Options.showcoding_flag )
610 wrapper.dump_PictureEssenceCoding(stdout);
612 if ( Options.showrate_flag )
613 wrapper.dump_Bitrate(stdout);
615 result = wrapper.test_rates(Options, stdout);
618 else if ( EssenceType == ESS_TIMED_TEXT )
620 FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>wrapper;
621 result = wrapper.file_info(Options, "Timed Text");
623 else if ( EssenceType == ESS_DCDATA_UNKNOWN )
625 FileInfoWrapper<ASDCP::DCData::MXFReader, MyDCDataDescriptor> wrapper;
626 result = wrapper.file_info(Options, "D-Cinema Generic Data");
628 else if ( EssenceType == ESS_DCDATA_DOLBY_ATMOS )
630 FileInfoWrapper<ASDCP::ATMOS::MXFReader, MyAtmosDescriptor> wrapper;
631 result = wrapper.file_info(Options, "Dolby ATMOS");
635 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames.front().c_str());
636 Kumu::FileReader Reader;
637 const Dictionary* Dict = &DefaultCompositeDict();
638 MXF::OP1aHeader TestHeader(Dict);
640 result = Reader.OpenRead(Options.filenames.front().c_str());
642 if ( ASDCP_SUCCESS(result) )
643 result = TestHeader.InitFromFile(Reader); // test UL and OP
645 if ( ASDCP_SUCCESS(result) )
647 TestHeader.Partition::Dump(stdout);
649 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
652 fputs("File contains no Identification object.\n", stdout);
654 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
657 fputs("File contains no SourcePackage object.\n", stdout);
661 fputs("File is not MXF.\n", stdout);
670 main(int argc, const char** argv)
672 Result_t result = RESULT_OK;
674 CommandOptions Options(argc, argv);
676 if ( Options.version_flag )
679 if ( Options.help_flag )
682 if ( Options.version_flag || Options.help_flag )
685 if ( Options.error_flag )
687 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
691 while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
693 result = show_file_info(Options);
694 Options.filenames.pop_front();
697 if ( ASDCP_FAILURE(result) )
699 fputs("Program stopped on error.\n", stderr);
701 if ( result == RESULT_SFORMAT )
703 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
705 else if ( result != RESULT_FAIL )
707 fputs(result, stderr);
719 // end asdcp-info.cpp