2 Copyright (c) 2011-2014, John Hurst
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
9 1. Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 3. The name of the author may not be used to endorse or promote products
15 derived from this software without specific prior written permission.
17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 /*! \file phdr-wrap.cpp
30 \brief prototype wrapping for HDR images in AS-02
32 This program wraps IMF essence (P-HDR picture) in to an AS-02 MXF file.
35 #include <KM_fileio.h>
37 #include <AS_02_PHDR.h>
39 using namespace ASDCP;
41 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
42 const ASDCP::Dictionary *g_dict = 0;
46 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
48 snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
54 //------------------------------------------------------------------------------------------
56 // command line option parser class
58 static const char* PROGRAM_NAME = "as-02-wrap"; // program name for messages
60 // local program identification info written to file headers
61 class MyInfo : public WriterInfo
66 static byte_t default_ProductUUID_Data[UUIDlen] =
67 { 0xef, 0xe4, 0x59, 0xab, 0xbe, 0x0f, 0x4c, 0x7d,
68 0xb3, 0xa2, 0xb8, 0x96, 0x79, 0xe2, 0x3e, 0x8e };
70 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
71 CompanyName = "WidgetCo";
72 ProductName = "phdr-wrap";
73 ProductVersion = ASDCP::Version();
79 // Increment the iterator, test for an additional non-option command line argument.
80 // Causes the caller to return if there are no remaining arguments or if the next
81 // argument begins with '-'.
82 #define TEST_EXTRA_ARG(i,c) \
83 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
84 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
90 banner(FILE* stream = stdout)
94 Copyright (c) 2011-2015, John Hurst\n\n\
95 asdcplib may be copied only under the terms of the license found at\n\
96 the top of every file in the asdcplib distribution kit.\n\n\
97 Specify the -h (help) option for further information about %s\n\n",
98 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
103 usage(FILE* stream = stdout)
106 USAGE: %s [-h|-help] [-V]\n\
108 %s [-a <uuid>] [-A <w>/<h>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
109 [-D <depth>] [-e|-E] [-i] [-j <key-id-string>] [-k <key-string>]\n\
110 [-M] [-m <expr>] [-p <ul>] [-r <n>/<d>] [-R] [-s <seconds>]\n\
111 [-t <min>] [-T <max>] [-u] [-v] [-W] [-x <int>] [-X <int>] [-Y]\n\
112 [-z|-Z] <input-file>+ <output-file>\n\n",
113 PROGRAM_NAME, PROGRAM_NAME);
117 -h | -help - Show help\n\
118 -V - Show version information\n\
119 -a <uuid> - Specify the Asset ID of the file\n\
120 -A <w>/<h> - Set aspect ratio for image (default 4/3)\n\
121 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
122 Defaults to 4,194,304 (4MB)\n\
123 -C <ul> - Set ChannelAssignment UL value\n\
124 -d <duration> - Number of frames to process, default all\n\
125 -D <depth> - Component depth for YCbCr images (default: 10)\n\
126 -e - Encrypt JP2K headers (default)\n\
127 -E - Do not encrypt JP2K headers\n\
128 -F (0|1) - Set field dominance for interlaced image (default: 0)\n\
129 -i - Indicates input essence is interlaced fields (forces -Y)\n\
130 -j <key-id-str> - Write key ID instead of creating a random value\n\
131 -k <key-string> - Use key for ciphertext operations\n\
132 -M - Do not create HMAC values when writing\n\
133 -m <filename> - Filename of master metadata instance (the contents of\n\
134 which will be placed in the MXF wrapper)\n\
135 -p <ul> - Set broadcast profile\n\
136 -r <n>/<d> - Edit Rate of the output file. 24/1 is the default\n\
137 -R - Indicates RGB image essence (default)\n\
138 -s <seconds> - Duration of a frame-wrapped partition (default 60)\n\
139 -t <min> - Set RGB component minimum code value (default: 0)\n\
140 -T <max> - Set RGB component maximum code value (default: 1023)\n\
141 -u - Print UL catalog to stderr\n\
142 -v - Verbose, prints informative messages to stderr\n\
143 -W - Read input file only, do not write source file\n\
144 -x <int> - Horizontal subsampling degree (default: 2)\n\
145 -X <int> - Vertical subsampling degree (default: 2)\n\
146 -Y - Indicates YCbCr image essence (default: RGB)\n\
147 -z - Fail if j2c inputs have unequal parameters (default)\n\
148 -Z - Ignore unequal parameters in j2c inputs\n\
150 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
151 o All option arguments must be separated from the option by whitespace.\n\n");
161 bool error_flag; // true if the given options are in error or not complete
162 bool key_flag; // true if an encryption key was given
163 bool asset_id_flag; // true if an asset ID was given
164 bool encrypt_header_flag; // true if j2c headers are to be encrypted
165 bool write_hmac; // true if HMAC values are to be generated and written
166 bool verbose_flag; // true if the verbose option was selected
167 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
168 bool no_write_flag; // true if no output files are to be written
169 bool version_flag; // true if the version display option was selected
170 bool help_flag; // true if the help display option was selected
171 ui32_t duration; // number of frames to be processed
172 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
173 bool use_cdci_descriptor; //
174 Rational edit_rate; // edit rate of JP2K sequence
175 ui32_t fb_size; // size of picture frame buffer
176 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
177 bool key_id_flag; // true if a key ID was given
178 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
179 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
180 std::string out_file; //
181 bool show_ul_values_flag; /// if true, dump the UL table before going tp work.
182 Kumu::PathList_t filenames; // list of filenames to be processed
188 ui32_t horizontal_subsampling;
189 ui32_t vertical_subsampling;
190 ui32_t component_depth;
192 ASDCP::Rational aspect_ratio;
193 ui8_t field_dominance;
194 ui32_t mxf_header_size;
196 //new attributes for AS-02 support
197 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
198 ui32_t partition_space; //Shim parameter partition_spacing
200 std::string PHDR_master_metadata; //
203 CommandOptions(int argc, const char** argv) :
204 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
205 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
206 no_write_flag(false), version_flag(false), help_flag(false),
207 duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
208 show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
209 rgba_MaxRef(1023), rgba_MinRef(0),
210 horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
211 frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
212 mxf_header_size(16384)
214 memset(key_value, 0, KeyLen);
215 memset(key_id_value, 0, UUIDlen);
217 for ( int i = 1; i < argc; i++ )
220 if ( (strcmp( argv[i], "-help") == 0) )
226 if ( argv[i][0] == '-'
227 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
230 switch ( argv[i][1] )
233 TEST_EXTRA_ARG(i, 'A');
234 if ( ! DecodeRational(argv[i], aspect_ratio) )
236 fprintf(stderr, "Error decoding aspect ratio value: %s\n", argv[i]);
242 asset_id_flag = true;
243 TEST_EXTRA_ARG(i, 'a');
246 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
248 if ( length != UUIDlen )
250 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
257 TEST_EXTRA_ARG(i, 'b');
258 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
261 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
266 TEST_EXTRA_ARG(i, 'D');
267 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
271 TEST_EXTRA_ARG(i, 'd');
272 duration = Kumu::xabs(strtol(argv[i], 0, 10));
275 case 'E': encrypt_header_flag = false; break;
276 case 'e': encrypt_header_flag = true; break;
279 TEST_EXTRA_ARG(i, 'F');
280 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
281 if ( field_dominance > 1 )
283 fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
288 case 'h': help_flag = true; break;
292 use_cdci_descriptor = true;
297 TEST_EXTRA_ARG(i, 'j');
300 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
302 if ( length != UUIDlen )
304 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
310 case 'k': key_flag = true;
311 TEST_EXTRA_ARG(i, 'k');
314 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
316 if ( length != KeyLen )
318 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
324 case 'M': write_hmac = false; break;
327 TEST_EXTRA_ARG(i, 'm');
328 if ( KM_FAILURE(Kumu::ReadFileIntoString(argv[i], PHDR_master_metadata) ) )
330 fprintf(stderr, "Unable to read metadata file %s\n", argv[i]);
336 TEST_EXTRA_ARG(i, 'p');
337 if ( ! picture_coding.DecodeHex(argv[i]) )
339 fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
345 TEST_EXTRA_ARG(i, 'r');
346 if ( ! DecodeRational(argv[i], edit_rate) )
348 fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
355 use_cdci_descriptor = false;
359 TEST_EXTRA_ARG(i, 's');
360 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
364 TEST_EXTRA_ARG(i, 't');
365 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
369 TEST_EXTRA_ARG(i, 'T');
370 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
373 case 'u': show_ul_values_flag = true; break;
374 case 'V': version_flag = true; break;
375 case 'v': verbose_flag = true; break;
376 case 'W': no_write_flag = true; break;
379 TEST_EXTRA_ARG(i, 'x');
380 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
384 TEST_EXTRA_ARG(i, 'X');
385 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
389 use_cdci_descriptor = true;
392 case 'Z': j2c_pedantic = false; break;
393 case 'z': j2c_pedantic = true; break;
396 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
403 if ( argv[i][0] != '-' )
405 filenames.push_back(argv[i]);
409 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
415 if ( help_flag || version_flag )
418 if ( filenames.size() < 2 )
420 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
424 out_file = filenames.back();
425 filenames.pop_back();
427 if ( ! picture_coding.HasValue() )
429 picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
437 //------------------------------------------------------------------------------------------
441 Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
442 const ASDCP::Dictionary& dict,
443 ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
444 ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
447 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
448 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
451 write_JP2K_file(CommandOptions& Options)
453 AS_02::PHDR::MXFWriter Writer;
454 AS_02::PHDR::FrameBuffer FrameBuffer(Options.fb_size);
455 AS_02::PHDR::SequenceParser Parser;
457 AESEncContext* Context = 0;
458 HMACContext* HMAC = 0;
459 byte_t IV_buf[CBC_BLOCK_SIZE];
460 Kumu::FortunaRNG RNG;
462 ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
463 ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
465 // set up essence parser
466 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
469 if ( ASDCP_SUCCESS(result) )
471 ASDCP::JP2K::PictureDescriptor PDesc;
472 Parser.FillPictureDescriptor(PDesc);
473 PDesc.EditRate = Options.edit_rate;
475 if ( Options.verbose_flag )
477 fprintf(stderr, "JPEG 2000 P-HDR pictures\n");
478 fputs("PictureDescriptor:\n", stderr);
479 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
480 JP2K::PictureDescriptorDump(PDesc);
483 if ( Options.use_cdci_descriptor )
485 ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
486 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
488 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
489 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
490 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
492 if ( ASDCP_SUCCESS(result) )
494 tmp_dscr->PictureEssenceCoding = Options.picture_coding;
495 tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
496 tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
497 tmp_dscr->ComponentDepth = Options.component_depth;
498 tmp_dscr->FrameLayout = Options.frame_layout;
499 tmp_dscr->AspectRatio = Options.aspect_ratio;
500 tmp_dscr->FieldDominance = Options.field_dominance;
501 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
506 ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
507 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
509 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
510 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
511 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
513 if ( ASDCP_SUCCESS(result) )
515 tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
516 tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
517 tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
518 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
523 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
525 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
526 Info.LabelSetType = LS_MXF_SMPTE;
528 if ( Options.asset_id_flag )
529 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
531 Kumu::GenRandomUUID(Info.AssetUUID);
533 // configure encryption
534 if( Options.key_flag )
536 Kumu::GenRandomUUID(Info.ContextID);
537 Info.EncryptedEssence = true;
539 if ( Options.key_id_flag )
540 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
542 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
544 Context = new AESEncContext;
545 result = Context->InitKey(Options.key_value);
547 if ( ASDCP_SUCCESS(result) )
548 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
550 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
552 Info.UsesHMAC = true;
553 HMAC = new HMACContext;
554 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
558 if ( ASDCP_SUCCESS(result) )
560 result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
561 Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
565 if ( ASDCP_SUCCESS(result) )
568 result = Parser.Reset();
570 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
572 result = Parser.ReadFrame(FrameBuffer);
574 if ( ASDCP_SUCCESS(result) )
576 if ( Options.verbose_flag )
577 FrameBuffer.Dump(stderr, Options.fb_dump_size);
579 if ( Options.encrypt_header_flag )
580 FrameBuffer.PlaintextOffset(0);
583 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
585 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
587 // The Writer class will forward the last block of ciphertext
588 // to the encryption context for use as the IV for the next
589 // frame. If you want to use non-sequitur IV values, un-comment
590 // the following line of code.
591 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
592 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
596 if ( result == RESULT_ENDOFFILE )
600 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
601 result = Writer.Finalize(Options.PHDR_master_metadata);
608 main(int argc, const char** argv)
610 Result_t result = RESULT_OK;
612 g_dict = &ASDCP::DefaultSMPTEDict();
615 CommandOptions Options(argc, argv);
617 if ( Options.version_flag )
620 if ( Options.help_flag )
623 if ( Options.show_ul_values_flag )
625 g_dict->Dump(stdout);
628 if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
631 if ( Options.error_flag )
633 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
637 EssenceType_t EssenceType;
638 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
640 if ( ASDCP_SUCCESS(result) )
642 switch ( EssenceType )
645 result = write_JP2K_file(Options);
649 fprintf(stderr, "%s: Unknown file type, not P-HDR-compatible essence.\n",
650 Options.filenames.front().c_str());
655 if ( ASDCP_FAILURE(result) )
657 fputs("Program stopped on error.\n", stderr);
659 if ( result != RESULT_FAIL )
661 fputs(result, stderr);