2 Copyright (c) 2003-2014, 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>
53 #include <PCMParserList.h>
54 #include <WavFileWriter.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-2015 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;
309 if ( picture_rate == 192 ) return EditRate_192;
310 if ( picture_rate == 200 ) return EditRate_200;
311 if ( picture_rate == 240 ) return EditRate_240;
316 const char* szPictureRate()
318 if ( picture_rate == 16 ) return "16";
319 if ( picture_rate == 18 ) return "18.182";
320 if ( picture_rate == 20 ) return "20";
321 if ( picture_rate == 22 ) return "21.818";
322 if ( picture_rate == 23 ) return "23.976";
323 if ( picture_rate == 24 ) return "24";
324 if ( picture_rate == 25 ) return "25";
325 if ( picture_rate == 30 ) return "30";
326 if ( picture_rate == 48 ) return "48";
327 if ( picture_rate == 50 ) return "50";
328 if ( picture_rate == 60 ) return "60";
329 if ( picture_rate == 96 ) return "96";
330 if ( picture_rate == 100 ) return "100";
331 if ( picture_rate == 120 ) return "120";
332 if ( picture_rate == 192 ) return "192";
333 if ( picture_rate == 200 ) return "200";
334 if ( picture_rate == 240 ) return "240";
339 CommandOptions(int argc, const char** argv) :
340 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
341 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
342 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
343 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
344 number_width(6), start_frame(0),
345 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
346 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
347 channel_fmt(PCM::CF_NONE)
349 memset(key_value, 0, KeyLen);
350 memset(key_id_value, 0, UUIDlen);
352 for ( int i = 1; i < argc; i++ )
355 if ( (strcmp( argv[i], "-help") == 0) )
361 if ( argv[i][0] == '-'
362 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
365 switch ( argv[i][1] )
367 case '1': mono_wav = true; break;
368 case '2': split_wav = true; break;
369 case '3': stereo_image_flag = true; break;
372 asset_id_flag = true;
373 TEST_EXTRA_ARG(i, 'a');
376 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
378 if ( length != UUIDlen )
380 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
387 TEST_EXTRA_ARG(i, 'b');
388 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
391 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
396 TEST_EXTRA_ARG(i, 'c');
402 TEST_EXTRA_ARG(i, 'd');
403 duration_flag = true;
404 duration = Kumu::xabs(strtol(argv[i], 0, 10));
407 case 'E': encrypt_header_flag = false; break;
408 case 'e': encrypt_header_flag = true; break;
411 TEST_EXTRA_ARG(i, 'f');
412 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
415 case 'G': mode = MMT_GOP_START; break;
416 case 'g': mode = MMT_GEN_KEY; break;
417 case 'H': showheader_flag = true; break;
418 case 'h': help_flag = true; break;
419 case 'i': mode = MMT_INFO; break;
421 case 'j': key_id_flag = true;
422 TEST_EXTRA_ARG(i, 'j');
425 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
427 if ( length != UUIDlen )
429 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
435 case 'k': key_flag = true;
436 TEST_EXTRA_ARG(i, 'k');
439 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
441 if ( length != KeyLen )
443 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
450 TEST_EXTRA_ARG(i, 'l');
451 channel_fmt = decode_channel_fmt(argv[i]);
454 case 'L': use_smpte_labels = true; break;
455 case 'M': write_hmac = false; break;
456 case 'm': read_hmac = true; break;
457 case 'n': showindex_flag = true; break;
460 TEST_EXTRA_ARG(i, 'p');
461 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
464 case 'R': do_repeat = true; break;
465 case 'S': split_wav = true; break;
468 TEST_EXTRA_ARG(i, 's');
469 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
472 case 't': mode = MMT_DIGEST; break;
473 case 'U': mode = MMT_UL_LIST; break;
474 case 'u': mode = MMT_GEN_ID; break;
475 case 'V': version_flag = true; break;
476 case 'v': verbose_flag = true; break;
477 case 'W': no_write_flag = true; break;
480 TEST_EXTRA_ARG(i, 'w');
481 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
485 TEST_EXTRA_ARG(i, 'x');
490 case 'Z': j2c_pedantic = false; break;
491 case 'z': j2c_pedantic = true; break;
494 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
501 if ( argv[i][0] != '-' )
503 filenames[file_count++] = argv[i];
507 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
511 if ( file_count >= MAX_IN_FILES )
513 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
519 if ( help_flag || version_flag )
522 if ( ( mode == MMT_INFO
523 || mode == MMT_CREATE
524 || mode == MMT_EXTRACT
525 || mode == MMT_GOP_START
526 || mode == MMT_DIGEST ) && file_count == 0 )
528 fputs("Option requires at least one filename argument.\n", stderr);
532 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
534 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
542 //------------------------------------------------------------------------------------------
545 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
546 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
549 write_MPEG2_file(CommandOptions& Options)
551 AESEncContext* Context = 0;
552 HMACContext* HMAC = 0;
553 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
554 MPEG2::Parser Parser;
555 MPEG2::MXFWriter Writer;
556 MPEG2::VideoDescriptor VDesc;
557 byte_t IV_buf[CBC_BLOCK_SIZE];
559 // set up essence parser
560 Result_t result = Parser.OpenRead(Options.filenames[0]);
563 if ( ASDCP_SUCCESS(result) )
565 Parser.FillVideoDescriptor(VDesc);
567 if ( Options.verbose_flag )
569 fputs("MPEG-2 Pictures\n", stderr);
570 fputs("VideoDescriptor:\n", stderr);
571 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
572 MPEG2::VideoDescriptorDump(VDesc);
576 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
578 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
579 if ( Options.asset_id_flag )
580 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
582 Kumu::GenRandomUUID(Info.AssetUUID);
584 if ( Options.use_smpte_labels )
586 Info.LabelSetType = LS_MXF_SMPTE;
587 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
591 // configure encryption
592 if( Options.key_flag )
594 Kumu::FortunaRNG RNG;
596 Kumu::GenRandomUUID(Info.ContextID);
597 Info.EncryptedEssence = true;
599 if ( Options.key_id_flag )
600 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
602 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
604 Context = new AESEncContext;
605 result = Context->InitKey(Options.key_value);
607 if ( ASDCP_SUCCESS(result) )
608 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
610 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
612 Info.UsesHMAC = true;
613 HMAC = new HMACContext;
614 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
617 #endif // HAVE_OPENSSL
619 if ( ASDCP_SUCCESS(result) )
620 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
623 if ( ASDCP_SUCCESS(result) )
624 // loop through the frames
626 result = Parser.Reset();
629 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
631 if ( ! Options.do_repeat || duration == 1 )
633 result = Parser.ReadFrame(FrameBuffer);
635 if ( ASDCP_SUCCESS(result) )
637 if ( Options.verbose_flag )
638 FrameBuffer.Dump(stderr, Options.fb_dump_size);
640 if ( Options.encrypt_header_flag )
641 FrameBuffer.PlaintextOffset(0);
645 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
647 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
649 // The Writer class will forward the last block of ciphertext
650 // to the encryption context for use as the IV for the next
651 // frame. If you want to use non-sequitur IV values, un-comment
652 // the following line of code.
653 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
654 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
658 if ( result == RESULT_ENDOFFILE )
662 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
663 result = Writer.Finalize();
668 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
669 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
670 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
673 read_MPEG2_file(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
675 AESDecContext* Context = 0;
676 HMACContext* HMAC = 0;
677 MPEG2::MXFReader Reader(fileReaderFactory);
678 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
679 Kumu::FileWriter OutFile;
680 ui32_t frame_count = 0;
682 Result_t result = Reader.OpenRead(Options.filenames[0]);
684 if ( ASDCP_SUCCESS(result) )
686 MPEG2::VideoDescriptor VDesc;
687 Reader.FillVideoDescriptor(VDesc);
688 frame_count = VDesc.ContainerDuration;
690 if ( Options.verbose_flag )
692 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
693 MPEG2::VideoDescriptorDump(VDesc);
697 if ( ASDCP_SUCCESS(result) )
700 snprintf(filename, 256, "%s.ves", Options.file_root);
701 result = OutFile.OpenWrite(filename);
705 if ( ASDCP_SUCCESS(result) && Options.key_flag )
707 Context = new AESDecContext;
708 result = Context->InitKey(Options.key_value);
710 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
713 Reader.FillWriterInfo(Info);
717 HMAC = new HMACContext;
718 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
722 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
726 #endif // HAVE_OPENSSL
728 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
729 if ( last_frame > frame_count )
730 last_frame = frame_count;
732 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
734 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
736 if ( ASDCP_SUCCESS(result) )
738 if ( Options.verbose_flag )
739 FrameBuffer.Dump(stderr, Options.fb_dump_size);
741 ui32_t write_count = 0;
742 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
752 gop_start_test(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
754 using namespace ASDCP::MPEG2;
756 MXFReader Reader(fileReaderFactory);
757 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
758 ui32_t frame_count = 0;
760 Result_t result = Reader.OpenRead(Options.filenames[0]);
762 if ( ASDCP_SUCCESS(result) )
764 MPEG2::VideoDescriptor VDesc;
765 Reader.FillVideoDescriptor(VDesc);
766 frame_count = VDesc.ContainerDuration;
768 if ( Options.verbose_flag )
770 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
771 MPEG2::VideoDescriptorDump(VDesc);
775 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
776 if ( last_frame > frame_count )
777 last_frame = frame_count;
779 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
781 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
783 if ( ASDCP_SUCCESS(result) )
785 if ( Options.verbose_flag )
786 FrameBuffer.Dump(stderr, Options.fb_dump_size);
788 if ( FrameBuffer.FrameType() != FRAME_I )
789 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
791 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
798 //------------------------------------------------------------------------------------------
801 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
802 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
805 write_JP2K_S_file(CommandOptions& Options)
807 AESEncContext* Context = 0;
808 HMACContext* HMAC = 0;
809 JP2K::MXFSWriter Writer;
810 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
811 JP2K::PictureDescriptor PDesc;
812 JP2K::SequenceParser ParserLeft, ParserRight;
813 byte_t IV_buf[CBC_BLOCK_SIZE];
815 if ( Options.file_count != 2 )
817 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
821 // set up essence parser
822 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
824 if ( ASDCP_SUCCESS(result) )
825 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
828 if ( ASDCP_SUCCESS(result) )
830 ParserLeft.FillPictureDescriptor(PDesc);
831 PDesc.EditRate = Options.PictureRate();
833 if ( Options.verbose_flag )
835 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
836 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
837 JP2K::PictureDescriptorDump(PDesc);
841 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
843 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
844 if ( Options.asset_id_flag )
845 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
847 Kumu::GenRandomUUID(Info.AssetUUID);
849 if ( Options.use_smpte_labels )
851 Info.LabelSetType = LS_MXF_SMPTE;
852 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
856 // configure encryption
857 if( Options.key_flag )
859 Kumu::FortunaRNG RNG;
861 Kumu::GenRandomUUID(Info.ContextID);
862 Info.EncryptedEssence = true;
864 if ( Options.key_id_flag )
865 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
867 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
869 Context = new AESEncContext;
870 result = Context->InitKey(Options.key_value);
872 if ( ASDCP_SUCCESS(result) )
873 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
875 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
877 Info.UsesHMAC = true;
878 HMAC = new HMACContext;
879 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
882 #endif // HAVE_OPENSSL
884 if ( ASDCP_SUCCESS(result) )
885 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
888 if ( ASDCP_SUCCESS(result) )
891 result = ParserLeft.Reset();
892 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
894 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
896 result = ParserLeft.ReadFrame(FrameBuffer);
898 if ( ASDCP_SUCCESS(result) )
900 if ( Options.verbose_flag )
901 FrameBuffer.Dump(stderr, Options.fb_dump_size);
903 if ( Options.encrypt_header_flag )
904 FrameBuffer.PlaintextOffset(0);
907 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
908 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
910 if ( ASDCP_SUCCESS(result) )
911 result = ParserRight.ReadFrame(FrameBuffer);
913 if ( ASDCP_SUCCESS(result) )
915 if ( Options.verbose_flag )
916 FrameBuffer.Dump(stderr, Options.fb_dump_size);
918 if ( Options.encrypt_header_flag )
919 FrameBuffer.PlaintextOffset(0);
922 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
923 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
926 if ( result == RESULT_ENDOFFILE )
930 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
931 result = Writer.Finalize();
936 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
937 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
938 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
940 read_JP2K_S_file(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
942 AESDecContext* Context = 0;
943 HMACContext* HMAC = 0;
944 JP2K::MXFSReader Reader(fileReaderFactory);
945 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
946 ui32_t frame_count = 0;
948 Result_t result = Reader.OpenRead(Options.filenames[0]);
950 if ( ASDCP_SUCCESS(result) )
952 JP2K::PictureDescriptor PDesc;
953 Reader.FillPictureDescriptor(PDesc);
955 frame_count = PDesc.ContainerDuration;
957 if ( Options.verbose_flag )
959 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
960 JP2K::PictureDescriptorDump(PDesc);
965 if ( ASDCP_SUCCESS(result) && Options.key_flag )
967 Context = new AESDecContext;
968 result = Context->InitKey(Options.key_value);
970 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
973 Reader.FillWriterInfo(Info);
977 HMAC = new HMACContext;
978 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
982 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
986 #endif // HAVE_OPENSSL
988 const int filename_max = 1024;
989 char filename[filename_max];
990 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
991 if ( last_frame > frame_count )
992 last_frame = frame_count;
994 char left_format[64]; char right_format[64];
995 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
996 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
998 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1000 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
1002 if ( ASDCP_SUCCESS(result) )
1004 Kumu::FileWriter OutFile;
1006 snprintf(filename, filename_max, left_format, Options.file_root, i);
1007 result = OutFile.OpenWrite(filename);
1009 if ( ASDCP_SUCCESS(result) )
1010 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1012 if ( Options.verbose_flag )
1013 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1016 if ( ASDCP_SUCCESS(result) )
1017 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
1019 if ( ASDCP_SUCCESS(result) )
1021 Kumu::FileWriter OutFile;
1023 snprintf(filename, filename_max, right_format, Options.file_root, i);
1024 result = OutFile.OpenWrite(filename);
1026 if ( ASDCP_SUCCESS(result) )
1027 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1036 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1037 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1040 write_JP2K_file(CommandOptions& Options)
1042 AESEncContext* Context = 0;
1043 HMACContext* HMAC = 0;
1044 JP2K::MXFWriter Writer;
1045 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1046 JP2K::PictureDescriptor PDesc;
1047 JP2K::SequenceParser Parser;
1048 byte_t IV_buf[CBC_BLOCK_SIZE];
1050 // set up essence parser
1051 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1053 // set up MXF writer
1054 if ( ASDCP_SUCCESS(result) )
1056 Parser.FillPictureDescriptor(PDesc);
1057 PDesc.EditRate = Options.PictureRate();
1059 if ( Options.verbose_flag )
1061 fprintf(stderr, "JPEG 2000 pictures\n");
1062 fputs("PictureDescriptor:\n", stderr);
1063 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1064 JP2K::PictureDescriptorDump(PDesc);
1068 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1070 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1071 if ( Options.asset_id_flag )
1072 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1074 Kumu::GenRandomUUID(Info.AssetUUID);
1076 if ( Options.use_smpte_labels )
1078 Info.LabelSetType = LS_MXF_SMPTE;
1079 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1083 // configure encryption
1084 if( Options.key_flag )
1086 Kumu::FortunaRNG RNG;
1087 Kumu::GenRandomUUID(Info.ContextID);
1088 Info.EncryptedEssence = true;
1090 if ( Options.key_id_flag )
1091 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1093 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1095 Context = new AESEncContext;
1096 result = Context->InitKey(Options.key_value);
1098 if ( ASDCP_SUCCESS(result) )
1099 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1101 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1103 Info.UsesHMAC = true;
1104 HMAC = new HMACContext;
1105 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1110 if ( ASDCP_SUCCESS(result) )
1111 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1114 if ( ASDCP_SUCCESS(result) )
1116 ui32_t duration = 0;
1117 result = Parser.Reset();
1119 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1121 if ( ! Options.do_repeat || duration == 1 )
1123 result = Parser.ReadFrame(FrameBuffer);
1125 if ( ASDCP_SUCCESS(result) )
1127 if ( Options.verbose_flag )
1128 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1130 if ( Options.encrypt_header_flag )
1131 FrameBuffer.PlaintextOffset(0);
1135 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1137 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1139 // The Writer class will forward the last block of ciphertext
1140 // to the encryption context for use as the IV for the next
1141 // frame. If you want to use non-sequitur IV values, un-comment
1142 // the following line of code.
1143 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1144 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1148 if ( result == RESULT_ENDOFFILE )
1152 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1153 result = Writer.Finalize();
1158 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1159 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1160 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1163 read_JP2K_file(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
1165 AESDecContext* Context = 0;
1166 HMACContext* HMAC = 0;
1167 JP2K::MXFReader Reader(fileReaderFactory);
1168 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1169 ui32_t frame_count = 0;
1171 Result_t result = Reader.OpenRead(Options.filenames[0]);
1173 if ( ASDCP_SUCCESS(result) )
1175 JP2K::PictureDescriptor PDesc;
1176 Reader.FillPictureDescriptor(PDesc);
1178 frame_count = PDesc.ContainerDuration;
1180 if ( Options.verbose_flag )
1182 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1183 JP2K::PictureDescriptorDump(PDesc);
1188 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1190 Context = new AESDecContext;
1191 result = Context->InitKey(Options.key_value);
1193 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1196 Reader.FillWriterInfo(Info);
1198 if ( Info.UsesHMAC )
1200 HMAC = new HMACContext;
1201 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1205 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1209 #endif // HAVE_OPENSSL
1211 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1212 if ( last_frame > frame_count )
1213 last_frame = frame_count;
1215 char name_format[64];
1216 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1218 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1220 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1222 if ( ASDCP_SUCCESS(result) )
1224 Kumu::FileWriter OutFile;
1227 snprintf(filename, 256, name_format, Options.file_root, i);
1228 result = OutFile.OpenWrite(filename);
1230 if ( ASDCP_SUCCESS(result) )
1231 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1233 if ( Options.verbose_flag )
1234 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1241 //------------------------------------------------------------------------------------------
1245 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1246 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1249 write_PCM_file(CommandOptions& Options)
1251 AESEncContext* Context = 0;
1252 HMACContext* HMAC = 0;
1253 PCMParserList Parser;
1254 PCM::MXFWriter Writer;
1255 PCM::FrameBuffer FrameBuffer;
1256 PCM::AudioDescriptor ADesc;
1257 Rational PictureRate = Options.PictureRate();
1258 byte_t IV_buf[CBC_BLOCK_SIZE];
1260 // set up essence parser
1261 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1263 // set up MXF writer
1264 if ( ASDCP_SUCCESS(result) )
1266 Parser.FillAudioDescriptor(ADesc);
1268 ADesc.EditRate = PictureRate;
1269 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1270 ADesc.ChannelFormat = Options.channel_fmt;
1272 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1274 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1277 if ( Options.verbose_flag )
1279 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1280 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1281 Options.szPictureRate(),
1282 PCM::CalcSamplesPerFrame(ADesc));
1283 fputs("AudioDescriptor:\n", stderr);
1284 PCM::AudioDescriptorDump(ADesc);
1288 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1290 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1291 if ( Options.asset_id_flag )
1292 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1294 Kumu::GenRandomUUID(Info.AssetUUID);
1296 if ( Options.use_smpte_labels )
1298 Info.LabelSetType = LS_MXF_SMPTE;
1299 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1303 // configure encryption
1304 if( Options.key_flag )
1306 Kumu::FortunaRNG RNG;
1307 Kumu::GenRandomUUID(Info.ContextID);
1308 Info.EncryptedEssence = true;
1310 if ( Options.key_id_flag )
1311 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1313 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1315 Context = new AESEncContext;
1316 result = Context->InitKey(Options.key_value);
1318 if ( ASDCP_SUCCESS(result) )
1319 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1321 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1323 Info.UsesHMAC = true;
1324 HMAC = new HMACContext;
1325 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1328 #endif // HAVE_OPENSSL
1330 if ( ASDCP_SUCCESS(result) )
1331 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1334 if ( ASDCP_SUCCESS(result) )
1336 result = Parser.Reset();
1337 ui32_t duration = 0;
1339 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1341 result = Parser.ReadFrame(FrameBuffer);
1343 if ( ASDCP_SUCCESS(result) )
1345 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1347 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1348 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1349 result = RESULT_ENDOFFILE;
1353 if ( Options.verbose_flag )
1354 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1356 if ( ! Options.no_write_flag )
1358 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1360 // The Writer class will forward the last block of ciphertext
1361 // to the encryption context for use as the IV for the next
1362 // frame. If you want to use non-sequitur IV values, un-comment
1363 // the following line of code.
1364 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1365 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1370 if ( result == RESULT_ENDOFFILE )
1374 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1375 result = Writer.Finalize();
1380 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1381 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1382 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1385 read_PCM_file(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
1387 AESDecContext* Context = 0;
1388 HMACContext* HMAC = 0;
1389 PCM::MXFReader Reader(fileReaderFactory);
1390 PCM::FrameBuffer FrameBuffer;
1391 WavFileWriter OutWave;
1392 PCM::AudioDescriptor ADesc;
1393 ui32_t last_frame = 0;
1395 Result_t result = Reader.OpenRead(Options.filenames[0]);
1397 if ( ASDCP_SUCCESS(result) )
1399 Reader.FillAudioDescriptor(ADesc);
1401 if ( ADesc.EditRate != EditRate_23_98
1402 && ADesc.EditRate != EditRate_24
1403 && ADesc.EditRate != EditRate_25
1404 && ADesc.EditRate != EditRate_30
1405 && ADesc.EditRate != EditRate_48
1406 && ADesc.EditRate != EditRate_50
1407 && ADesc.EditRate != EditRate_60 )
1408 ADesc.EditRate = Options.PictureRate();
1410 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1412 if ( Options.verbose_flag )
1413 PCM::AudioDescriptorDump(ADesc);
1416 if ( ASDCP_SUCCESS(result) )
1418 last_frame = ADesc.ContainerDuration;
1420 if ( Options.duration > 0 && Options.duration < last_frame )
1421 last_frame = Options.duration;
1423 if ( Options.start_frame > 0 )
1425 if ( Options.start_frame > ADesc.ContainerDuration )
1427 fprintf(stderr, "Start value greater than file duration.\n");
1431 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1434 ADesc.ContainerDuration = last_frame - Options.start_frame;
1435 OutWave.OpenWrite(ADesc, Options.file_root,
1436 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1437 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1441 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1443 Context = new AESDecContext;
1444 result = Context->InitKey(Options.key_value);
1446 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1449 Reader.FillWriterInfo(Info);
1451 if ( Info.UsesHMAC )
1453 HMAC = new HMACContext;
1454 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1458 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1462 #endif // HAVE_OPENSSL
1464 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1466 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1468 if ( ASDCP_SUCCESS(result) )
1470 if ( Options.verbose_flag )
1471 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1473 result = OutWave.WriteFrame(FrameBuffer);
1481 //------------------------------------------------------------------------------------------
1482 // TimedText essence
1485 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1486 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1489 write_timed_text_file(CommandOptions& Options)
1491 AESEncContext* Context = 0;
1492 HMACContext* HMAC = 0;
1493 TimedText::DCSubtitleParser Parser;
1494 TimedText::MXFWriter Writer;
1495 TimedText::FrameBuffer FrameBuffer;
1496 TimedText::TimedTextDescriptor TDesc;
1497 byte_t IV_buf[CBC_BLOCK_SIZE];
1499 // set up essence parser
1500 Result_t result = Parser.OpenRead(Options.filenames[0]);
1502 // set up MXF writer
1503 if ( ASDCP_SUCCESS(result) )
1505 Parser.FillTimedTextDescriptor(TDesc);
1506 FrameBuffer.Capacity(Options.fb_size);
1508 if ( Options.verbose_flag )
1510 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1511 TimedText::DescriptorDump(TDesc);
1515 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1517 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1518 if ( Options.asset_id_flag )
1519 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1521 Kumu::GenRandomUUID(Info.AssetUUID);
1523 if ( Options.use_smpte_labels )
1525 Info.LabelSetType = LS_MXF_SMPTE;
1526 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1530 // configure encryption
1531 if( Options.key_flag )
1533 Kumu::FortunaRNG RNG;
1534 Kumu::GenRandomUUID(Info.ContextID);
1535 Info.EncryptedEssence = true;
1537 if ( Options.key_id_flag )
1538 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1540 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1542 Context = new AESEncContext;
1543 result = Context->InitKey(Options.key_value);
1545 if ( ASDCP_SUCCESS(result) )
1546 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1548 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1550 Info.UsesHMAC = true;
1551 HMAC = new HMACContext;
1552 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1555 #endif // HAVE_OPENSSL
1557 if ( ASDCP_SUCCESS(result) )
1558 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1561 if ( ASDCP_FAILURE(result) )
1565 TimedText::ResourceList_t::const_iterator ri;
1567 result = Parser.ReadTimedTextResource(XMLDoc);
1569 if ( ASDCP_SUCCESS(result) )
1570 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1572 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1574 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1576 if ( ASDCP_SUCCESS(result) )
1578 if ( Options.verbose_flag )
1579 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1581 if ( ! Options.no_write_flag )
1583 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1585 // The Writer class will forward the last block of ciphertext
1586 // to the encryption context for use as the IV for the next
1587 // frame. If you want to use non-sequitur IV values, un-comment
1588 // the following line of code.
1589 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1590 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1594 if ( result == RESULT_ENDOFFILE )
1598 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1599 result = Writer.Finalize();
1605 // Read one or more timed text streams from a plaintext ASDCP file
1606 // Read one or more timed text streams from a ciphertext ASDCP file
1607 // Read one or more timed text streams from a ciphertext ASDCP file
1610 read_timed_text_file(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
1612 AESDecContext* Context = 0;
1613 HMACContext* HMAC = 0;
1614 TimedText::MXFReader Reader(fileReaderFactory);
1615 TimedText::FrameBuffer FrameBuffer;
1616 TimedText::TimedTextDescriptor TDesc;
1618 Result_t result = Reader.OpenRead(Options.filenames[0]);
1620 if ( ASDCP_SUCCESS(result) )
1622 Reader.FillTimedTextDescriptor(TDesc);
1623 FrameBuffer.Capacity(Options.fb_size);
1625 if ( Options.verbose_flag )
1626 TimedText::DescriptorDump(TDesc);
1630 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1632 Context = new AESDecContext;
1633 result = Context->InitKey(Options.key_value);
1635 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1638 Reader.FillWriterInfo(Info);
1640 if ( Info.UsesHMAC )
1642 HMAC = new HMACContext;
1643 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1647 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1651 #endif // HAVE_OPENSSL
1653 if ( ASDCP_FAILURE(result) )
1657 std::string out_path = Kumu::PathDirname(Options.file_root);
1660 TimedText::ResourceList_t::const_iterator ri;
1662 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1664 if ( ASDCP_SUCCESS(result) )
1666 Kumu::FileWriter Writer;
1667 result = Writer.OpenWrite(Options.file_root);
1669 if ( ASDCP_SUCCESS(result) )
1670 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1673 if ( out_path.empty() )
1678 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1680 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1682 if ( ASDCP_SUCCESS(result) )
1684 Kumu::FileWriter Writer;
1685 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1687 if ( ASDCP_SUCCESS(result) )
1689 if ( Options.verbose_flag )
1690 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1692 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1700 //------------------------------------------------------------------------------------------
1704 // These classes wrap the irregular names in the asdcplib API
1705 // so that I can use a template to simplify the implementation
1706 // of show_file_info()
1708 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1711 void FillDescriptor(MPEG2::MXFReader& Reader) {
1712 Reader.FillVideoDescriptor(*this);
1715 void Dump(FILE* stream) {
1716 MPEG2::VideoDescriptorDump(*this, stream);
1720 class MyPictureDescriptor : public JP2K::PictureDescriptor
1723 void FillDescriptor(JP2K::MXFReader& Reader) {
1724 Reader.FillPictureDescriptor(*this);
1727 void Dump(FILE* stream) {
1728 JP2K::PictureDescriptorDump(*this, stream);
1732 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1735 void FillDescriptor(JP2K::MXFSReader& Reader) {
1736 Reader.FillPictureDescriptor(*this);
1739 void Dump(FILE* stream) {
1740 JP2K::PictureDescriptorDump(*this, stream);
1744 class MyAudioDescriptor : public PCM::AudioDescriptor
1747 void FillDescriptor(PCM::MXFReader& Reader) {
1748 Reader.FillAudioDescriptor(*this);
1751 void Dump(FILE* stream) {
1752 PCM::AudioDescriptorDump(*this, stream);
1756 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1759 void FillDescriptor(TimedText::MXFReader& Reader) {
1760 Reader.FillTimedTextDescriptor(*this);
1763 void Dump(FILE* stream) {
1764 TimedText::DescriptorDump(*this, stream);
1768 // MSVC didn't like the function template, so now it's a static class method
1769 template<class ReaderT, class DescriptorT>
1770 class FileInfoWrapper
1774 file_info(CommandOptions& Options, const char* type_string, const Kumu::IFileReaderFactory& fileReaderFactory, FILE* stream = 0)
1776 assert(type_string);
1780 Result_t result = RESULT_OK;
1782 if ( Options.verbose_flag || Options.showheader_flag )
1784 ReaderT Reader(fileReaderFactory);
1785 result = Reader.OpenRead(Options.filenames[0]);
1787 if ( ASDCP_SUCCESS(result) )
1789 fprintf(stdout, "File essence type is %s.\n", type_string);
1791 if ( Options.showheader_flag )
1792 Reader.DumpHeaderMetadata(stream);
1795 Reader.FillWriterInfo(WI);
1796 WriterInfoDump(WI, stream);
1799 Desc.FillDescriptor(Reader);
1802 if ( Options.showindex_flag )
1803 Reader.DumpIndex(stream);
1805 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1807 Reader.DumpHeaderMetadata(stream);
1815 // Read header metadata from an ASDCP file
1818 show_file_info(CommandOptions& Options, const Kumu::IFileReaderFactory& fileReaderFactory)
1820 EssenceType_t EssenceType;
1821 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType, fileReaderFactory);
1823 if ( ASDCP_FAILURE(result) )
1826 if ( EssenceType == ESS_MPEG2_VES )
1828 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video", fileReaderFactory);
1830 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1832 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio", fileReaderFactory);
1834 if ( ASDCP_SUCCESS(result) )
1836 const Dictionary* Dict = &DefaultCompositeDict();
1837 PCM::MXFReader Reader(fileReaderFactory);
1838 MXF::OP1aHeader Header(Dict);
1839 MXF::WaveAudioDescriptor *descriptor = 0;
1841 result = Reader.OpenRead(Options.filenames[0]);
1843 if ( ASDCP_SUCCESS(result) )
1844 result = Reader.OP1aHeader().GetMDObjectByType(Dict->ul(MDD_WaveAudioDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
1846 if ( ASDCP_SUCCESS(result) )
1849 fprintf(stdout, " ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
1853 else if ( EssenceType == ESS_JPEG_2000 )
1855 if ( Options.stereo_image_flag )
1857 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1858 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures", fileReaderFactory);
1862 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1863 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures", fileReaderFactory);
1866 else if ( EssenceType == ESS_JPEG_2000_S )
1868 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1869 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures", fileReaderFactory);
1871 else if ( EssenceType == ESS_TIMED_TEXT )
1873 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text", fileReaderFactory);
1877 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1878 ASDCP::mem_ptr<Kumu::IFileReader> Reader(fileReaderFactory.CreateFileReader());
1879 const Dictionary* Dict = &DefaultCompositeDict();
1880 MXF::OP1aHeader TestHeader(Dict);
1882 result = Reader->OpenRead(Options.filenames[0]);
1884 if ( ASDCP_SUCCESS(result) )
1885 result = TestHeader.InitFromFile(*Reader); // test UL and OP
1887 if ( ASDCP_SUCCESS(result) )
1889 TestHeader.Partition::Dump(stdout);
1891 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1894 fputs("File contains no Identification object.\n", stdout);
1896 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1899 fputs("File contains no SourcePackage object.\n", stdout);
1903 fputs("File is not MXF.\n", stdout);
1912 digest_file(const char* filename)
1914 using namespace Kumu;
1916 ASDCP_TEST_NULL_STR(filename);
1920 ByteString Buf(8192);
1922 Result_t result = Reader.OpenRead(filename);
1924 while ( ASDCP_SUCCESS(result) )
1926 ui32_t read_count = 0;
1927 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1929 if ( result == RESULT_ENDOFFILE )
1935 if ( ASDCP_SUCCESS(result) )
1936 SHA1_Update(&Ctx, Buf.Data(), read_count);
1939 if ( ASDCP_SUCCESS(result) )
1941 const ui32_t sha_len = 20;
1942 byte_t bin_buf[sha_len];
1944 SHA1_Final(bin_buf, &Ctx);
1946 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1954 main(int argc, const char** argv)
1956 Result_t result = RESULT_OK;
1958 CommandOptions Options(argc, argv);
1960 if ( Options.version_flag )
1963 if ( Options.help_flag )
1966 if ( Options.version_flag || Options.help_flag )
1969 if ( Options.error_flag )
1971 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1975 Kumu::FileReaderFactory defaultFactory;
1977 if ( Options.mode == MMT_INFO )
1979 result = show_file_info(Options, defaultFactory);
1981 for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1983 Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1984 result = show_file_info(Options, defaultFactory);
1987 else if ( Options.mode == MMT_GOP_START )
1989 result = gop_start_test(Options, defaultFactory);
1991 else if ( Options.mode == MMT_GEN_KEY )
1993 Kumu::SymmetricKey key;
1994 GenRandomValue(key);
1995 printf("%s\n", Kumu::bin2hex(key.Value(), key.Size(), str_buf, 64));
1997 else if ( Options.mode == MMT_GEN_ID )
2000 Kumu::GenRandomValue(TmpID);
2001 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
2003 else if ( Options.mode == MMT_DIGEST )
2005 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
2006 result = digest_file(Options.filenames[i]);
2008 else if ( Options.mode == MMT_UL_LIST )
2010 if ( Options.use_smpte_labels )
2011 DefaultSMPTEDict().Dump(stdout);
2013 DefaultInteropDict().Dump(stdout);
2015 else if ( Options.mode == MMT_EXTRACT )
2017 EssenceType_t EssenceType;
2018 result = ASDCP::EssenceType(Options.filenames[0], EssenceType, defaultFactory);
2020 if ( ASDCP_SUCCESS(result) )
2022 switch ( EssenceType )
2025 result = read_MPEG2_file(Options, defaultFactory);
2029 if ( Options.stereo_image_flag )
2030 result = read_JP2K_S_file(Options, defaultFactory);
2032 result = read_JP2K_file(Options, defaultFactory);
2035 case ESS_JPEG_2000_S:
2036 result = read_JP2K_S_file(Options, defaultFactory);
2039 case ESS_PCM_24b_48k:
2040 case ESS_PCM_24b_96k:
2041 result = read_PCM_file(Options, defaultFactory);
2044 case ESS_TIMED_TEXT:
2045 result = read_timed_text_file(Options, defaultFactory);
2049 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
2054 else if ( Options.mode == MMT_CREATE )
2056 if ( Options.do_repeat && ! Options.duration_flag )
2058 fputs("Option -R requires -d <duration>\n", stderr);
2062 EssenceType_t EssenceType;
2063 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
2065 if ( ASDCP_SUCCESS(result) )
2067 switch ( EssenceType )
2070 result = write_MPEG2_file(Options);
2074 if ( Options.stereo_image_flag )
2075 result = write_JP2K_S_file(Options);
2078 result = write_JP2K_file(Options);
2082 case ESS_PCM_24b_48k:
2083 case ESS_PCM_24b_96k:
2084 result = write_PCM_file(Options);
2087 case ESS_TIMED_TEXT:
2088 result = write_timed_text_file(Options);
2092 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2093 Options.filenames[0]);
2100 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2104 if ( ASDCP_FAILURE(result) )
2106 fputs("Program stopped on error.\n", stderr);
2108 if ( result == RESULT_SFORMAT )
2110 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2112 else if ( result != RESULT_FAIL )
2114 fputs(result, stderr);
2115 fputc('\n', stderr);
2126 // end asdcp-test.cpp