2 Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, 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 as-02-wrap.cpp
29 \brief AS-02 file manipulation utility
31 This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
33 For more information about AS-02, please refer to the header file AS_02.h
34 For more information about asdcplib, please refer to the header file AS_DCP.h
37 #include <KM_fileio.h>
40 #include <PCMParserList.h>
43 using namespace ASDCP;
45 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
49 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
51 snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
55 //------------------------------------------------------------------------------------------
57 // command line option parser class
59 static const char* PROGRAM_NAME = "as-02-wrap"; // program name for messages
61 // local program identification info written to file headers
62 class MyInfo : public WriterInfo
67 static byte_t default_ProductUUID_Data[UUIDlen] =
68 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
69 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
71 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
72 CompanyName = "WidgetCo";
73 ProductName = "as-02-wrap";
74 ProductVersion = ASDCP::Version();
80 // Increment the iterator, test for an additional non-option command line argument.
81 // Causes the caller to return if there are no remaining arguments or if the next
82 // argument begins with '-'.
83 #define TEST_EXTRA_ARG(i,c) \
84 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
85 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
91 banner(FILE* stream = stdout)
95 Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
96 asdcplib may be copied only under the terms of the license found at\n\
97 the top of every file in the asdcplib distribution kit.\n\n\
98 Specify the -h (help) option for further information about %s\n\n",
99 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
104 usage(FILE* stream = stdout)
107 USAGE: %s [-h|-help] [-V]\n\
109 %s [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
110 [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
111 [-M] [-p <n>/<d>] [-s <seconds>] [-v] [-W]\n\
112 [-z|-Z] <input-file>+ <output-file>\n\n",
113 PROGRAM_NAME, PROGRAM_NAME);
117 -C <UL> - Set ChannelAssignment UL value\n\
118 -h | -help - Show help\n\
119 -V - Show version information\n\
120 -e - Encrypt JP2K headers (default)\n\
121 -E - Do not encrypt JP2K headers\n\
122 -j <key-id-str> - Write key ID instead of creating a random value\n\
123 -k <key-string> - Use key for ciphertext operations\n\
124 -M - Do not create HMAC values when writing\n\
125 -a <UUID> - Specify the Asset ID of the file\n\
126 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
127 Defaults to 4,194,304 (4MB)\n\
128 -d <duration> - Number of frames to process, default all\n\
129 -f <start-frame> - Starting frame number, default 0\n\
130 -p <n>/<d> - Edit Rate of the output file. 24/1 is the default\n\
131 -s <seconds> - Duration of a frame-wrapped partition (default 60)\n\
132 -v - Verbose, prints informative messages to stderr\n\
133 -W - Read input file only, do not write source file\n\
134 -z - Fail if j2c inputs have unequal parameters (default)\n\
135 -Z - Ignore unequal parameters in j2c inputs\n\
137 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
138 o All option arguments must be separated from the option by whitespace.\n\n");
148 bool error_flag; // true if the given options are in error or not complete
149 bool key_flag; // true if an encryption key was given
150 bool asset_id_flag; // true if an asset ID was given
151 bool encrypt_header_flag; // true if j2c headers are to be encrypted
152 bool write_hmac; // true if HMAC values are to be generated and written
153 bool verbose_flag; // true if the verbose option was selected
154 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
155 bool no_write_flag; // true if no output files are to be written
156 bool version_flag; // true if the version display option was selected
157 bool help_flag; // true if the help display option was selected
158 ui32_t start_frame; // frame number to begin processing
159 ui32_t duration; // number of frames to be processed
160 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
161 Rational edit_rate; // edit rate of JP2K sequence
162 ui32_t fb_size; // size of picture frame buffer
163 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
164 bool key_id_flag; // true if a key ID was given
165 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
166 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
167 std::string out_file; //
168 bool show_ul_values; /// if true, dump the UL table before going tp work.
169 Kumu::PathList_t filenames; // list of filenames to be processed
170 UL channel_assignment;
172 //new attributes for AS-02 support
173 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
174 ui32_t partition_space; //Shim parameter partition_spacing
177 CommandOptions(int argc, const char** argv) :
178 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
179 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
180 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
181 duration(0xffffffff), j2c_pedantic(true), edit_rate(30,1), fb_size(FRAME_BUFFER_SIZE),
182 show_ul_values(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60)
184 memset(key_value, 0, KeyLen);
185 memset(key_id_value, 0, UUIDlen);
187 for ( int i = 1; i < argc; i++ )
190 if ( (strcmp( argv[i], "-help") == 0) )
196 if ( argv[i][0] == '-'
197 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
200 switch ( argv[i][1] )
203 asset_id_flag = true;
204 TEST_EXTRA_ARG(i, 'a');
207 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
209 if ( length != UUIDlen )
211 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
218 TEST_EXTRA_ARG(i, 'b');
219 fb_size = abs(atoi(argv[i]));
222 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
227 TEST_EXTRA_ARG(i, 'U');
228 if ( ! channel_assignment.DecodeHex(argv[i]) )
230 fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
236 TEST_EXTRA_ARG(i, 'd');
237 duration = abs(atoi(argv[i]));
240 case 'E': encrypt_header_flag = false; break;
241 case 'e': encrypt_header_flag = true; break;
244 TEST_EXTRA_ARG(i, 'f');
245 start_frame = abs(atoi(argv[i]));
248 case 'h': help_flag = true; break;
250 case 'j': key_id_flag = true;
251 TEST_EXTRA_ARG(i, 'j');
254 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
256 if ( length != UUIDlen )
258 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
264 case 'k': key_flag = true;
265 TEST_EXTRA_ARG(i, 'k');
268 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
270 if ( length != KeyLen )
272 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
278 case 'M': write_hmac = false; break;
281 TEST_EXTRA_ARG(i, 'p');
282 /// TODO: VERY BROKEN, WANT RATIONAL
283 edit_rate.Numerator = abs(atoi(argv[i]));
284 edit_rate.Denominator = 1;
288 TEST_EXTRA_ARG(i, 's');
289 partition_space = abs(atoi(argv[i]));
292 case 'V': version_flag = true; break;
293 case 'v': verbose_flag = true; break;
294 case 'W': no_write_flag = true; break;
295 case 'Z': j2c_pedantic = false; break;
296 case 'z': j2c_pedantic = true; break;
299 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
306 if ( argv[i][0] != '-' )
308 filenames.push_back(argv[i]);
312 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
318 if ( help_flag || version_flag )
321 if ( filenames.size() < 2 )
323 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
327 out_file = filenames.back();
328 filenames.pop_back();
334 //------------------------------------------------------------------------------------------
337 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
338 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
341 write_JP2K_file(CommandOptions& Options)
343 AESEncContext* Context = 0;
344 HMACContext* HMAC = 0;
345 AS_02::JP2K::MXFWriter Writer;
346 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
347 JP2K::PictureDescriptor PDesc;
348 JP2K::SequenceParser Parser;
349 byte_t IV_buf[CBC_BLOCK_SIZE];
350 Kumu::FortunaRNG RNG;
352 // set up essence parser
353 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
356 if ( ASDCP_SUCCESS(result) )
358 Parser.FillPictureDescriptor(PDesc);
359 PDesc.EditRate = Options.edit_rate;
361 if ( Options.verbose_flag )
363 fprintf(stderr, "JPEG 2000 pictures\n");
364 fputs("PictureDescriptor:\n", stderr);
365 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
366 JP2K::PictureDescriptorDump(PDesc);
370 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
372 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
373 Info.LabelSetType = LS_MXF_SMPTE;
375 if ( Options.asset_id_flag )
376 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
378 Kumu::GenRandomUUID(Info.AssetUUID);
380 // configure encryption
381 if( Options.key_flag )
383 Kumu::GenRandomUUID(Info.ContextID);
384 Info.EncryptedEssence = true;
386 if ( Options.key_id_flag )
387 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
389 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
391 Context = new AESEncContext;
392 result = Context->InitKey(Options.key_value);
394 if ( ASDCP_SUCCESS(result) )
395 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
397 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
399 Info.UsesHMAC = true;
400 HMAC = new HMACContext;
401 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
405 if ( ASDCP_SUCCESS(result) )
406 result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc, Options.index_strategy, Options.partition_space);
409 if ( ASDCP_SUCCESS(result) )
412 result = Parser.Reset();
414 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
416 result = Parser.ReadFrame(FrameBuffer);
418 if ( ASDCP_SUCCESS(result) )
420 if ( Options.verbose_flag )
421 FrameBuffer.Dump(stderr, Options.fb_dump_size);
423 if ( Options.encrypt_header_flag )
424 FrameBuffer.PlaintextOffset(0);
427 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
429 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
431 // The Writer class will forward the last block of ciphertext
432 // to the encryption context for use as the IV for the next
433 // frame. If you want to use non-sequitur IV values, un-comment
434 // the following line of code.
435 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
436 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
440 if ( result == RESULT_ENDOFFILE )
444 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
445 result = Writer.Finalize();
450 //------------------------------------------------------------------------------------------
454 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
455 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
458 write_PCM_file(CommandOptions& Options)
460 AESEncContext* Context = 0;
461 HMACContext* HMAC = 0;
462 PCMParserList Parser;
463 AS_02::PCM::MXFWriter Writer;
464 PCM::FrameBuffer FrameBuffer;
465 PCM::AudioDescriptor ADesc;
466 byte_t IV_buf[CBC_BLOCK_SIZE];
467 Kumu::FortunaRNG RNG;
469 // set up essence parser
470 Result_t result = Parser.OpenRead(Options.filenames, Rational(1, 1));
473 if ( ASDCP_SUCCESS(result) )
475 Parser.FillAudioDescriptor(ADesc);
477 ADesc.EditRate = Options.edit_rate;
478 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
480 if ( Options.verbose_flag )
483 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
484 ADesc.AudioSamplingRate.Quotient() / 1000.0,
485 RationalToString(Options.edit_rate, buf, 64),
486 PCM::CalcSamplesPerFrame(ADesc));
487 fputs("AudioDescriptor:\n", stderr);
488 PCM::AudioDescriptorDump(ADesc);
492 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
494 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
495 Info.LabelSetType = LS_MXF_SMPTE;
497 if ( Options.asset_id_flag )
498 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
500 Kumu::GenRandomUUID(Info.AssetUUID);
502 // configure encryption
503 if( Options.key_flag )
505 Kumu::GenRandomUUID(Info.ContextID);
506 Info.EncryptedEssence = true;
508 if ( Options.key_id_flag )
509 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
511 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
513 Context = new AESEncContext;
514 result = Context->InitKey(Options.key_value);
516 if ( ASDCP_SUCCESS(result) )
517 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
519 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
521 Info.UsesHMAC = true;
522 HMAC = new HMACContext;
523 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
527 if ( ASDCP_SUCCESS(result) )
528 result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
530 if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
532 MXF::WaveAudioDescriptor *descriptor = 0;
533 Writer.OP1aHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
534 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
535 descriptor->ChannelAssignment = Options.channel_assignment;
539 if ( ASDCP_SUCCESS(result) )
541 result = Parser.Reset();
544 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
546 result = Parser.ReadFrame(FrameBuffer);
548 if ( ASDCP_SUCCESS(result) )
550 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
552 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
553 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
554 result = RESULT_ENDOFFILE;
558 if ( Options.verbose_flag )
559 FrameBuffer.Dump(stderr, Options.fb_dump_size);
561 if ( ! Options.no_write_flag )
563 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
565 // The Writer class will forward the last block of ciphertext
566 // to the encryption context for use as the IV for the next
567 // frame. If you want to use non-sequitur IV values, un-comment
568 // the following line of code.
569 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
570 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
575 if ( result == RESULT_ENDOFFILE )
579 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
580 result = Writer.Finalize();
588 main(int argc, const char** argv)
590 Result_t result = RESULT_OK;
592 CommandOptions Options(argc, argv);
594 if ( Options.version_flag )
597 if ( Options.help_flag )
600 if ( Options.version_flag || Options.help_flag )
603 if ( Options.error_flag )
605 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
609 if ( Options.show_ul_values )
611 DefaultSMPTEDict().Dump(stdout);
614 EssenceType_t EssenceType;
615 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
617 if ( ASDCP_SUCCESS(result) )
619 switch ( EssenceType )
622 result = write_JP2K_file(Options);
625 case ESS_PCM_24b_48k:
626 case ESS_PCM_24b_96k:
627 result = write_PCM_file(Options);
631 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
632 Options.filenames.front().c_str());
637 if ( ASDCP_FAILURE(result) )
639 fputs("Program stopped on error.\n", stderr);
641 if ( result != RESULT_FAIL )
643 fputs(result, stderr);
655 // end as-02-wrap.cpp