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
28 \version $Id: asdcp-test.cpp,v 1.46 2012/02/03 19:49:56 jhurst Exp $
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 == 23 ) return EditRate_23_98;
296 if ( picture_rate == 24 ) return EditRate_24;
297 if ( picture_rate == 25 ) return EditRate_25;
298 if ( picture_rate == 30 ) return EditRate_30;
299 if ( picture_rate == 48 ) return EditRate_48;
300 if ( picture_rate == 50 ) return EditRate_50;
301 if ( picture_rate == 60 ) return EditRate_60;
302 if ( picture_rate == 96 ) return EditRate_96;
303 if ( picture_rate == 100 ) return EditRate_100;
304 if ( picture_rate == 120 ) return EditRate_120;
309 const char* szPictureRate()
311 if ( picture_rate == 23 ) return "23.976";
312 if ( picture_rate == 24 ) return "24";
313 if ( picture_rate == 25 ) return "25";
314 if ( picture_rate == 30 ) return "30";
315 if ( picture_rate == 48 ) return "48";
316 if ( picture_rate == 50 ) return "50";
317 if ( picture_rate == 60 ) return "60";
318 if ( picture_rate == 96 ) return "96";
319 if ( picture_rate == 100 ) return "100";
320 if ( picture_rate == 120 ) return "120";
325 CommandOptions(int argc, const char** argv) :
326 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
327 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
328 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
329 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
330 number_width(6), start_frame(0),
331 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
332 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
333 channel_fmt(PCM::CF_NONE)
335 memset(key_value, 0, KeyLen);
336 memset(key_id_value, 0, UUIDlen);
338 for ( int i = 1; i < argc; i++ )
341 if ( (strcmp( argv[i], "-help") == 0) )
347 if ( argv[i][0] == '-'
348 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
351 switch ( argv[i][1] )
353 case '1': mono_wav = true; break;
354 case '2': split_wav = true; break;
355 case '3': stereo_image_flag = true; break;
358 asset_id_flag = true;
359 TEST_EXTRA_ARG(i, 'a');
362 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
364 if ( length != UUIDlen )
366 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
373 TEST_EXTRA_ARG(i, 'b');
374 fb_size = abs(atoi(argv[i]));
377 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
382 TEST_EXTRA_ARG(i, 'c');
388 TEST_EXTRA_ARG(i, 'd');
389 duration_flag = true;
390 duration = abs(atoi(argv[i]));
393 case 'E': encrypt_header_flag = false; break;
394 case 'e': encrypt_header_flag = true; break;
397 TEST_EXTRA_ARG(i, 'f');
398 start_frame = abs(atoi(argv[i]));
401 case 'G': mode = MMT_GOP_START; break;
402 case 'g': mode = MMT_GEN_KEY; break;
403 case 'H': showheader_flag = true; break;
404 case 'h': help_flag = true; break;
405 case 'i': mode = MMT_INFO; break;
407 case 'j': key_id_flag = true;
408 TEST_EXTRA_ARG(i, 'j');
411 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
413 if ( length != UUIDlen )
415 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
421 case 'k': key_flag = true;
422 TEST_EXTRA_ARG(i, 'k');
425 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
427 if ( length != KeyLen )
429 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
436 TEST_EXTRA_ARG(i, 'l');
437 channel_fmt = decode_channel_fmt(argv[i]);
440 case 'L': use_smpte_labels = true; break;
441 case 'M': write_hmac = false; break;
442 case 'm': read_hmac = true; break;
443 case 'n': showindex_flag = true; break;
446 TEST_EXTRA_ARG(i, 'p');
447 picture_rate = abs(atoi(argv[i]));
450 case 'R': do_repeat = true; break;
451 case 'S': split_wav = true; break;
454 TEST_EXTRA_ARG(i, 's');
455 fb_dump_size = abs(atoi(argv[i]));
458 case 't': mode = MMT_DIGEST; break;
459 case 'U': mode = MMT_UL_LIST; break;
460 case 'u': mode = MMT_GEN_ID; break;
461 case 'V': version_flag = true; break;
462 case 'v': verbose_flag = true; break;
463 case 'W': no_write_flag = true; break;
466 TEST_EXTRA_ARG(i, 'w');
467 number_width = abs(atoi(argv[i]));
471 TEST_EXTRA_ARG(i, 'x');
476 case 'Z': j2c_pedantic = false; break;
477 case 'z': j2c_pedantic = true; break;
480 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
487 if ( argv[i][0] != '-' )
489 filenames[file_count++] = argv[i];
493 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
497 if ( file_count >= MAX_IN_FILES )
499 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
505 if ( help_flag || version_flag )
508 if ( ( mode == MMT_INFO
509 || mode == MMT_CREATE
510 || mode == MMT_EXTRACT
511 || mode == MMT_GOP_START
512 || mode == MMT_DIGEST ) && file_count == 0 )
514 fputs("Option requires at least one filename argument.\n", stderr);
518 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
520 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
528 //------------------------------------------------------------------------------------------
531 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
532 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
535 write_MPEG2_file(CommandOptions& Options)
537 AESEncContext* Context = 0;
538 HMACContext* HMAC = 0;
539 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
540 MPEG2::Parser Parser;
541 MPEG2::MXFWriter Writer;
542 MPEG2::VideoDescriptor VDesc;
543 byte_t IV_buf[CBC_BLOCK_SIZE];
544 Kumu::FortunaRNG RNG;
546 // set up essence parser
547 Result_t result = Parser.OpenRead(Options.filenames[0]);
550 if ( ASDCP_SUCCESS(result) )
552 Parser.FillVideoDescriptor(VDesc);
554 if ( Options.verbose_flag )
556 fputs("MPEG-2 Pictures\n", stderr);
557 fputs("VideoDescriptor:\n", stderr);
558 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
559 MPEG2::VideoDescriptorDump(VDesc);
563 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
565 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
566 if ( Options.asset_id_flag )
567 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
569 Kumu::GenRandomUUID(Info.AssetUUID);
571 if ( Options.use_smpte_labels )
573 Info.LabelSetType = LS_MXF_SMPTE;
574 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
577 // configure encryption
578 if( Options.key_flag )
580 Kumu::GenRandomUUID(Info.ContextID);
581 Info.EncryptedEssence = true;
583 if ( Options.key_id_flag )
584 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
586 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
588 Context = new AESEncContext;
589 result = Context->InitKey(Options.key_value);
591 if ( ASDCP_SUCCESS(result) )
592 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
594 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
596 Info.UsesHMAC = true;
597 HMAC = new HMACContext;
598 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
602 if ( ASDCP_SUCCESS(result) )
603 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
606 if ( ASDCP_SUCCESS(result) )
607 // loop through the frames
609 result = Parser.Reset();
612 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
614 if ( ! Options.do_repeat || duration == 1 )
616 result = Parser.ReadFrame(FrameBuffer);
618 if ( ASDCP_SUCCESS(result) )
620 if ( Options.verbose_flag )
621 FrameBuffer.Dump(stderr, Options.fb_dump_size);
623 if ( Options.encrypt_header_flag )
624 FrameBuffer.PlaintextOffset(0);
628 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
630 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
632 // The Writer class will forward the last block of ciphertext
633 // to the encryption context for use as the IV for the next
634 // frame. If you want to use non-sequitur IV values, un-comment
635 // the following line of code.
636 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
637 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
641 if ( result == RESULT_ENDOFFILE )
645 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
646 result = Writer.Finalize();
651 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
652 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
653 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
656 read_MPEG2_file(CommandOptions& Options)
658 AESDecContext* Context = 0;
659 HMACContext* HMAC = 0;
660 MPEG2::MXFReader Reader;
661 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
662 Kumu::FileWriter OutFile;
663 ui32_t frame_count = 0;
665 Result_t result = Reader.OpenRead(Options.filenames[0]);
667 if ( ASDCP_SUCCESS(result) )
669 MPEG2::VideoDescriptor VDesc;
670 Reader.FillVideoDescriptor(VDesc);
671 frame_count = VDesc.ContainerDuration;
673 if ( Options.verbose_flag )
675 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
676 MPEG2::VideoDescriptorDump(VDesc);
680 if ( ASDCP_SUCCESS(result) )
683 snprintf(filename, 256, "%s.ves", Options.file_root);
684 result = OutFile.OpenWrite(filename);
687 if ( ASDCP_SUCCESS(result) && Options.key_flag )
689 Context = new AESDecContext;
690 result = Context->InitKey(Options.key_value);
692 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
695 Reader.FillWriterInfo(Info);
699 HMAC = new HMACContext;
700 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
704 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
709 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
710 if ( last_frame > frame_count )
711 last_frame = frame_count;
713 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
715 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
717 if ( ASDCP_SUCCESS(result) )
719 if ( Options.verbose_flag )
720 FrameBuffer.Dump(stderr, Options.fb_dump_size);
722 ui32_t write_count = 0;
723 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
733 gop_start_test(CommandOptions& Options)
735 using namespace ASDCP::MPEG2;
738 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
739 ui32_t frame_count = 0;
741 Result_t result = Reader.OpenRead(Options.filenames[0]);
743 if ( ASDCP_SUCCESS(result) )
745 MPEG2::VideoDescriptor VDesc;
746 Reader.FillVideoDescriptor(VDesc);
747 frame_count = VDesc.ContainerDuration;
749 if ( Options.verbose_flag )
751 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
752 MPEG2::VideoDescriptorDump(VDesc);
756 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
757 if ( last_frame > frame_count )
758 last_frame = frame_count;
760 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
762 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
764 if ( ASDCP_SUCCESS(result) )
766 if ( Options.verbose_flag )
767 FrameBuffer.Dump(stderr, Options.fb_dump_size);
769 if ( FrameBuffer.FrameType() != FRAME_I )
770 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
772 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
779 //------------------------------------------------------------------------------------------
782 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
783 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
786 write_JP2K_S_file(CommandOptions& Options)
788 AESEncContext* Context = 0;
789 HMACContext* HMAC = 0;
790 JP2K::MXFSWriter Writer;
791 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
792 JP2K::PictureDescriptor PDesc;
793 JP2K::SequenceParser ParserLeft, ParserRight;
794 byte_t IV_buf[CBC_BLOCK_SIZE];
795 Kumu::FortunaRNG RNG;
797 if ( Options.file_count != 2 )
799 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
803 // set up essence parser
804 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
806 if ( ASDCP_SUCCESS(result) )
807 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
810 if ( ASDCP_SUCCESS(result) )
812 ParserLeft.FillPictureDescriptor(PDesc);
813 PDesc.EditRate = Options.PictureRate();
815 if ( Options.verbose_flag )
817 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
818 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
819 JP2K::PictureDescriptorDump(PDesc);
823 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
825 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
826 if ( Options.asset_id_flag )
827 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
829 Kumu::GenRandomUUID(Info.AssetUUID);
831 if ( Options.use_smpte_labels )
833 Info.LabelSetType = LS_MXF_SMPTE;
834 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
837 // configure encryption
838 if( Options.key_flag )
840 Kumu::GenRandomUUID(Info.ContextID);
841 Info.EncryptedEssence = true;
843 if ( Options.key_id_flag )
844 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
846 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
848 Context = new AESEncContext;
849 result = Context->InitKey(Options.key_value);
851 if ( ASDCP_SUCCESS(result) )
852 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
854 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
856 Info.UsesHMAC = true;
857 HMAC = new HMACContext;
858 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
862 if ( ASDCP_SUCCESS(result) )
863 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
866 if ( ASDCP_SUCCESS(result) )
869 result = ParserLeft.Reset();
870 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
872 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
874 result = ParserLeft.ReadFrame(FrameBuffer);
876 if ( ASDCP_SUCCESS(result) )
878 if ( Options.verbose_flag )
879 FrameBuffer.Dump(stderr, Options.fb_dump_size);
881 if ( Options.encrypt_header_flag )
882 FrameBuffer.PlaintextOffset(0);
885 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
886 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
888 if ( ASDCP_SUCCESS(result) )
889 result = ParserRight.ReadFrame(FrameBuffer);
891 if ( ASDCP_SUCCESS(result) )
893 if ( Options.verbose_flag )
894 FrameBuffer.Dump(stderr, Options.fb_dump_size);
896 if ( Options.encrypt_header_flag )
897 FrameBuffer.PlaintextOffset(0);
900 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
901 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
904 if ( result == RESULT_ENDOFFILE )
908 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
909 result = Writer.Finalize();
914 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
915 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
916 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
918 read_JP2K_S_file(CommandOptions& Options)
920 AESDecContext* Context = 0;
921 HMACContext* HMAC = 0;
922 JP2K::MXFSReader Reader;
923 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
924 ui32_t frame_count = 0;
926 Result_t result = Reader.OpenRead(Options.filenames[0]);
928 if ( ASDCP_SUCCESS(result) )
930 JP2K::PictureDescriptor PDesc;
931 Reader.FillPictureDescriptor(PDesc);
933 frame_count = PDesc.ContainerDuration;
935 if ( Options.verbose_flag )
937 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
938 JP2K::PictureDescriptorDump(PDesc);
942 if ( ASDCP_SUCCESS(result) && Options.key_flag )
944 Context = new AESDecContext;
945 result = Context->InitKey(Options.key_value);
947 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
950 Reader.FillWriterInfo(Info);
954 HMAC = new HMACContext;
955 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
959 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
964 const int filename_max = 1024;
965 char filename[filename_max];
966 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
967 if ( last_frame > frame_count )
968 last_frame = frame_count;
970 char left_format[64]; char right_format[64];
971 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
972 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
974 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
976 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
978 if ( ASDCP_SUCCESS(result) )
980 Kumu::FileWriter OutFile;
982 snprintf(filename, filename_max, left_format, Options.file_root, i);
983 result = OutFile.OpenWrite(filename);
985 if ( ASDCP_SUCCESS(result) )
986 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
988 if ( Options.verbose_flag )
989 FrameBuffer.Dump(stderr, Options.fb_dump_size);
992 if ( ASDCP_SUCCESS(result) )
993 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
995 if ( ASDCP_SUCCESS(result) )
997 Kumu::FileWriter OutFile;
999 snprintf(filename, filename_max, right_format, Options.file_root, i);
1000 result = OutFile.OpenWrite(filename);
1002 if ( ASDCP_SUCCESS(result) )
1003 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1012 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1013 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1016 write_JP2K_file(CommandOptions& Options)
1018 AESEncContext* Context = 0;
1019 HMACContext* HMAC = 0;
1020 JP2K::MXFWriter Writer;
1021 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1022 JP2K::PictureDescriptor PDesc;
1023 JP2K::SequenceParser Parser;
1024 byte_t IV_buf[CBC_BLOCK_SIZE];
1025 Kumu::FortunaRNG RNG;
1027 // set up essence parser
1028 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1030 // set up MXF writer
1031 if ( ASDCP_SUCCESS(result) )
1033 Parser.FillPictureDescriptor(PDesc);
1034 PDesc.EditRate = Options.PictureRate();
1036 if ( Options.verbose_flag )
1038 fprintf(stderr, "JPEG 2000 pictures\n");
1039 fputs("PictureDescriptor:\n", stderr);
1040 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1041 JP2K::PictureDescriptorDump(PDesc);
1045 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1047 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1048 if ( Options.asset_id_flag )
1049 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1051 Kumu::GenRandomUUID(Info.AssetUUID);
1053 if ( Options.use_smpte_labels )
1055 Info.LabelSetType = LS_MXF_SMPTE;
1056 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1059 // configure encryption
1060 if( Options.key_flag )
1062 Kumu::GenRandomUUID(Info.ContextID);
1063 Info.EncryptedEssence = true;
1065 if ( Options.key_id_flag )
1066 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1068 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1070 Context = new AESEncContext;
1071 result = Context->InitKey(Options.key_value);
1073 if ( ASDCP_SUCCESS(result) )
1074 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1076 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1078 Info.UsesHMAC = true;
1079 HMAC = new HMACContext;
1080 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1084 if ( ASDCP_SUCCESS(result) )
1085 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1088 if ( ASDCP_SUCCESS(result) )
1090 ui32_t duration = 0;
1091 result = Parser.Reset();
1093 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1095 if ( ! Options.do_repeat || duration == 1 )
1097 result = Parser.ReadFrame(FrameBuffer);
1099 if ( ASDCP_SUCCESS(result) )
1101 if ( Options.verbose_flag )
1102 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1104 if ( Options.encrypt_header_flag )
1105 FrameBuffer.PlaintextOffset(0);
1109 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1111 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1113 // The Writer class will forward the last block of ciphertext
1114 // to the encryption context for use as the IV for the next
1115 // frame. If you want to use non-sequitur IV values, un-comment
1116 // the following line of code.
1117 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1118 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1122 if ( result == RESULT_ENDOFFILE )
1126 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1127 result = Writer.Finalize();
1132 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1133 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1134 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1137 read_JP2K_file(CommandOptions& Options)
1139 AESDecContext* Context = 0;
1140 HMACContext* HMAC = 0;
1141 JP2K::MXFReader Reader;
1142 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1143 ui32_t frame_count = 0;
1145 Result_t result = Reader.OpenRead(Options.filenames[0]);
1147 if ( ASDCP_SUCCESS(result) )
1149 JP2K::PictureDescriptor PDesc;
1150 Reader.FillPictureDescriptor(PDesc);
1152 frame_count = PDesc.ContainerDuration;
1154 if ( Options.verbose_flag )
1156 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1157 JP2K::PictureDescriptorDump(PDesc);
1161 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1163 Context = new AESDecContext;
1164 result = Context->InitKey(Options.key_value);
1166 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1169 Reader.FillWriterInfo(Info);
1171 if ( Info.UsesHMAC )
1173 HMAC = new HMACContext;
1174 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1178 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1183 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1184 if ( last_frame > frame_count )
1185 last_frame = frame_count;
1187 char name_format[64];
1188 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1190 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1192 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1194 if ( ASDCP_SUCCESS(result) )
1196 Kumu::FileWriter OutFile;
1199 snprintf(filename, 256, name_format, Options.file_root, i);
1200 result = OutFile.OpenWrite(filename);
1202 if ( ASDCP_SUCCESS(result) )
1203 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1205 if ( Options.verbose_flag )
1206 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1213 //------------------------------------------------------------------------------------------
1217 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1218 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1221 write_PCM_file(CommandOptions& Options)
1223 AESEncContext* Context = 0;
1224 HMACContext* HMAC = 0;
1225 PCMParserList Parser;
1226 PCM::MXFWriter Writer;
1227 PCM::FrameBuffer FrameBuffer;
1228 PCM::AudioDescriptor ADesc;
1229 Rational PictureRate = Options.PictureRate();
1230 byte_t IV_buf[CBC_BLOCK_SIZE];
1231 Kumu::FortunaRNG RNG;
1233 // set up essence parser
1234 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1236 // set up MXF writer
1237 if ( ASDCP_SUCCESS(result) )
1239 Parser.FillAudioDescriptor(ADesc);
1241 ADesc.EditRate = PictureRate;
1242 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1243 ADesc.ChannelFormat = Options.channel_fmt;
1245 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1247 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1250 if ( Options.verbose_flag )
1252 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1253 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1254 Options.szPictureRate(),
1255 PCM::CalcSamplesPerFrame(ADesc));
1256 fputs("AudioDescriptor:\n", stderr);
1257 PCM::AudioDescriptorDump(ADesc);
1261 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1263 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1264 if ( Options.asset_id_flag )
1265 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1267 Kumu::GenRandomUUID(Info.AssetUUID);
1269 if ( Options.use_smpte_labels )
1271 Info.LabelSetType = LS_MXF_SMPTE;
1272 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1275 // configure encryption
1276 if( Options.key_flag )
1278 Kumu::GenRandomUUID(Info.ContextID);
1279 Info.EncryptedEssence = true;
1281 if ( Options.key_id_flag )
1282 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1284 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1286 Context = new AESEncContext;
1287 result = Context->InitKey(Options.key_value);
1289 if ( ASDCP_SUCCESS(result) )
1290 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1292 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1294 Info.UsesHMAC = true;
1295 HMAC = new HMACContext;
1296 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1300 if ( ASDCP_SUCCESS(result) )
1301 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1304 if ( ASDCP_SUCCESS(result) )
1306 result = Parser.Reset();
1307 ui32_t duration = 0;
1309 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1311 result = Parser.ReadFrame(FrameBuffer);
1313 if ( ASDCP_SUCCESS(result) )
1315 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1317 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1318 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1319 result = RESULT_ENDOFFILE;
1323 if ( Options.verbose_flag )
1324 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1326 if ( ! Options.no_write_flag )
1328 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1330 // The Writer class will forward the last block of ciphertext
1331 // to the encryption context for use as the IV for the next
1332 // frame. If you want to use non-sequitur IV values, un-comment
1333 // the following line of code.
1334 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1335 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1340 if ( result == RESULT_ENDOFFILE )
1344 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1345 result = Writer.Finalize();
1350 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1351 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1352 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1355 read_PCM_file(CommandOptions& Options)
1357 AESDecContext* Context = 0;
1358 HMACContext* HMAC = 0;
1359 PCM::MXFReader Reader;
1360 PCM::FrameBuffer FrameBuffer;
1361 WavFileWriter OutWave;
1362 PCM::AudioDescriptor ADesc;
1363 ui32_t last_frame = 0;
1365 Result_t result = Reader.OpenRead(Options.filenames[0]);
1367 if ( ASDCP_SUCCESS(result) )
1369 Reader.FillAudioDescriptor(ADesc);
1371 if ( ADesc.EditRate != EditRate_23_98
1372 && ADesc.EditRate != EditRate_24
1373 && ADesc.EditRate != EditRate_25
1374 && ADesc.EditRate != EditRate_30
1375 && ADesc.EditRate != EditRate_48
1376 && ADesc.EditRate != EditRate_50
1377 && ADesc.EditRate != EditRate_60 )
1378 ADesc.EditRate = Options.PictureRate();
1380 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1382 if ( Options.verbose_flag )
1383 PCM::AudioDescriptorDump(ADesc);
1386 if ( ASDCP_SUCCESS(result) )
1388 last_frame = ADesc.ContainerDuration;
1390 if ( Options.duration > 0 && Options.duration < last_frame )
1391 last_frame = Options.duration;
1393 if ( Options.start_frame > 0 )
1395 if ( Options.start_frame > ADesc.ContainerDuration )
1397 fprintf(stderr, "Start value greater than file duration.\n");
1401 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1404 ADesc.ContainerDuration = last_frame - Options.start_frame;
1405 OutWave.OpenWrite(ADesc, Options.file_root,
1406 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1407 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1410 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1412 Context = new AESDecContext;
1413 result = Context->InitKey(Options.key_value);
1415 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1418 Reader.FillWriterInfo(Info);
1420 if ( Info.UsesHMAC )
1422 HMAC = new HMACContext;
1423 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1427 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1432 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1434 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1436 if ( ASDCP_SUCCESS(result) )
1438 if ( Options.verbose_flag )
1439 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1441 result = OutWave.WriteFrame(FrameBuffer);
1449 //------------------------------------------------------------------------------------------
1450 // TimedText essence
1453 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1454 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1457 write_timed_text_file(CommandOptions& Options)
1459 AESEncContext* Context = 0;
1460 HMACContext* HMAC = 0;
1461 TimedText::DCSubtitleParser Parser;
1462 TimedText::MXFWriter Writer;
1463 TimedText::FrameBuffer FrameBuffer;
1464 TimedText::TimedTextDescriptor TDesc;
1465 byte_t IV_buf[CBC_BLOCK_SIZE];
1466 Kumu::FortunaRNG RNG;
1468 // set up essence parser
1469 Result_t result = Parser.OpenRead(Options.filenames[0]);
1471 // set up MXF writer
1472 if ( ASDCP_SUCCESS(result) )
1474 Parser.FillTimedTextDescriptor(TDesc);
1475 FrameBuffer.Capacity(Options.fb_size);
1477 if ( Options.verbose_flag )
1479 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1480 TimedText::DescriptorDump(TDesc);
1484 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1486 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1487 if ( Options.asset_id_flag )
1488 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1490 Kumu::GenRandomUUID(Info.AssetUUID);
1492 if ( Options.use_smpte_labels )
1494 Info.LabelSetType = LS_MXF_SMPTE;
1495 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1498 // configure encryption
1499 if( Options.key_flag )
1501 Kumu::GenRandomUUID(Info.ContextID);
1502 Info.EncryptedEssence = true;
1504 if ( Options.key_id_flag )
1505 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1507 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1509 Context = new AESEncContext;
1510 result = Context->InitKey(Options.key_value);
1512 if ( ASDCP_SUCCESS(result) )
1513 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1515 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1517 Info.UsesHMAC = true;
1518 HMAC = new HMACContext;
1519 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1523 if ( ASDCP_SUCCESS(result) )
1524 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1527 if ( ASDCP_FAILURE(result) )
1531 TimedText::ResourceList_t::const_iterator ri;
1533 result = Parser.ReadTimedTextResource(XMLDoc);
1535 if ( ASDCP_SUCCESS(result) )
1536 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1538 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1540 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1542 if ( ASDCP_SUCCESS(result) )
1544 if ( Options.verbose_flag )
1545 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1547 if ( ! Options.no_write_flag )
1549 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1551 // The Writer class will forward the last block of ciphertext
1552 // to the encryption context for use as the IV for the next
1553 // frame. If you want to use non-sequitur IV values, un-comment
1554 // the following line of code.
1555 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1556 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1560 if ( result == RESULT_ENDOFFILE )
1564 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1565 result = Writer.Finalize();
1571 // Read one or more timed text streams from a plaintext ASDCP file
1572 // Read one or more timed text streams from a ciphertext ASDCP file
1573 // Read one or more timed text streams from a ciphertext ASDCP file
1576 read_timed_text_file(CommandOptions& Options)
1578 AESDecContext* Context = 0;
1579 HMACContext* HMAC = 0;
1580 TimedText::MXFReader Reader;
1581 TimedText::FrameBuffer FrameBuffer;
1582 TimedText::TimedTextDescriptor TDesc;
1584 Result_t result = Reader.OpenRead(Options.filenames[0]);
1586 if ( ASDCP_SUCCESS(result) )
1588 Reader.FillTimedTextDescriptor(TDesc);
1589 FrameBuffer.Capacity(Options.fb_size);
1591 if ( Options.verbose_flag )
1592 TimedText::DescriptorDump(TDesc);
1595 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1597 Context = new AESDecContext;
1598 result = Context->InitKey(Options.key_value);
1600 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1603 Reader.FillWriterInfo(Info);
1605 if ( Info.UsesHMAC )
1607 HMAC = new HMACContext;
1608 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1612 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1617 if ( ASDCP_FAILURE(result) )
1621 std::string out_path = Kumu::PathDirname(Options.file_root);
1624 TimedText::ResourceList_t::const_iterator ri;
1626 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1628 if ( ASDCP_SUCCESS(result) )
1630 Kumu::FileWriter Writer;
1631 result = Writer.OpenWrite(Options.file_root);
1633 if ( ASDCP_SUCCESS(result) )
1634 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1637 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1639 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1641 if ( ASDCP_SUCCESS(result) )
1643 Kumu::FileWriter Writer;
1644 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1646 if ( ASDCP_SUCCESS(result) )
1648 if ( Options.verbose_flag )
1649 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1651 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1659 //------------------------------------------------------------------------------------------
1663 // These classes wrap the irregular names in the asdcplib API
1664 // so that I can use a template to simplify the implementation
1665 // of show_file_info()
1667 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1670 void FillDescriptor(MPEG2::MXFReader& Reader) {
1671 Reader.FillVideoDescriptor(*this);
1674 void Dump(FILE* stream) {
1675 MPEG2::VideoDescriptorDump(*this, stream);
1679 class MyPictureDescriptor : public JP2K::PictureDescriptor
1682 void FillDescriptor(JP2K::MXFReader& Reader) {
1683 Reader.FillPictureDescriptor(*this);
1686 void Dump(FILE* stream) {
1687 JP2K::PictureDescriptorDump(*this, stream);
1691 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1694 void FillDescriptor(JP2K::MXFSReader& Reader) {
1695 Reader.FillPictureDescriptor(*this);
1698 void Dump(FILE* stream) {
1699 JP2K::PictureDescriptorDump(*this, stream);
1703 class MyAudioDescriptor : public PCM::AudioDescriptor
1706 void FillDescriptor(PCM::MXFReader& Reader) {
1707 Reader.FillAudioDescriptor(*this);
1710 void Dump(FILE* stream) {
1711 PCM::AudioDescriptorDump(*this, stream);
1715 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1718 void FillDescriptor(TimedText::MXFReader& Reader) {
1719 Reader.FillTimedTextDescriptor(*this);
1722 void Dump(FILE* stream) {
1723 TimedText::DescriptorDump(*this, stream);
1727 // MSVC didn't like the function template, so now it's a static class method
1728 template<class ReaderT, class DescriptorT>
1729 class FileInfoWrapper
1733 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1735 assert(type_string);
1739 Result_t result = RESULT_OK;
1741 if ( Options.verbose_flag || Options.showheader_flag )
1744 result = Reader.OpenRead(Options.filenames[0]);
1746 if ( ASDCP_SUCCESS(result) )
1748 fprintf(stdout, "File essence type is %s.\n", type_string);
1750 if ( Options.showheader_flag )
1751 Reader.DumpHeaderMetadata(stream);
1754 Reader.FillWriterInfo(WI);
1755 WriterInfoDump(WI, stream);
1758 Desc.FillDescriptor(Reader);
1761 if ( Options.showindex_flag )
1762 Reader.DumpIndex(stream);
1764 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1766 Reader.DumpHeaderMetadata(stream);
1774 // Read header metadata from an ASDCP file
1777 show_file_info(CommandOptions& Options)
1779 EssenceType_t EssenceType;
1780 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1782 if ( ASDCP_FAILURE(result) )
1785 if ( EssenceType == ESS_MPEG2_VES )
1787 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1789 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1791 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1793 if ( ASDCP_SUCCESS(result) )
1795 const Dictionary* Dict = &DefaultCompositeDict();
1796 PCM::MXFReader Reader;
1797 MXF::OPAtomHeader OPAtomHeader(Dict);
1798 MXF::WaveAudioDescriptor *descriptor = 0;
1800 result = Reader.OpenRead(Options.filenames[0]);
1802 if ( ASDCP_SUCCESS(result) )
1803 result = Reader.OPAtomHeader().GetMDObjectByType(Dict->ul(MDD_WaveAudioDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
1805 if ( ASDCP_SUCCESS(result) )
1808 fprintf(stdout, " ChannelAssignment: %s\n", descriptor->ChannelAssignment.EncodeString(buf, 64));
1812 else if ( EssenceType == ESS_JPEG_2000 )
1814 if ( Options.stereo_image_flag )
1816 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1817 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1821 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1822 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1825 else if ( EssenceType == ESS_JPEG_2000_S )
1827 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1828 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1830 else if ( EssenceType == ESS_TIMED_TEXT )
1832 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1836 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1837 Kumu::FileReader Reader;
1838 const Dictionary* Dict = &DefaultCompositeDict();
1839 MXF::OPAtomHeader TestHeader(Dict);
1841 result = Reader.OpenRead(Options.filenames[0]);
1843 if ( ASDCP_SUCCESS(result) )
1844 result = TestHeader.InitFromFile(Reader); // test UL and OP
1846 if ( ASDCP_SUCCESS(result) )
1848 TestHeader.Partition::Dump(stdout);
1850 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1853 fputs("File contains no Identification object.\n", stdout);
1855 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1858 fputs("File contains no SourcePackage object.\n", stdout);
1862 fputs("File is not MXF.\n", stdout);
1872 digest_file(const char* filename)
1874 using namespace Kumu;
1876 ASDCP_TEST_NULL_STR(filename);
1880 ByteString Buf(8192);
1882 Result_t result = Reader.OpenRead(filename);
1884 while ( ASDCP_SUCCESS(result) )
1886 ui32_t read_count = 0;
1887 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1889 if ( result == RESULT_ENDOFFILE )
1895 if ( ASDCP_SUCCESS(result) )
1896 SHA1_Update(&Ctx, Buf.Data(), read_count);
1899 if ( ASDCP_SUCCESS(result) )
1901 const ui32_t sha_len = 20;
1902 byte_t bin_buf[sha_len];
1904 SHA1_Final(bin_buf, &Ctx);
1906 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1914 main(int argc, const char** argv)
1916 Result_t result = RESULT_OK;
1918 CommandOptions Options(argc, argv);
1920 if ( Options.version_flag )
1923 if ( Options.help_flag )
1926 if ( Options.version_flag || Options.help_flag )
1929 if ( Options.error_flag )
1931 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1935 if ( Options.mode == MMT_INFO )
1937 result = show_file_info(Options);
1939 for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1941 Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1942 result = show_file_info(Options);
1945 else if ( Options.mode == MMT_GOP_START )
1947 result = gop_start_test(Options);
1949 else if ( Options.mode == MMT_GEN_KEY )
1951 Kumu::FortunaRNG RNG;
1952 byte_t bin_buf[KeyLen];
1954 RNG.FillRandom(bin_buf, KeyLen);
1955 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1957 else if ( Options.mode == MMT_GEN_ID )
1960 Kumu::GenRandomValue(TmpID);
1961 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1963 else if ( Options.mode == MMT_DIGEST )
1965 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1966 result = digest_file(Options.filenames[i]);
1968 else if ( Options.mode == MMT_UL_LIST )
1970 if ( Options.use_smpte_labels )
1971 DefaultSMPTEDict().Dump(stdout);
1973 DefaultInteropDict().Dump(stdout);
1975 else if ( Options.mode == MMT_EXTRACT )
1977 EssenceType_t EssenceType;
1978 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1980 if ( ASDCP_SUCCESS(result) )
1982 switch ( EssenceType )
1985 result = read_MPEG2_file(Options);
1989 if ( Options.stereo_image_flag )
1990 result = read_JP2K_S_file(Options);
1992 result = read_JP2K_file(Options);
1995 case ESS_JPEG_2000_S:
1996 result = read_JP2K_S_file(Options);
1999 case ESS_PCM_24b_48k:
2000 case ESS_PCM_24b_96k:
2001 result = read_PCM_file(Options);
2004 case ESS_TIMED_TEXT:
2005 result = read_timed_text_file(Options);
2009 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
2014 else if ( Options.mode == MMT_CREATE )
2016 if ( Options.do_repeat && ! Options.duration_flag )
2018 fputs("Option -R requires -d <duration>\n", stderr);
2022 EssenceType_t EssenceType;
2023 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
2025 if ( ASDCP_SUCCESS(result) )
2027 switch ( EssenceType )
2030 result = write_MPEG2_file(Options);
2034 if ( Options.stereo_image_flag )
2035 result = write_JP2K_S_file(Options);
2038 result = write_JP2K_file(Options);
2042 case ESS_PCM_24b_48k:
2043 case ESS_PCM_24b_96k:
2044 result = write_PCM_file(Options);
2047 case ESS_TIMED_TEXT:
2048 result = write_timed_text_file(Options);
2052 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2053 Options.filenames[0]);
2060 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2064 if ( ASDCP_FAILURE(result) )
2066 fputs("Program stopped on error.\n", stderr);
2068 if ( result == RESULT_SFORMAT )
2070 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2072 else if ( result != RESULT_FAIL )
2074 fputs(result, stderr);
2075 fputc('\n', stderr);
2086 // end asdcp-test.cpp