2 Copyright (c) 2011-2018, 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 create_random_uuid(byte_t* uuidbuf)
93 GenRandomValue(tmp_id);
94 memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size());
99 banner(FILE* stream = stdout)
102 %s (asdcplib %s)\n\n\
103 Copyright (c) 2011-2018, John Hurst\n\n\
104 asdcplib may be copied only under the terms of the license found at\n\
105 the top of every file in the asdcplib distribution kit.\n\n\
106 Specify the -h (help) option for further information about %s\n\n",
107 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
112 usage(FILE* stream = stdout)
115 USAGE: %s [-h|-help] [-V]\n\
117 %s [-a <uuid>] [-A <w>/<h>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
118 [-D <depth>] [-e|-E] [-i] [-j <key-id-string>] [-k <key-string>]\n\
119 [-M] [-m <expr>] [-p <ul>] [-r <n>/<d>] [-R] [-s <seconds>]\n\
120 [-t <min>] [-T <max>] [-u] [-v] [-W] [-x <int>] [-X <int>] [-Y]\n\
121 [-z|-Z] <input-file>+ <output-file>\n\n",
122 PROGRAM_NAME, PROGRAM_NAME);
126 -h | -help - Show help\n\
127 -V - Show version information\n\
128 -a <uuid> - Specify the Asset ID of the file\n\
129 -A <w>/<h> - Set aspect ratio for image (default 4/3)\n\
130 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
131 Defaults to 4,194,304 (4MB)\n\
132 -C <ul> - Set ChannelAssignment UL value\n\
133 -d <duration> - Number of frames to process, default all\n\
134 -D <depth> - Component depth for YCbCr images (default: 10)\n\
135 -e - Encrypt JP2K headers (default)\n\
136 -E - Do not encrypt JP2K headers\n\
137 -F (0|1) - Set field dominance for interlaced image (default: 0)\n\
138 -i - Indicates input essence is interlaced fields (forces -Y)\n\
139 -j <key-id-str> - Write key ID instead of creating a random value\n\
140 -k <key-string> - Use key for ciphertext operations\n\
141 -M - Do not create HMAC values when writing\n\
142 -m <filename> - Filename of master metadata instance (the contents of\n\
143 which will be placed in the MXF wrapper)\n\
144 -p <ul> - Set broadcast profile\n\
145 -r <n>/<d> - Edit Rate of the output file. 24/1 is the default\n\
146 -R - Indicates RGB image essence (default)\n\
147 -s <seconds> - Duration of a frame-wrapped partition (default 60)\n\
148 -t <min> - Set RGB component minimum code value (default: 0)\n\
149 -T <max> - Set RGB component maximum code value (default: 1023)\n\
150 -u - Print UL catalog to stderr\n\
151 -v - Verbose, prints informative messages to stderr\n\
152 -W - Read input file only, do not write source file\n\
153 -x <int> - Horizontal subsampling degree (default: 2)\n\
154 -X <int> - Vertical subsampling degree (default: 2)\n\
155 -Y - Indicates YCbCr image essence (default: RGB)\n\
156 -z - Fail if j2c inputs have unequal parameters (default)\n\
157 -Z - Ignore unequal parameters in j2c inputs\n\
159 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
160 o All option arguments must be separated from the option by whitespace.\n\n");
170 bool error_flag; // true if the given options are in error or not complete
171 bool key_flag; // true if an encryption key was given
172 bool asset_id_flag; // true if an asset ID was given
173 bool encrypt_header_flag; // true if j2c headers are to be encrypted
174 bool write_hmac; // true if HMAC values are to be generated and written
175 bool verbose_flag; // true if the verbose option was selected
176 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
177 bool no_write_flag; // true if no output files are to be written
178 bool version_flag; // true if the version display option was selected
179 bool help_flag; // true if the help display option was selected
180 ui32_t duration; // number of frames to be processed
181 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
182 bool use_cdci_descriptor; //
183 Rational edit_rate; // edit rate of JP2K sequence
184 ui32_t fb_size; // size of picture frame buffer
185 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
186 bool key_id_flag; // true if a key ID was given
187 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
188 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
189 std::string out_file; //
190 bool show_ul_values_flag; /// if true, dump the UL table before going tp work.
191 Kumu::PathList_t filenames; // list of filenames to be processed
197 ui32_t horizontal_subsampling;
198 ui32_t vertical_subsampling;
199 ui32_t component_depth;
201 ASDCP::Rational aspect_ratio;
202 ui8_t field_dominance;
203 ui32_t mxf_header_size;
205 //new attributes for AS-02 support
206 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
207 ui32_t partition_space; //Shim parameter partition_spacing
209 std::string PHDR_master_metadata;
210 std::string global_metadata_filename;
213 CommandOptions(int argc, const char** argv) :
214 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
215 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
216 no_write_flag(false), version_flag(false), help_flag(false),
217 duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
218 show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
219 rgba_MaxRef(1023), rgba_MinRef(0),
220 horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
221 frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
222 mxf_header_size(16384)
224 memset(key_value, 0, KeyLen);
225 memset(key_id_value, 0, UUIDlen);
227 for ( int i = 1; i < argc; i++ )
230 if ( (strcmp( argv[i], "-help") == 0) )
236 if ( argv[i][0] == '-'
237 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
240 switch ( argv[i][1] )
243 TEST_EXTRA_ARG(i, 'A');
244 if ( ! DecodeRational(argv[i], aspect_ratio) )
246 fprintf(stderr, "Error decoding aspect ratio value: %s\n", argv[i]);
252 asset_id_flag = true;
253 TEST_EXTRA_ARG(i, 'a');
256 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
258 if ( length != UUIDlen )
260 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
267 TEST_EXTRA_ARG(i, 'b');
268 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
271 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
276 TEST_EXTRA_ARG(i, 'D');
277 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
281 TEST_EXTRA_ARG(i, 'd');
282 duration = Kumu::xabs(strtol(argv[i], 0, 10));
285 case 'E': encrypt_header_flag = false; break;
286 case 'e': encrypt_header_flag = true; break;
289 TEST_EXTRA_ARG(i, 'F');
290 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
291 if ( field_dominance > 1 )
293 fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
299 TEST_EXTRA_ARG(i, 'g');
300 global_metadata_filename = argv[i];
303 case 'h': help_flag = true; break;
307 use_cdci_descriptor = true;
312 TEST_EXTRA_ARG(i, 'j');
315 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
317 if ( length != UUIDlen )
319 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
325 case 'k': key_flag = true;
326 TEST_EXTRA_ARG(i, 'k');
329 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
331 if ( length != KeyLen )
333 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
339 case 'M': write_hmac = false; break;
342 TEST_EXTRA_ARG(i, 'm');
343 if ( KM_FAILURE(Kumu::ReadFileIntoString(argv[i], PHDR_master_metadata) ) )
345 fprintf(stderr, "Unable to read metadata file %s\n", argv[i]);
351 TEST_EXTRA_ARG(i, 'p');
352 if ( ! picture_coding.DecodeHex(argv[i]) )
354 fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
360 TEST_EXTRA_ARG(i, 'r');
361 if ( ! DecodeRational(argv[i], edit_rate) )
363 fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
370 use_cdci_descriptor = false;
374 TEST_EXTRA_ARG(i, 's');
375 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
379 TEST_EXTRA_ARG(i, 't');
380 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
384 TEST_EXTRA_ARG(i, 'T');
385 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
388 case 'u': show_ul_values_flag = true; break;
389 case 'V': version_flag = true; break;
390 case 'v': verbose_flag = true; break;
391 case 'W': no_write_flag = true; break;
394 TEST_EXTRA_ARG(i, 'x');
395 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
399 TEST_EXTRA_ARG(i, 'X');
400 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
404 use_cdci_descriptor = true;
407 case 'Z': j2c_pedantic = false; break;
408 case 'z': j2c_pedantic = true; break;
411 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
418 if ( argv[i][0] != '-' )
420 filenames.push_back(argv[i]);
424 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
430 if ( help_flag || version_flag )
433 if ( filenames.size() < 2 )
435 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
439 out_file = filenames.back();
440 filenames.pop_back();
442 if ( ! picture_coding.HasValue() )
444 picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
452 //------------------------------------------------------------------------------------------
456 Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
457 const ASDCP::Dictionary& dict,
458 ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
459 ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
462 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
463 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
466 write_JP2K_file(CommandOptions& Options)
468 AS_02::PHDR::MXFWriter Writer;
469 AS_02::PHDR::FrameBuffer FrameBuffer(Options.fb_size);
470 AS_02::PHDR::SequenceParser Parser;
472 AESEncContext* Context = 0;
473 HMACContext* HMAC = 0;
474 byte_t IV_buf[CBC_BLOCK_SIZE];
475 Kumu::FortunaRNG RNG;
477 ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
478 ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
480 // set up essence parser
481 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
484 if ( ASDCP_SUCCESS(result) )
486 ASDCP::JP2K::PictureDescriptor PDesc;
487 Parser.FillPictureDescriptor(PDesc);
488 PDesc.EditRate = Options.edit_rate;
490 if ( Options.verbose_flag )
492 fprintf(stderr, "JPEG 2000 P-HDR pictures\n");
493 fputs("PictureDescriptor:\n", stderr);
494 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
495 JP2K::PictureDescriptorDump(PDesc);
498 if ( Options.use_cdci_descriptor )
500 ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
501 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
503 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
504 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
505 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
507 if ( ASDCP_SUCCESS(result) )
509 tmp_dscr->PictureEssenceCoding = Options.picture_coding;
510 tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
511 tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
512 tmp_dscr->ComponentDepth = Options.component_depth;
513 tmp_dscr->FrameLayout = Options.frame_layout;
514 tmp_dscr->AspectRatio = Options.aspect_ratio;
515 tmp_dscr->FieldDominance = Options.field_dominance;
516 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
521 ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
522 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
524 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
525 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
526 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
528 if ( ASDCP_SUCCESS(result) )
530 tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
531 tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
532 tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
533 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
538 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
540 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
541 Info.LabelSetType = LS_MXF_SMPTE;
543 if ( Options.asset_id_flag )
544 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
546 Kumu::GenRandomUUID(Info.AssetUUID);
548 // configure encryption
549 if( Options.key_flag )
551 Kumu::GenRandomUUID(Info.ContextID);
552 Info.EncryptedEssence = true;
554 if ( Options.key_id_flag )
555 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
557 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
559 Context = new AESEncContext;
560 result = Context->InitKey(Options.key_value);
562 if ( ASDCP_SUCCESS(result) )
563 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
565 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
567 Info.UsesHMAC = true;
568 HMAC = new HMACContext;
569 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
573 if ( ASDCP_SUCCESS(result) )
575 result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
576 Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
580 if ( ASDCP_SUCCESS(result) )
583 result = Parser.Reset();
585 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
587 result = Parser.ReadFrame(FrameBuffer);
589 if ( ASDCP_SUCCESS(result) )
591 if ( Options.verbose_flag )
592 FrameBuffer.Dump(stderr, Options.fb_dump_size);
594 if ( Options.encrypt_header_flag )
595 FrameBuffer.PlaintextOffset(0);
598 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
600 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
602 // The Writer class will forward the last block of ciphertext
603 // to the encryption context for use as the IV for the next
604 // frame. If you want to use non-sequitur IV values, un-comment
605 // the following line of code.
606 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
607 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
611 if ( result == RESULT_ENDOFFILE )
615 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
616 result = Writer.Finalize(Options.PHDR_master_metadata);
624 main(int argc, const char** argv)
626 Result_t result = RESULT_OK;
628 g_dict = &ASDCP::DefaultSMPTEDict();
631 CommandOptions Options(argc, argv);
633 if ( Options.version_flag )
636 if ( Options.help_flag )
639 if ( Options.show_ul_values_flag )
641 g_dict->Dump(stdout);
644 if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
647 if ( Options.error_flag )
649 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
653 EssenceType_t EssenceType;
654 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
656 if ( ASDCP_SUCCESS(result) )
658 switch ( EssenceType )
661 result = write_JP2K_file(Options);
665 fprintf(stderr, "%s: Unknown file type, not P-HDR-compatible essence.\n",
666 Options.filenames.front().c_str());
671 if ( ASDCP_FAILURE(result) )
673 fputs("Program stopped on error.\n", stderr);
675 if ( result != RESULT_FAIL )
677 fputs(result, stderr);