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");
155 static ASDCP::Rational
156 decode_rational(const char* str_rat)
159 ui32_t Num = atoi(str_rat);
162 const char* den_str = strrchr(str_rat, '/');
164 Den = atoi(den_str+1);
166 return ASDCP::Rational(Num, Den);
176 bool error_flag; // true if the given options are in error or not complete
177 bool key_flag; // true if an encryption key was given
178 bool asset_id_flag; // true if an asset ID was given
179 bool encrypt_header_flag; // true if j2c headers are to be encrypted
180 bool write_hmac; // true if HMAC values are to be generated and written
181 bool verbose_flag; // true if the verbose option was selected
182 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
183 bool no_write_flag; // true if no output files are to be written
184 bool version_flag; // true if the version display option was selected
185 bool help_flag; // true if the help display option was selected
186 ui32_t duration; // number of frames to be processed
187 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
188 bool use_cdci_descriptor; //
189 Rational edit_rate; // edit rate of JP2K sequence
190 ui32_t fb_size; // size of picture frame buffer
191 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
192 bool key_id_flag; // true if a key ID was given
193 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
194 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
195 std::string out_file; //
196 bool show_ul_values_flag; /// if true, dump the UL table before going tp work.
197 Kumu::PathList_t filenames; // list of filenames to be processed
203 ui32_t horizontal_subsampling;
204 ui32_t vertical_subsampling;
205 ui32_t component_depth;
207 ASDCP::Rational aspect_ratio;
208 ui8_t field_dominance;
209 ui32_t mxf_header_size;
211 //new attributes for AS-02 support
212 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
213 ui32_t partition_space; //Shim parameter partition_spacing
215 std::string PHDR_master_metadata; //
218 CommandOptions(int argc, const char** argv) :
219 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
220 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
221 no_write_flag(false), version_flag(false), help_flag(false),
222 duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
223 show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
224 rgba_MaxRef(1023), rgba_MinRef(0),
225 horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
226 frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
227 mxf_header_size(16384)
229 memset(key_value, 0, KeyLen);
230 memset(key_id_value, 0, UUIDlen);
232 for ( int i = 1; i < argc; i++ )
235 if ( (strcmp( argv[i], "-help") == 0) )
241 if ( argv[i][0] == '-'
242 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
245 switch ( argv[i][1] )
248 TEST_EXTRA_ARG(i, 'A');
249 edit_rate = decode_rational(argv[i]);
253 asset_id_flag = true;
254 TEST_EXTRA_ARG(i, 'a');
257 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
259 if ( length != UUIDlen )
261 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
268 TEST_EXTRA_ARG(i, 'b');
269 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
272 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
277 TEST_EXTRA_ARG(i, 'D');
278 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
282 TEST_EXTRA_ARG(i, 'd');
283 duration = Kumu::xabs(strtol(argv[i], 0, 10));
286 case 'E': encrypt_header_flag = false; break;
287 case 'e': encrypt_header_flag = true; break;
290 TEST_EXTRA_ARG(i, 'F');
291 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
292 if ( field_dominance > 1 )
294 fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
299 case 'h': help_flag = true; break;
303 use_cdci_descriptor = true;
308 TEST_EXTRA_ARG(i, 'j');
311 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
313 if ( length != UUIDlen )
315 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
321 case 'k': key_flag = true;
322 TEST_EXTRA_ARG(i, 'k');
325 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
327 if ( length != KeyLen )
329 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
335 case 'M': write_hmac = false; break;
338 TEST_EXTRA_ARG(i, 'm');
339 if ( KM_FAILURE(Kumu::ReadFileIntoString(argv[i], PHDR_master_metadata) ) )
341 fprintf(stderr, "Unable to read metadata file %s\n", argv[i]);
347 TEST_EXTRA_ARG(i, 'p');
348 if ( ! picture_coding.DecodeHex(argv[i]) )
350 fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
356 TEST_EXTRA_ARG(i, 'r');
357 edit_rate = decode_rational(argv[i]);
361 use_cdci_descriptor = false;
365 TEST_EXTRA_ARG(i, 's');
366 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
370 TEST_EXTRA_ARG(i, 't');
371 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
375 TEST_EXTRA_ARG(i, 'T');
376 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
379 case 'u': show_ul_values_flag = true; break;
380 case 'V': version_flag = true; break;
381 case 'v': verbose_flag = true; break;
382 case 'W': no_write_flag = true; break;
385 TEST_EXTRA_ARG(i, 'x');
386 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
390 TEST_EXTRA_ARG(i, 'X');
391 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
395 use_cdci_descriptor = true;
398 case 'Z': j2c_pedantic = false; break;
399 case 'z': j2c_pedantic = true; break;
402 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
409 if ( argv[i][0] != '-' )
411 filenames.push_back(argv[i]);
415 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
421 if ( help_flag || version_flag )
424 if ( filenames.size() < 2 )
426 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
430 out_file = filenames.back();
431 filenames.pop_back();
433 if ( ! picture_coding.HasValue() )
435 picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
443 //------------------------------------------------------------------------------------------
447 Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
448 const ASDCP::Dictionary& dict,
449 ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
450 ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
453 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
454 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
457 write_JP2K_file(CommandOptions& Options)
459 AS_02::PHDR::MXFWriter Writer;
460 AS_02::PHDR::FrameBuffer FrameBuffer(Options.fb_size);
461 AS_02::PHDR::SequenceParser Parser;
463 AESEncContext* Context = 0;
464 HMACContext* HMAC = 0;
465 byte_t IV_buf[CBC_BLOCK_SIZE];
466 Kumu::FortunaRNG RNG;
468 ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
469 ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
471 // set up essence parser
472 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
475 if ( ASDCP_SUCCESS(result) )
477 ASDCP::JP2K::PictureDescriptor PDesc;
478 Parser.FillPictureDescriptor(PDesc);
479 PDesc.EditRate = Options.edit_rate;
481 if ( Options.verbose_flag )
483 fprintf(stderr, "JPEG 2000 P-HDR pictures\n");
484 fputs("PictureDescriptor:\n", stderr);
485 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
486 JP2K::PictureDescriptorDump(PDesc);
489 if ( Options.use_cdci_descriptor )
491 ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
492 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
494 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
495 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
496 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
498 if ( ASDCP_SUCCESS(result) )
500 tmp_dscr->PictureEssenceCoding = Options.picture_coding;
501 tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
502 tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
503 tmp_dscr->ComponentDepth = Options.component_depth;
504 tmp_dscr->FrameLayout = Options.frame_layout;
505 tmp_dscr->AspectRatio = Options.aspect_ratio;
506 tmp_dscr->FieldDominance = Options.field_dominance;
507 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
512 ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
513 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
515 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
516 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
517 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
519 if ( ASDCP_SUCCESS(result) )
521 tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
522 tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
523 tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
524 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
529 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
531 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
532 Info.LabelSetType = LS_MXF_SMPTE;
534 if ( Options.asset_id_flag )
535 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
537 Kumu::GenRandomUUID(Info.AssetUUID);
539 // configure encryption
540 if( Options.key_flag )
542 Kumu::GenRandomUUID(Info.ContextID);
543 Info.EncryptedEssence = true;
545 if ( Options.key_id_flag )
546 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
548 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
550 Context = new AESEncContext;
551 result = Context->InitKey(Options.key_value);
553 if ( ASDCP_SUCCESS(result) )
554 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
556 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
558 Info.UsesHMAC = true;
559 HMAC = new HMACContext;
560 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
564 if ( ASDCP_SUCCESS(result) )
566 result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
567 Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
571 if ( ASDCP_SUCCESS(result) )
574 result = Parser.Reset();
576 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
578 result = Parser.ReadFrame(FrameBuffer);
580 if ( ASDCP_SUCCESS(result) )
582 if ( Options.verbose_flag )
583 FrameBuffer.Dump(stderr, Options.fb_dump_size);
585 if ( Options.encrypt_header_flag )
586 FrameBuffer.PlaintextOffset(0);
589 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
591 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
593 // The Writer class will forward the last block of ciphertext
594 // to the encryption context for use as the IV for the next
595 // frame. If you want to use non-sequitur IV values, un-comment
596 // the following line of code.
597 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
598 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
602 if ( result == RESULT_ENDOFFILE )
606 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
607 result = Writer.Finalize(Options.PHDR_master_metadata);
614 main(int argc, const char** argv)
616 Result_t result = RESULT_OK;
618 g_dict = &ASDCP::DefaultSMPTEDict();
621 CommandOptions Options(argc, argv);
623 if ( Options.version_flag )
626 if ( Options.help_flag )
629 if ( Options.show_ul_values_flag )
631 g_dict->Dump(stdout);
634 if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
637 if ( Options.error_flag )
639 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
643 EssenceType_t EssenceType;
644 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
646 if ( ASDCP_SUCCESS(result) )
648 switch ( EssenceType )
651 result = write_JP2K_file(Options);
655 fprintf(stderr, "%s: Unknown file type, not P-HDR-compatible essence.\n",
656 Options.filenames.front().c_str());
661 if ( ASDCP_FAILURE(result) )
663 fputs("Program stopped on error.\n", stderr);
665 if ( result != RESULT_FAIL )
667 fputs(result, stderr);