2 Copyright (c) 2003-2012, 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-test.cpp
29 \brief AS-DCP file manipulation utility
31 This program provides command line access to the major features of the asdcplib
32 library, and serves as a library unit test which provides the functionality of
33 the supported use cases.
35 For more information about asdcplib, please refer to the header file AS_DCP.h
37 WARNING: While the asdcplib library attempts to provide a complete and secure
38 implementation of the cryptographic features of the AS-DCP file formats, this
39 unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40 production environment without some modification.
42 In particular, this program uses weak IV generation and externally generated
43 plaintext keys. These shortcomings exist because cryptographic-quality
44 random number generation and key management are outside the scope of the
45 asdcplib library. Developers using asdcplib for commercial implementations
46 claiming SMPTE conformance are expected to provide proper implementations of
50 #include <KM_fileio.h>
52 #include <PCMParserList.h>
53 #include <WavFileWriter.h>
56 #include <openssl/sha.h>
61 using namespace ASDCP;
63 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
65 //------------------------------------------------------------------------------------------
67 // command line option parser class
69 static const char* PROGRAM_NAME = "asdcp-test"; // program name for messages
70 const ui32_t MAX_IN_FILES = 16; // maximum number of input files handled by
71 // the command option parser
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
79 static byte_t default_ProductUUID_Data[UUIDlen] =
80 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
83 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84 CompanyName = "WidgetCo";
85 ProductName = "asdcp-test";
86 ProductVersion = ASDCP::Version();
92 // Increment the iterator, test for an additional non-option command line argument.
93 // Causes the caller to return if there are no remaining arguments or if the next
94 // argument begins with '-'.
95 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
97 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
102 banner(FILE* stream = stdout)
105 %s (asdcplib %s)\n\n\
106 Copyright (c) 2003-2012 John Hurst\n\n\
107 asdcplib may be copied only under the terms of the license found at\n\
108 the top of every file in the asdcplib distribution kit.\n\n\
109 Specify the -h (help) option for further information about %s\n\n",
110 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
115 usage(FILE* stream = stdout)
118 USAGE: %s -c <output-file> [-3] [-a <uuid>] [-b <buffer-size>]\n\
119 [-d <duration>] [-e|-E] [-f <start-frame>] [-j <key-id-string>]\n\
120 [-k <key-string>] [-l <label>] [-L] [-M] [-p <frame-rate>] [-R]\n\
121 [-s <num>] [-v] [-W] [-z|-Z] <input-file> [<input-file-2> ...]\n\
123 %s [-h|-help] [-V]\n\
125 %s -i [-H] [-n] [-v] <input-file>\n\
129 %s -G [-v] <input-file>\n\
131 %s -t <input-file>\n\
133 %s -x <file-prefix> [-3] [-b <buffer-size>] [-d <duration>]\n\
134 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
135 [-v] [-W] [-w] <input-file>\n\n",
136 PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME,
137 PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
141 -3 - With -c, create a stereoscopic image file. Expects two\n\
142 directories of JP2K codestreams (directories must have\n\
143 an equal number of frames; left eye is first).\n\
144 - With -x, force stereoscopic interpretation of a JP2K\n\
146 -c <output-file> - Create an AS-DCP track file from input(s)\n\
147 -g - Generate a random 16 byte value to stdout\n\
148 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
149 -h | -help - Show help\n\
150 -i - Show file info\n\
151 -t - Calculate message digest of input file\n\
152 -U - Dump UL catalog to stdout\n\
153 -u - Generate a random UUID value to stdout\n\
154 -V - Show version information\n\
155 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
160 -e - Encrypt MPEG or JP2K headers (default)\n\
161 -E - Do not encrypt MPEG or JP2K headers\n\
162 -j <key-id-str> - Write key ID instead of creating a random value\n\
163 -k <key-string> - Use key for ciphertext operations\n\
164 -m - verify HMAC values when reading\n\
165 -M - Do not create HMAC values when writing\n\
169 Read/Write Options:\n\
170 -a <UUID> - Specify the Asset ID of a file (with -c)\n\
171 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
172 Defaults to 4,194,304 (4MB)\n\
173 -d <duration> - Number of frames to process, default all\n\
174 -f <start-frame> - Starting frame number, default 0\n\
175 -l <label> - Use given channel format label when writing MXF sound\n\
176 files. SMPTE 429-2 labels: '5.1', '6.1', '7.1', '7.1DS', 'WTF'.\n\
177 Default is no label (valid for Interop only).\n\
178 -L - Write SMPTE UL values instead of MXF Interop\n\
179 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
180 Use one of [23|24|25|30|48|50|60], 24 is default\n\
181 -R - Repeat the first frame over the entire file (picture\n\
182 essence only, requires -c, -d)\n\
183 -S - Split Wave essence to stereo WAV files during extract.\n\
184 Default is multichannel WAV\n\
185 -1 - Split Wave essence to mono WAV files during extract.\n\
186 Default is multichannel WAV\n\
187 -W - Read input file only, do not write source file\n\
188 -w <width> - Width of numeric element in a series of frame file names\n\
189 (use with -x, default 6).\n\
190 -z - Fail if j2c inputs have unequal parameters (default)\n\
191 -Z - Ignore unequal parameters in j2c inputs\n\
196 -H - Show MXF header metadata, used with option -i\n\
197 -n - Show index, used with option -i\n\
200 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
201 stderr, used with option -v\n\
202 -v - Verbose, prints informative messages to stderr\n\
204 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
205 o All option arguments must be separated from the option by whitespace.\n\
206 o An argument of \"23\" to the -p option will be interpreted\n\
207 as 24000/1001 fps.\n\
227 decode_channel_fmt(const std::string& label_name)
229 if ( label_name == "5.1" )
230 return PCM::CF_CFG_1;
232 else if ( label_name == "6.1" )
233 return PCM::CF_CFG_2;
235 else if ( label_name == "7.1" )
236 return PCM::CF_CFG_3;
238 else if ( label_name == "WTF" )
239 return PCM::CF_CFG_4;
241 else if ( label_name == "7.1DS" )
242 return PCM::CF_CFG_5;
244 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
245 fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
257 bool error_flag; // true if the given options are in error or not complete
258 bool key_flag; // true if an encryption key was given
259 bool key_id_flag; // true if a key ID was given
260 bool asset_id_flag; // true if an asset ID was given
261 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
262 bool write_hmac; // true if HMAC values are to be generated and written
263 bool read_hmac; // true if HMAC values are to be validated
264 bool split_wav; // true if PCM is to be extracted to stereo WAV files
265 bool mono_wav; // true if PCM is to be extracted to mono WAV files
266 bool verbose_flag; // true if the verbose option was selected
267 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
268 bool showindex_flag; // true if index is to be displayed
269 bool showheader_flag; // true if MXF file header is to be displayed
270 bool no_write_flag; // true if no output files are to be written
271 bool version_flag; // true if the version display option was selected
272 bool help_flag; // true if the help display option was selected
273 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
274 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
275 ui32_t start_frame; // frame number to begin processing
276 ui32_t duration; // number of frames to be processed
277 bool duration_flag; // true if duration argument given
278 bool do_repeat; // if true and -c -d, repeat first input frame
279 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
280 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
281 ui32_t picture_rate; // fps of picture when wrapping PCM
282 ui32_t fb_size; // size of picture frame buffer
283 ui32_t file_count; // number of elements in filenames[]
284 const char* file_root; // filename pre for files written by the extract mode
285 const char* out_file; // name of mxf file created by create mode
286 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
287 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
288 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
289 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
290 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
293 Rational PictureRate()
295 if ( picture_rate == 16 ) return EditRate_16;
296 if ( picture_rate == 18 ) return EditRate_18;
297 if ( picture_rate == 20 ) return EditRate_20;
298 if ( picture_rate == 22 ) return EditRate_22;
299 if ( picture_rate == 23 ) return EditRate_23_98;
300 if ( picture_rate == 24 ) return EditRate_24;
301 if ( picture_rate == 25 ) return EditRate_25;
302 if ( picture_rate == 30 ) return EditRate_30;
303 if ( picture_rate == 48 ) return EditRate_48;
304 if ( picture_rate == 50 ) return EditRate_50;
305 if ( picture_rate == 60 ) return EditRate_60;
306 if ( picture_rate == 96 ) return EditRate_96;
307 if ( picture_rate == 100 ) return EditRate_100;
308 if ( picture_rate == 120 ) return EditRate_120;
313 const char* szPictureRate()
315 if ( picture_rate == 16 ) return "16";
316 if ( picture_rate == 18 ) return "18.182";
317 if ( picture_rate == 20 ) return "20";
318 if ( picture_rate == 22 ) return "21.818";
319 if ( picture_rate == 23 ) return "23.976";
320 if ( picture_rate == 24 ) return "24";
321 if ( picture_rate == 25 ) return "25";
322 if ( picture_rate == 30 ) return "30";
323 if ( picture_rate == 48 ) return "48";
324 if ( picture_rate == 50 ) return "50";
325 if ( picture_rate == 60 ) return "60";
326 if ( picture_rate == 96 ) return "96";
327 if ( picture_rate == 100 ) return "100";
328 if ( picture_rate == 120 ) return "120";
333 CommandOptions(int argc, const char** argv) :
334 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
335 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
336 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
337 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
338 number_width(6), start_frame(0),
339 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
340 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
341 channel_fmt(PCM::CF_NONE)
343 memset(key_value, 0, KeyLen);
344 memset(key_id_value, 0, UUIDlen);
346 for ( int i = 1; i < argc; i++ )
349 if ( (strcmp( argv[i], "-help") == 0) )
355 if ( argv[i][0] == '-'
356 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
359 switch ( argv[i][1] )
361 case '1': mono_wav = true; break;
362 case '2': split_wav = true; break;
363 case '3': stereo_image_flag = true; break;
366 asset_id_flag = true;
367 TEST_EXTRA_ARG(i, 'a');
370 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
372 if ( length != UUIDlen )
374 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
381 TEST_EXTRA_ARG(i, 'b');
382 fb_size = abs(atoi(argv[i]));
385 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
390 TEST_EXTRA_ARG(i, 'c');
396 TEST_EXTRA_ARG(i, 'd');
397 duration_flag = true;
398 duration = abs(atoi(argv[i]));
401 case 'E': encrypt_header_flag = false; break;
402 case 'e': encrypt_header_flag = true; break;
405 TEST_EXTRA_ARG(i, 'f');
406 start_frame = abs(atoi(argv[i]));
409 case 'G': mode = MMT_GOP_START; break;
410 case 'g': mode = MMT_GEN_KEY; break;
411 case 'H': showheader_flag = true; break;
412 case 'h': help_flag = true; break;
413 case 'i': mode = MMT_INFO; break;
415 case 'j': key_id_flag = true;
416 TEST_EXTRA_ARG(i, 'j');
419 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
421 if ( length != UUIDlen )
423 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
429 case 'k': key_flag = true;
430 TEST_EXTRA_ARG(i, 'k');
433 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
435 if ( length != KeyLen )
437 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
444 TEST_EXTRA_ARG(i, 'l');
445 channel_fmt = decode_channel_fmt(argv[i]);
448 case 'L': use_smpte_labels = true; break;
449 case 'M': write_hmac = false; break;
450 case 'm': read_hmac = true; break;
451 case 'n': showindex_flag = true; break;
454 TEST_EXTRA_ARG(i, 'p');
455 picture_rate = abs(atoi(argv[i]));
458 case 'R': do_repeat = true; break;
459 case 'S': split_wav = true; break;
462 TEST_EXTRA_ARG(i, 's');
463 fb_dump_size = abs(atoi(argv[i]));
466 case 't': mode = MMT_DIGEST; break;
467 case 'U': mode = MMT_UL_LIST; break;
468 case 'u': mode = MMT_GEN_ID; break;
469 case 'V': version_flag = true; break;
470 case 'v': verbose_flag = true; break;
471 case 'W': no_write_flag = true; break;
474 TEST_EXTRA_ARG(i, 'w');
475 number_width = abs(atoi(argv[i]));
479 TEST_EXTRA_ARG(i, 'x');
484 case 'Z': j2c_pedantic = false; break;
485 case 'z': j2c_pedantic = true; break;
488 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
495 if ( argv[i][0] != '-' )
497 filenames[file_count++] = argv[i];
501 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
505 if ( file_count >= MAX_IN_FILES )
507 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
513 if ( help_flag || version_flag )
516 if ( ( mode == MMT_INFO
517 || mode == MMT_CREATE
518 || mode == MMT_EXTRACT
519 || mode == MMT_GOP_START
520 || mode == MMT_DIGEST ) && file_count == 0 )
522 fputs("Option requires at least one filename argument.\n", stderr);
526 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
528 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
536 //------------------------------------------------------------------------------------------
539 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
540 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
543 write_MPEG2_file(CommandOptions& Options)
545 AESEncContext* Context = 0;
546 HMACContext* HMAC = 0;
547 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
548 MPEG2::Parser Parser;
549 MPEG2::MXFWriter Writer;
550 MPEG2::VideoDescriptor VDesc;
551 byte_t IV_buf[CBC_BLOCK_SIZE];
552 Kumu::FortunaRNG RNG;
554 // set up essence parser
555 Result_t result = Parser.OpenRead(Options.filenames[0]);
558 if ( ASDCP_SUCCESS(result) )
560 Parser.FillVideoDescriptor(VDesc);
562 if ( Options.verbose_flag )
564 fputs("MPEG-2 Pictures\n", stderr);
565 fputs("VideoDescriptor:\n", stderr);
566 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
567 MPEG2::VideoDescriptorDump(VDesc);
571 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
573 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
574 if ( Options.asset_id_flag )
575 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
577 Kumu::GenRandomUUID(Info.AssetUUID);
579 if ( Options.use_smpte_labels )
581 Info.LabelSetType = LS_MXF_SMPTE;
582 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
585 // configure encryption
586 if( Options.key_flag )
588 Kumu::GenRandomUUID(Info.ContextID);
589 Info.EncryptedEssence = true;
591 if ( Options.key_id_flag )
592 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
594 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
596 Context = new AESEncContext;
597 result = Context->InitKey(Options.key_value);
599 if ( ASDCP_SUCCESS(result) )
600 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
602 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
604 Info.UsesHMAC = true;
605 HMAC = new HMACContext;
606 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
610 if ( ASDCP_SUCCESS(result) )
611 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
614 if ( ASDCP_SUCCESS(result) )
615 // loop through the frames
617 result = Parser.Reset();
620 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
622 if ( ! Options.do_repeat || duration == 1 )
624 result = Parser.ReadFrame(FrameBuffer);
626 if ( ASDCP_SUCCESS(result) )
628 if ( Options.verbose_flag )
629 FrameBuffer.Dump(stderr, Options.fb_dump_size);
631 if ( Options.encrypt_header_flag )
632 FrameBuffer.PlaintextOffset(0);
636 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
638 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
640 // The Writer class will forward the last block of ciphertext
641 // to the encryption context for use as the IV for the next
642 // frame. If you want to use non-sequitur IV values, un-comment
643 // the following line of code.
644 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
645 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
649 if ( result == RESULT_ENDOFFILE )
653 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
654 result = Writer.Finalize();
659 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
660 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
661 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
664 read_MPEG2_file(CommandOptions& Options)
666 AESDecContext* Context = 0;
667 HMACContext* HMAC = 0;
668 MPEG2::MXFReader Reader;
669 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
670 Kumu::FileWriter OutFile;
671 ui32_t frame_count = 0;
673 Result_t result = Reader.OpenRead(Options.filenames[0]);
675 if ( ASDCP_SUCCESS(result) )
677 MPEG2::VideoDescriptor VDesc;
678 Reader.FillVideoDescriptor(VDesc);
679 frame_count = VDesc.ContainerDuration;
681 if ( Options.verbose_flag )
683 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
684 MPEG2::VideoDescriptorDump(VDesc);
688 if ( ASDCP_SUCCESS(result) )
691 snprintf(filename, 256, "%s.ves", Options.file_root);
692 result = OutFile.OpenWrite(filename);
695 if ( ASDCP_SUCCESS(result) && Options.key_flag )
697 Context = new AESDecContext;
698 result = Context->InitKey(Options.key_value);
700 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
703 Reader.FillWriterInfo(Info);
707 HMAC = new HMACContext;
708 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
712 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
717 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
718 if ( last_frame > frame_count )
719 last_frame = frame_count;
721 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
723 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
725 if ( ASDCP_SUCCESS(result) )
727 if ( Options.verbose_flag )
728 FrameBuffer.Dump(stderr, Options.fb_dump_size);
730 ui32_t write_count = 0;
731 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
741 gop_start_test(CommandOptions& Options)
743 using namespace ASDCP::MPEG2;
746 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
747 ui32_t frame_count = 0;
749 Result_t result = Reader.OpenRead(Options.filenames[0]);
751 if ( ASDCP_SUCCESS(result) )
753 MPEG2::VideoDescriptor VDesc;
754 Reader.FillVideoDescriptor(VDesc);
755 frame_count = VDesc.ContainerDuration;
757 if ( Options.verbose_flag )
759 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
760 MPEG2::VideoDescriptorDump(VDesc);
764 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
765 if ( last_frame > frame_count )
766 last_frame = frame_count;
768 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
770 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
772 if ( ASDCP_SUCCESS(result) )
774 if ( Options.verbose_flag )
775 FrameBuffer.Dump(stderr, Options.fb_dump_size);
777 if ( FrameBuffer.FrameType() != FRAME_I )
778 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
780 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
787 //------------------------------------------------------------------------------------------
790 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
791 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
794 write_JP2K_S_file(CommandOptions& Options)
796 AESEncContext* Context = 0;
797 HMACContext* HMAC = 0;
798 JP2K::MXFSWriter Writer;
799 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
800 JP2K::PictureDescriptor PDesc;
801 JP2K::SequenceParser ParserLeft, ParserRight;
802 byte_t IV_buf[CBC_BLOCK_SIZE];
803 Kumu::FortunaRNG RNG;
805 if ( Options.file_count != 2 )
807 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
811 // set up essence parser
812 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
814 if ( ASDCP_SUCCESS(result) )
815 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
818 if ( ASDCP_SUCCESS(result) )
820 ParserLeft.FillPictureDescriptor(PDesc);
821 PDesc.EditRate = Options.PictureRate();
823 if ( Options.verbose_flag )
825 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
826 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
827 JP2K::PictureDescriptorDump(PDesc);
831 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
833 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
834 if ( Options.asset_id_flag )
835 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
837 Kumu::GenRandomUUID(Info.AssetUUID);
839 if ( Options.use_smpte_labels )
841 Info.LabelSetType = LS_MXF_SMPTE;
842 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
845 // configure encryption
846 if( Options.key_flag )
848 Kumu::GenRandomUUID(Info.ContextID);
849 Info.EncryptedEssence = true;
851 if ( Options.key_id_flag )
852 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
854 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
856 Context = new AESEncContext;
857 result = Context->InitKey(Options.key_value);
859 if ( ASDCP_SUCCESS(result) )
860 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
862 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
864 Info.UsesHMAC = true;
865 HMAC = new HMACContext;
866 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
870 if ( ASDCP_SUCCESS(result) )
871 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
874 if ( ASDCP_SUCCESS(result) )
877 result = ParserLeft.Reset();
878 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
880 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
882 result = ParserLeft.ReadFrame(FrameBuffer);
884 if ( ASDCP_SUCCESS(result) )
886 if ( Options.verbose_flag )
887 FrameBuffer.Dump(stderr, Options.fb_dump_size);
889 if ( Options.encrypt_header_flag )
890 FrameBuffer.PlaintextOffset(0);
893 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
894 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
896 if ( ASDCP_SUCCESS(result) )
897 result = ParserRight.ReadFrame(FrameBuffer);
899 if ( ASDCP_SUCCESS(result) )
901 if ( Options.verbose_flag )
902 FrameBuffer.Dump(stderr, Options.fb_dump_size);
904 if ( Options.encrypt_header_flag )
905 FrameBuffer.PlaintextOffset(0);
908 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
909 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
912 if ( result == RESULT_ENDOFFILE )
916 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
917 result = Writer.Finalize();
922 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
923 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
924 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
926 read_JP2K_S_file(CommandOptions& Options)
928 AESDecContext* Context = 0;
929 HMACContext* HMAC = 0;
930 JP2K::MXFSReader Reader;
931 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
932 ui32_t frame_count = 0;
934 Result_t result = Reader.OpenRead(Options.filenames[0]);
936 if ( ASDCP_SUCCESS(result) )
938 JP2K::PictureDescriptor PDesc;
939 Reader.FillPictureDescriptor(PDesc);
941 frame_count = PDesc.ContainerDuration;
943 if ( Options.verbose_flag )
945 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
946 JP2K::PictureDescriptorDump(PDesc);
950 if ( ASDCP_SUCCESS(result) && Options.key_flag )
952 Context = new AESDecContext;
953 result = Context->InitKey(Options.key_value);
955 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
958 Reader.FillWriterInfo(Info);
962 HMAC = new HMACContext;
963 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
967 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
972 const int filename_max = 1024;
973 char filename[filename_max];
974 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
975 if ( last_frame > frame_count )
976 last_frame = frame_count;
978 char left_format[64]; char right_format[64];
979 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
980 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
982 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
984 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
986 if ( ASDCP_SUCCESS(result) )
988 Kumu::FileWriter OutFile;
990 snprintf(filename, filename_max, left_format, Options.file_root, i);
991 result = OutFile.OpenWrite(filename);
993 if ( ASDCP_SUCCESS(result) )
994 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
996 if ( Options.verbose_flag )
997 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1000 if ( ASDCP_SUCCESS(result) )
1001 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
1003 if ( ASDCP_SUCCESS(result) )
1005 Kumu::FileWriter OutFile;
1007 snprintf(filename, filename_max, right_format, Options.file_root, i);
1008 result = OutFile.OpenWrite(filename);
1010 if ( ASDCP_SUCCESS(result) )
1011 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1020 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1021 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1024 write_JP2K_file(CommandOptions& Options)
1026 AESEncContext* Context = 0;
1027 HMACContext* HMAC = 0;
1028 JP2K::MXFWriter Writer;
1029 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1030 JP2K::PictureDescriptor PDesc;
1031 JP2K::SequenceParser Parser;
1032 byte_t IV_buf[CBC_BLOCK_SIZE];
1033 Kumu::FortunaRNG RNG;
1035 // set up essence parser
1036 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1038 // set up MXF writer
1039 if ( ASDCP_SUCCESS(result) )
1041 Parser.FillPictureDescriptor(PDesc);
1042 PDesc.EditRate = Options.PictureRate();
1044 if ( Options.verbose_flag )
1046 fprintf(stderr, "JPEG 2000 pictures\n");
1047 fputs("PictureDescriptor:\n", stderr);
1048 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1049 JP2K::PictureDescriptorDump(PDesc);
1053 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1055 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1056 if ( Options.asset_id_flag )
1057 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1059 Kumu::GenRandomUUID(Info.AssetUUID);
1061 if ( Options.use_smpte_labels )
1063 Info.LabelSetType = LS_MXF_SMPTE;
1064 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1067 // configure encryption
1068 if( Options.key_flag )
1070 Kumu::GenRandomUUID(Info.ContextID);
1071 Info.EncryptedEssence = true;
1073 if ( Options.key_id_flag )
1074 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1076 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1078 Context = new AESEncContext;
1079 result = Context->InitKey(Options.key_value);
1081 if ( ASDCP_SUCCESS(result) )
1082 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1084 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1086 Info.UsesHMAC = true;
1087 HMAC = new HMACContext;
1088 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1092 if ( ASDCP_SUCCESS(result) )
1093 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1096 if ( ASDCP_SUCCESS(result) )
1098 ui32_t duration = 0;
1099 result = Parser.Reset();
1101 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1103 if ( ! Options.do_repeat || duration == 1 )
1105 result = Parser.ReadFrame(FrameBuffer);
1107 if ( ASDCP_SUCCESS(result) )
1109 if ( Options.verbose_flag )
1110 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1112 if ( Options.encrypt_header_flag )
1113 FrameBuffer.PlaintextOffset(0);
1117 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1119 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1121 // The Writer class will forward the last block of ciphertext
1122 // to the encryption context for use as the IV for the next
1123 // frame. If you want to use non-sequitur IV values, un-comment
1124 // the following line of code.
1125 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1126 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1130 if ( result == RESULT_ENDOFFILE )
1134 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1135 result = Writer.Finalize();
1140 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1141 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1142 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1145 read_JP2K_file(CommandOptions& Options)
1147 AESDecContext* Context = 0;
1148 HMACContext* HMAC = 0;
1149 JP2K::MXFReader Reader;
1150 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1151 ui32_t frame_count = 0;
1153 Result_t result = Reader.OpenRead(Options.filenames[0]);
1155 if ( ASDCP_SUCCESS(result) )
1157 JP2K::PictureDescriptor PDesc;
1158 Reader.FillPictureDescriptor(PDesc);
1160 frame_count = PDesc.ContainerDuration;
1162 if ( Options.verbose_flag )
1164 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1165 JP2K::PictureDescriptorDump(PDesc);
1169 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1171 Context = new AESDecContext;
1172 result = Context->InitKey(Options.key_value);
1174 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1177 Reader.FillWriterInfo(Info);
1179 if ( Info.UsesHMAC )
1181 HMAC = new HMACContext;
1182 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1186 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1191 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1192 if ( last_frame > frame_count )
1193 last_frame = frame_count;
1195 char name_format[64];
1196 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1198 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1200 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1202 if ( ASDCP_SUCCESS(result) )
1204 Kumu::FileWriter OutFile;
1207 snprintf(filename, 256, name_format, Options.file_root, i);
1208 result = OutFile.OpenWrite(filename);
1210 if ( ASDCP_SUCCESS(result) )
1211 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1213 if ( Options.verbose_flag )
1214 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1221 //------------------------------------------------------------------------------------------
1225 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1226 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1229 write_PCM_file(CommandOptions& Options)
1231 AESEncContext* Context = 0;
1232 HMACContext* HMAC = 0;
1233 PCMParserList Parser;
1234 PCM::MXFWriter Writer;
1235 PCM::FrameBuffer FrameBuffer;
1236 PCM::AudioDescriptor ADesc;
1237 Rational PictureRate = Options.PictureRate();
1238 byte_t IV_buf[CBC_BLOCK_SIZE];
1239 Kumu::FortunaRNG RNG;
1241 // set up essence parser
1242 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1244 // set up MXF writer
1245 if ( ASDCP_SUCCESS(result) )
1247 Parser.FillAudioDescriptor(ADesc);
1249 ADesc.EditRate = PictureRate;
1250 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1251 ADesc.ChannelFormat = Options.channel_fmt;
1253 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1255 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1258 if ( Options.verbose_flag )
1260 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1261 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1262 Options.szPictureRate(),
1263 PCM::CalcSamplesPerFrame(ADesc));
1264 fputs("AudioDescriptor:\n", stderr);
1265 PCM::AudioDescriptorDump(ADesc);
1269 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1271 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1272 if ( Options.asset_id_flag )
1273 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1275 Kumu::GenRandomUUID(Info.AssetUUID);
1277 if ( Options.use_smpte_labels )
1279 Info.LabelSetType = LS_MXF_SMPTE;
1280 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1283 // configure encryption
1284 if( Options.key_flag )
1286 Kumu::GenRandomUUID(Info.ContextID);
1287 Info.EncryptedEssence = true;
1289 if ( Options.key_id_flag )
1290 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1292 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1294 Context = new AESEncContext;
1295 result = Context->InitKey(Options.key_value);
1297 if ( ASDCP_SUCCESS(result) )
1298 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1300 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1302 Info.UsesHMAC = true;
1303 HMAC = new HMACContext;
1304 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1308 if ( ASDCP_SUCCESS(result) )
1309 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1312 if ( ASDCP_SUCCESS(result) )
1314 result = Parser.Reset();
1315 ui32_t duration = 0;
1317 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1319 result = Parser.ReadFrame(FrameBuffer);
1321 if ( ASDCP_SUCCESS(result) )
1323 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1325 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1326 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1327 result = RESULT_ENDOFFILE;
1331 if ( Options.verbose_flag )
1332 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1334 if ( ! Options.no_write_flag )
1336 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1338 // The Writer class will forward the last block of ciphertext
1339 // to the encryption context for use as the IV for the next
1340 // frame. If you want to use non-sequitur IV values, un-comment
1341 // the following line of code.
1342 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1343 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1348 if ( result == RESULT_ENDOFFILE )
1352 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1353 result = Writer.Finalize();
1358 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1359 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1360 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1363 read_PCM_file(CommandOptions& Options)
1365 AESDecContext* Context = 0;
1366 HMACContext* HMAC = 0;
1367 PCM::MXFReader Reader;
1368 PCM::FrameBuffer FrameBuffer;
1369 WavFileWriter OutWave;
1370 PCM::AudioDescriptor ADesc;
1371 ui32_t last_frame = 0;
1373 Result_t result = Reader.OpenRead(Options.filenames[0]);
1375 if ( ASDCP_SUCCESS(result) )
1377 Reader.FillAudioDescriptor(ADesc);
1379 if ( ADesc.EditRate != EditRate_23_98
1380 && ADesc.EditRate != EditRate_24
1381 && ADesc.EditRate != EditRate_25
1382 && ADesc.EditRate != EditRate_30
1383 && ADesc.EditRate != EditRate_48
1384 && ADesc.EditRate != EditRate_50
1385 && ADesc.EditRate != EditRate_60 )
1386 ADesc.EditRate = Options.PictureRate();
1388 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1390 if ( Options.verbose_flag )
1391 PCM::AudioDescriptorDump(ADesc);
1394 if ( ASDCP_SUCCESS(result) )
1396 last_frame = ADesc.ContainerDuration;
1398 if ( Options.duration > 0 && Options.duration < last_frame )
1399 last_frame = Options.duration;
1401 if ( Options.start_frame > 0 )
1403 if ( Options.start_frame > ADesc.ContainerDuration )
1405 fprintf(stderr, "Start value greater than file duration.\n");
1409 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1412 ADesc.ContainerDuration = last_frame - Options.start_frame;
1413 OutWave.OpenWrite(ADesc, Options.file_root,
1414 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1415 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1418 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1420 Context = new AESDecContext;
1421 result = Context->InitKey(Options.key_value);
1423 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1426 Reader.FillWriterInfo(Info);
1428 if ( Info.UsesHMAC )
1430 HMAC = new HMACContext;
1431 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1435 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1440 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1442 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1444 if ( ASDCP_SUCCESS(result) )
1446 if ( Options.verbose_flag )
1447 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1449 result = OutWave.WriteFrame(FrameBuffer);
1457 //------------------------------------------------------------------------------------------
1458 // TimedText essence
1461 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1462 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1465 write_timed_text_file(CommandOptions& Options)
1467 AESEncContext* Context = 0;
1468 HMACContext* HMAC = 0;
1469 TimedText::DCSubtitleParser Parser;
1470 TimedText::MXFWriter Writer;
1471 TimedText::FrameBuffer FrameBuffer;
1472 TimedText::TimedTextDescriptor TDesc;
1473 byte_t IV_buf[CBC_BLOCK_SIZE];
1474 Kumu::FortunaRNG RNG;
1476 // set up essence parser
1477 Result_t result = Parser.OpenRead(Options.filenames[0]);
1479 // set up MXF writer
1480 if ( ASDCP_SUCCESS(result) )
1482 Parser.FillTimedTextDescriptor(TDesc);
1483 FrameBuffer.Capacity(Options.fb_size);
1485 if ( Options.verbose_flag )
1487 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1488 TimedText::DescriptorDump(TDesc);
1492 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1494 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1495 if ( Options.asset_id_flag )
1496 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1498 Kumu::GenRandomUUID(Info.AssetUUID);
1500 if ( Options.use_smpte_labels )
1502 Info.LabelSetType = LS_MXF_SMPTE;
1503 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1506 // configure encryption
1507 if( Options.key_flag )
1509 Kumu::GenRandomUUID(Info.ContextID);
1510 Info.EncryptedEssence = true;
1512 if ( Options.key_id_flag )
1513 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1515 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1517 Context = new AESEncContext;
1518 result = Context->InitKey(Options.key_value);
1520 if ( ASDCP_SUCCESS(result) )
1521 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1523 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1525 Info.UsesHMAC = true;
1526 HMAC = new HMACContext;
1527 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1531 if ( ASDCP_SUCCESS(result) )
1532 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1535 if ( ASDCP_FAILURE(result) )
1539 TimedText::ResourceList_t::const_iterator ri;
1541 result = Parser.ReadTimedTextResource(XMLDoc);
1543 if ( ASDCP_SUCCESS(result) )
1544 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1546 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1548 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1550 if ( ASDCP_SUCCESS(result) )
1552 if ( Options.verbose_flag )
1553 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1555 if ( ! Options.no_write_flag )
1557 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1559 // The Writer class will forward the last block of ciphertext
1560 // to the encryption context for use as the IV for the next
1561 // frame. If you want to use non-sequitur IV values, un-comment
1562 // the following line of code.
1563 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1564 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1568 if ( result == RESULT_ENDOFFILE )
1572 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1573 result = Writer.Finalize();
1579 // Read one or more timed text streams from a plaintext ASDCP file
1580 // Read one or more timed text streams from a ciphertext ASDCP file
1581 // Read one or more timed text streams from a ciphertext ASDCP file
1584 read_timed_text_file(CommandOptions& Options)
1586 AESDecContext* Context = 0;
1587 HMACContext* HMAC = 0;
1588 TimedText::MXFReader Reader;
1589 TimedText::FrameBuffer FrameBuffer;
1590 TimedText::TimedTextDescriptor TDesc;
1592 Result_t result = Reader.OpenRead(Options.filenames[0]);
1594 if ( ASDCP_SUCCESS(result) )
1596 Reader.FillTimedTextDescriptor(TDesc);
1597 FrameBuffer.Capacity(Options.fb_size);
1599 if ( Options.verbose_flag )
1600 TimedText::DescriptorDump(TDesc);
1603 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1605 Context = new AESDecContext;
1606 result = Context->InitKey(Options.key_value);
1608 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1611 Reader.FillWriterInfo(Info);
1613 if ( Info.UsesHMAC )
1615 HMAC = new HMACContext;
1616 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1620 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1625 if ( ASDCP_FAILURE(result) )
1629 std::string out_path = Kumu::PathDirname(Options.file_root);
1632 TimedText::ResourceList_t::const_iterator ri;
1634 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1636 if ( ASDCP_SUCCESS(result) )
1638 Kumu::FileWriter Writer;
1639 result = Writer.OpenWrite(Options.file_root);
1641 if ( ASDCP_SUCCESS(result) )
1642 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1645 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1647 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1649 if ( ASDCP_SUCCESS(result) )
1651 Kumu::FileWriter Writer;
1652 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1654 if ( ASDCP_SUCCESS(result) )
1656 if ( Options.verbose_flag )
1657 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1659 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1667 //------------------------------------------------------------------------------------------
1671 // These classes wrap the irregular names in the asdcplib API
1672 // so that I can use a template to simplify the implementation
1673 // of show_file_info()
1675 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1678 void FillDescriptor(MPEG2::MXFReader& Reader) {
1679 Reader.FillVideoDescriptor(*this);
1682 void Dump(FILE* stream) {
1683 MPEG2::VideoDescriptorDump(*this, stream);
1687 class MyPictureDescriptor : public JP2K::PictureDescriptor
1690 void FillDescriptor(JP2K::MXFReader& Reader) {
1691 Reader.FillPictureDescriptor(*this);
1694 void Dump(FILE* stream) {
1695 JP2K::PictureDescriptorDump(*this, stream);
1699 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1702 void FillDescriptor(JP2K::MXFSReader& Reader) {
1703 Reader.FillPictureDescriptor(*this);
1706 void Dump(FILE* stream) {
1707 JP2K::PictureDescriptorDump(*this, stream);
1711 class MyAudioDescriptor : public PCM::AudioDescriptor
1714 void FillDescriptor(PCM::MXFReader& Reader) {
1715 Reader.FillAudioDescriptor(*this);
1718 void Dump(FILE* stream) {
1719 PCM::AudioDescriptorDump(*this, stream);
1723 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1726 void FillDescriptor(TimedText::MXFReader& Reader) {
1727 Reader.FillTimedTextDescriptor(*this);
1730 void Dump(FILE* stream) {
1731 TimedText::DescriptorDump(*this, stream);
1735 // MSVC didn't like the function template, so now it's a static class method
1736 template<class ReaderT, class DescriptorT>
1737 class FileInfoWrapper
1741 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1743 assert(type_string);
1747 Result_t result = RESULT_OK;
1749 if ( Options.verbose_flag || Options.showheader_flag )
1752 result = Reader.OpenRead(Options.filenames[0]);
1754 if ( ASDCP_SUCCESS(result) )
1756 fprintf(stdout, "File essence type is %s.\n", type_string);
1758 if ( Options.showheader_flag )
1759 Reader.DumpHeaderMetadata(stream);
1762 Reader.FillWriterInfo(WI);
1763 WriterInfoDump(WI, stream);
1766 Desc.FillDescriptor(Reader);
1769 if ( Options.showindex_flag )
1770 Reader.DumpIndex(stream);
1772 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1774 Reader.DumpHeaderMetadata(stream);
1782 // Read header metadata from an ASDCP file
1785 show_file_info(CommandOptions& Options)
1787 EssenceType_t EssenceType;
1788 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1790 if ( ASDCP_FAILURE(result) )
1793 if ( EssenceType == ESS_MPEG2_VES )
1795 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1797 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1799 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1801 if ( ASDCP_SUCCESS(result) )
1803 const Dictionary* Dict = &DefaultCompositeDict();
1804 PCM::MXFReader Reader;
1805 MXF::OPAtomHeader OPAtomHeader(Dict);
1806 MXF::WaveAudioDescriptor *descriptor = 0;
1808 result = Reader.OpenRead(Options.filenames[0]);
1810 if ( ASDCP_SUCCESS(result) )
1811 result = Reader.OPAtomHeader().GetMDObjectByType(Dict->ul(MDD_WaveAudioDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
1813 if ( ASDCP_SUCCESS(result) )
1816 fprintf(stdout, " ChannelAssignment: %s\n", descriptor->ChannelAssignment.EncodeString(buf, 64));
1820 else if ( EssenceType == ESS_JPEG_2000 )
1822 if ( Options.stereo_image_flag )
1824 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1825 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1829 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1830 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1833 else if ( EssenceType == ESS_JPEG_2000_S )
1835 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1836 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1838 else if ( EssenceType == ESS_TIMED_TEXT )
1840 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1844 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1845 Kumu::FileReader Reader;
1846 const Dictionary* Dict = &DefaultCompositeDict();
1847 MXF::OPAtomHeader TestHeader(Dict);
1849 result = Reader.OpenRead(Options.filenames[0]);
1851 if ( ASDCP_SUCCESS(result) )
1852 result = TestHeader.InitFromFile(Reader); // test UL and OP
1854 if ( ASDCP_SUCCESS(result) )
1856 TestHeader.Partition::Dump(stdout);
1858 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1861 fputs("File contains no Identification object.\n", stdout);
1863 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1866 fputs("File contains no SourcePackage object.\n", stdout);
1870 fputs("File is not MXF.\n", stdout);
1880 digest_file(const char* filename)
1882 using namespace Kumu;
1884 ASDCP_TEST_NULL_STR(filename);
1888 ByteString Buf(8192);
1890 Result_t result = Reader.OpenRead(filename);
1892 while ( ASDCP_SUCCESS(result) )
1894 ui32_t read_count = 0;
1895 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1897 if ( result == RESULT_ENDOFFILE )
1903 if ( ASDCP_SUCCESS(result) )
1904 SHA1_Update(&Ctx, Buf.Data(), read_count);
1907 if ( ASDCP_SUCCESS(result) )
1909 const ui32_t sha_len = 20;
1910 byte_t bin_buf[sha_len];
1912 SHA1_Final(bin_buf, &Ctx);
1914 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1922 main(int argc, const char** argv)
1924 Result_t result = RESULT_OK;
1926 CommandOptions Options(argc, argv);
1928 if ( Options.version_flag )
1931 if ( Options.help_flag )
1934 if ( Options.version_flag || Options.help_flag )
1937 if ( Options.error_flag )
1939 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1943 if ( Options.mode == MMT_INFO )
1945 result = show_file_info(Options);
1947 for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1949 Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1950 result = show_file_info(Options);
1953 else if ( Options.mode == MMT_GOP_START )
1955 result = gop_start_test(Options);
1957 else if ( Options.mode == MMT_GEN_KEY )
1959 Kumu::FortunaRNG RNG;
1960 byte_t bin_buf[KeyLen];
1962 RNG.FillRandom(bin_buf, KeyLen);
1963 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1965 else if ( Options.mode == MMT_GEN_ID )
1968 Kumu::GenRandomValue(TmpID);
1969 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1971 else if ( Options.mode == MMT_DIGEST )
1973 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1974 result = digest_file(Options.filenames[i]);
1976 else if ( Options.mode == MMT_UL_LIST )
1978 if ( Options.use_smpte_labels )
1979 DefaultSMPTEDict().Dump(stdout);
1981 DefaultInteropDict().Dump(stdout);
1983 else if ( Options.mode == MMT_EXTRACT )
1985 EssenceType_t EssenceType;
1986 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1988 if ( ASDCP_SUCCESS(result) )
1990 switch ( EssenceType )
1993 result = read_MPEG2_file(Options);
1997 if ( Options.stereo_image_flag )
1998 result = read_JP2K_S_file(Options);
2000 result = read_JP2K_file(Options);
2003 case ESS_JPEG_2000_S:
2004 result = read_JP2K_S_file(Options);
2007 case ESS_PCM_24b_48k:
2008 case ESS_PCM_24b_96k:
2009 result = read_PCM_file(Options);
2012 case ESS_TIMED_TEXT:
2013 result = read_timed_text_file(Options);
2017 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
2022 else if ( Options.mode == MMT_CREATE )
2024 if ( Options.do_repeat && ! Options.duration_flag )
2026 fputs("Option -R requires -d <duration>\n", stderr);
2030 EssenceType_t EssenceType;
2031 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
2033 if ( ASDCP_SUCCESS(result) )
2035 switch ( EssenceType )
2038 result = write_MPEG2_file(Options);
2042 if ( Options.stereo_image_flag )
2043 result = write_JP2K_S_file(Options);
2046 result = write_JP2K_file(Options);
2050 case ESS_PCM_24b_48k:
2051 case ESS_PCM_24b_96k:
2052 result = write_PCM_file(Options);
2055 case ESS_TIMED_TEXT:
2056 result = write_timed_text_file(Options);
2060 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2061 Options.filenames[0]);
2068 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2072 if ( ASDCP_FAILURE(result) )
2074 fputs("Program stopped on error.\n", stderr);
2076 if ( result == RESULT_SFORMAT )
2078 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2080 else if ( result != RESULT_FAIL )
2082 fputs(result, stderr);
2083 fputc('\n', stderr);
2094 // end asdcp-test.cpp