2 Copyright (c) 2003-2012, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14 derived from this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /*! \file asdcp-test.cpp
29 \brief AS-DCP file manipulation utility
31 This program provides command line access to the major features of the asdcplib
32 library, and serves as a library unit test which provides the functionality of
33 the supported use cases.
35 For more information about asdcplib, please refer to the header file AS_DCP.h
37 WARNING: While the asdcplib library attempts to provide a complete and secure
38 implementation of the cryptographic features of the AS-DCP file formats, this
39 unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40 production environment without some modification.
42 In particular, this program uses weak IV generation and externally generated
43 plaintext keys. These shortcomings exist because cryptographic-quality
44 random number generation and key management are outside the scope of the
45 asdcplib library. Developers using asdcplib for commercial implementations
46 claiming SMPTE conformance are expected to provide proper implementations of
50 #include <KM_fileio.h>
52 #include <PCMParserList.h>
53 #include <WavFileWriter.h>
56 #include <openssl/sha.h>
61 using namespace ASDCP;
63 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
65 //------------------------------------------------------------------------------------------
67 // command line option parser class
69 static const char* PROGRAM_NAME = "asdcp-test"; // program name for messages
70 const ui32_t MAX_IN_FILES = 16; // maximum number of input files handled by
71 // the command option parser
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
79 static byte_t default_ProductUUID_Data[UUIDlen] =
80 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
83 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84 CompanyName = "WidgetCo";
85 ProductName = "asdcp-test";
86 ProductVersion = ASDCP::Version();
92 // Increment the iterator, test for an additional non-option command line argument.
93 // Causes the caller to return if there are no remaining arguments or if the next
94 // argument begins with '-'.
95 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
97 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
102 banner(FILE* stream = stdout)
105 %s (asdcplib %s)\n\n\
106 Copyright (c) 2003-2012 John Hurst\n\n\
107 asdcplib may be copied only under the terms of the license found at\n\
108 the top of every file in the asdcplib distribution kit.\n\n\
109 Specify the -h (help) option for further information about %s\n\n",
110 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
115 usage(FILE* stream = stdout)
118 USAGE: %s -c <output-file> [-3] [-a <uuid>] [-b <buffer-size>]\n\
119 [-d <duration>] [-e|-E] [-f <start-frame>] [-j <key-id-string>]\n\
120 [-k <key-string>] [-l <label>] [-L] [-M] [-p <frame-rate>] [-R]\n\
121 [-s <num>] [-v] [-W] [-z|-Z] <input-file> [<input-file-2> ...]\n\
123 %s [-h|-help] [-V]\n\
125 %s -i [-H] [-n] [-v] <input-file>\n\
129 %s -G [-v] <input-file>\n\
131 %s -t <input-file>\n\
133 %s -x <file-prefix> [-3] [-b <buffer-size>] [-d <duration>]\n\
134 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
135 [-v] [-W] [-w] <input-file>\n\n",
136 PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME,
137 PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
141 -3 - With -c, create a stereoscopic image file. Expects two\n\
142 directories of JP2K codestreams (directories must have\n\
143 an equal number of frames; left eye is first).\n\
144 - With -x, force stereoscopic interpretation of a JP2K\n\
146 -c <output-file> - Create an AS-DCP track file from input(s)\n\
147 -g - Generate a random 16 byte value to stdout\n\
148 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
149 -h | -help - Show help\n\
150 -i - Show file info\n\
151 -t - Calculate message digest of input file\n\
152 -U - Dump UL catalog to stdout\n\
153 -u - Generate a random UUID value to stdout\n\
154 -V - Show version information\n\
155 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
160 -e - Encrypt MPEG or JP2K headers (default)\n\
161 -E - Do not encrypt MPEG or JP2K headers\n\
162 -j <key-id-str> - Write key ID instead of creating a random value\n\
163 -k <key-string> - Use key for ciphertext operations\n\
164 -m - verify HMAC values when reading\n\
165 -M - Do not create HMAC values when writing\n\
169 Read/Write Options:\n\
170 -a <UUID> - Specify the Asset ID of a file (with -c)\n\
171 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
172 Defaults to 4,194,304 (4MB)\n\
173 -d <duration> - Number of frames to process, default all\n\
174 -f <start-frame> - Starting frame number, default 0\n\
175 -l <label> - Use given channel format label when writing MXF sound\n\
176 files. SMPTE 429-2 labels: '5.1', '6.1', '7.1', '7.1DS', 'WTF'.\n\
177 Default is no label (valid for Interop only).\n\
178 -L - Write SMPTE UL values instead of MXF Interop\n\
179 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
180 Use one of [23|24|25|30|48|50|60], 24 is default\n\
181 -R - Repeat the first frame over the entire file (picture\n\
182 essence only, requires -c, -d)\n\
183 -S - Split Wave essence to stereo WAV files during extract.\n\
184 Default is multichannel WAV\n\
185 -1 - Split Wave essence to mono WAV files during extract.\n\
186 Default is multichannel WAV\n\
187 -W - Read input file only, do not write source file\n\
188 -w <width> - Width of numeric element in a series of frame file names\n\
189 (use with -x, default 6).\n\
190 -z - Fail if j2c inputs have unequal parameters (default)\n\
191 -Z - Ignore unequal parameters in j2c inputs\n\
196 -H - Show MXF header metadata, used with option -i\n\
197 -n - Show index, used with option -i\n\
200 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
201 stderr, used with option -v\n\
202 -v - Verbose, prints informative messages to stderr\n\
204 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
205 o All option arguments must be separated from the option by whitespace.\n\
206 o An argument of \"23\" to the -p option will be interpreted\n\
207 as 24000/1001 fps.\n\
227 decode_channel_fmt(const std::string& label_name)
229 if ( label_name == "5.1" )
230 return PCM::CF_CFG_1;
232 else if ( label_name == "6.1" )
233 return PCM::CF_CFG_2;
235 else if ( label_name == "7.1" )
236 return PCM::CF_CFG_3;
238 else if ( label_name == "WTF" )
239 return PCM::CF_CFG_4;
241 else if ( label_name == "7.1DS" )
242 return PCM::CF_CFG_5;
244 else if ( label_name == "MCA" )
245 return PCM::CF_CFG_6;
247 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
248 fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
260 bool error_flag; // true if the given options are in error or not complete
261 bool key_flag; // true if an encryption key was given
262 bool key_id_flag; // true if a key ID was given
263 bool asset_id_flag; // true if an asset ID was given
264 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
265 bool write_hmac; // true if HMAC values are to be generated and written
266 bool read_hmac; // true if HMAC values are to be validated
267 bool split_wav; // true if PCM is to be extracted to stereo WAV files
268 bool mono_wav; // true if PCM is to be extracted to mono WAV files
269 bool verbose_flag; // true if the verbose option was selected
270 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
271 bool showindex_flag; // true if index is to be displayed
272 bool showheader_flag; // true if MXF file header is to be displayed
273 bool no_write_flag; // true if no output files are to be written
274 bool version_flag; // true if the version display option was selected
275 bool help_flag; // true if the help display option was selected
276 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
277 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
278 ui32_t start_frame; // frame number to begin processing
279 ui32_t duration; // number of frames to be processed
280 bool duration_flag; // true if duration argument given
281 bool do_repeat; // if true and -c -d, repeat first input frame
282 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
283 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
284 ui32_t picture_rate; // fps of picture when wrapping PCM
285 ui32_t fb_size; // size of picture frame buffer
286 ui32_t file_count; // number of elements in filenames[]
287 const char* file_root; // filename pre for files written by the extract mode
288 const char* out_file; // name of mxf file created by create mode
289 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
290 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
291 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
292 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
293 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
296 Rational PictureRate()
298 if ( picture_rate == 16 ) return EditRate_16;
299 if ( picture_rate == 18 ) return EditRate_18;
300 if ( picture_rate == 20 ) return EditRate_20;
301 if ( picture_rate == 22 ) return EditRate_22;
302 if ( picture_rate == 23 ) return EditRate_23_98;
303 if ( picture_rate == 24 ) return EditRate_24;
304 if ( picture_rate == 25 ) return EditRate_25;
305 if ( picture_rate == 30 ) return EditRate_30;
306 if ( picture_rate == 48 ) return EditRate_48;
307 if ( picture_rate == 50 ) return EditRate_50;
308 if ( picture_rate == 60 ) return EditRate_60;
309 if ( picture_rate == 96 ) return EditRate_96;
310 if ( picture_rate == 100 ) return EditRate_100;
311 if ( picture_rate == 120 ) return EditRate_120;
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";
336 CommandOptions(int argc, const char** argv) :
337 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
338 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
339 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
340 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
341 number_width(6), start_frame(0),
342 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
343 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
344 channel_fmt(PCM::CF_NONE)
346 memset(key_value, 0, KeyLen);
347 memset(key_id_value, 0, UUIDlen);
349 for ( int i = 1; i < argc; i++ )
352 if ( (strcmp( argv[i], "-help") == 0) )
358 if ( argv[i][0] == '-'
359 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
362 switch ( argv[i][1] )
364 case '1': mono_wav = true; break;
365 case '2': split_wav = true; break;
366 case '3': stereo_image_flag = true; break;
369 asset_id_flag = true;
370 TEST_EXTRA_ARG(i, 'a');
373 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
375 if ( length != UUIDlen )
377 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
384 TEST_EXTRA_ARG(i, 'b');
385 fb_size = abs(atoi(argv[i]));
388 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
393 TEST_EXTRA_ARG(i, 'c');
399 TEST_EXTRA_ARG(i, 'd');
400 duration_flag = true;
401 duration = abs(atoi(argv[i]));
404 case 'E': encrypt_header_flag = false; break;
405 case 'e': encrypt_header_flag = true; break;
408 TEST_EXTRA_ARG(i, 'f');
409 start_frame = abs(atoi(argv[i]));
412 case 'G': mode = MMT_GOP_START; break;
413 case 'g': mode = MMT_GEN_KEY; break;
414 case 'H': showheader_flag = true; break;
415 case 'h': help_flag = true; break;
416 case 'i': mode = MMT_INFO; break;
418 case 'j': key_id_flag = true;
419 TEST_EXTRA_ARG(i, 'j');
422 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
424 if ( length != UUIDlen )
426 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
432 case 'k': key_flag = true;
433 TEST_EXTRA_ARG(i, 'k');
436 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
438 if ( length != KeyLen )
440 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
447 TEST_EXTRA_ARG(i, 'l');
448 channel_fmt = decode_channel_fmt(argv[i]);
451 case 'L': use_smpte_labels = true; break;
452 case 'M': write_hmac = false; break;
453 case 'm': read_hmac = true; break;
454 case 'n': showindex_flag = true; break;
457 TEST_EXTRA_ARG(i, 'p');
458 picture_rate = abs(atoi(argv[i]));
461 case 'R': do_repeat = true; break;
462 case 'S': split_wav = true; break;
465 TEST_EXTRA_ARG(i, 's');
466 fb_dump_size = abs(atoi(argv[i]));
469 case 't': mode = MMT_DIGEST; break;
470 case 'U': mode = MMT_UL_LIST; break;
471 case 'u': mode = MMT_GEN_ID; break;
472 case 'V': version_flag = true; break;
473 case 'v': verbose_flag = true; break;
474 case 'W': no_write_flag = true; break;
477 TEST_EXTRA_ARG(i, 'w');
478 number_width = abs(atoi(argv[i]));
482 TEST_EXTRA_ARG(i, 'x');
487 case 'Z': j2c_pedantic = false; break;
488 case 'z': j2c_pedantic = true; break;
491 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
498 if ( argv[i][0] != '-' )
500 filenames[file_count++] = argv[i];
504 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
508 if ( file_count >= MAX_IN_FILES )
510 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
516 if ( help_flag || version_flag )
519 if ( ( mode == MMT_INFO
520 || mode == MMT_CREATE
521 || mode == MMT_EXTRACT
522 || mode == MMT_GOP_START
523 || mode == MMT_DIGEST ) && file_count == 0 )
525 fputs("Option requires at least one filename argument.\n", stderr);
529 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
531 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
539 //------------------------------------------------------------------------------------------
542 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
543 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
546 write_MPEG2_file(CommandOptions& Options)
548 AESEncContext* Context = 0;
549 HMACContext* HMAC = 0;
550 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
551 MPEG2::Parser Parser;
552 MPEG2::MXFWriter Writer;
553 MPEG2::VideoDescriptor VDesc;
554 byte_t IV_buf[CBC_BLOCK_SIZE];
555 Kumu::FortunaRNG RNG;
557 // set up essence parser
558 Result_t result = Parser.OpenRead(Options.filenames[0]);
561 if ( ASDCP_SUCCESS(result) )
563 Parser.FillVideoDescriptor(VDesc);
565 if ( Options.verbose_flag )
567 fputs("MPEG-2 Pictures\n", stderr);
568 fputs("VideoDescriptor:\n", stderr);
569 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
570 MPEG2::VideoDescriptorDump(VDesc);
574 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
576 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
577 if ( Options.asset_id_flag )
578 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
580 Kumu::GenRandomUUID(Info.AssetUUID);
582 if ( Options.use_smpte_labels )
584 Info.LabelSetType = LS_MXF_SMPTE;
585 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
588 // configure encryption
589 if( Options.key_flag )
591 Kumu::GenRandomUUID(Info.ContextID);
592 Info.EncryptedEssence = true;
594 if ( Options.key_id_flag )
595 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
597 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
599 Context = new AESEncContext;
600 result = Context->InitKey(Options.key_value);
602 if ( ASDCP_SUCCESS(result) )
603 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
605 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
607 Info.UsesHMAC = true;
608 HMAC = new HMACContext;
609 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
613 if ( ASDCP_SUCCESS(result) )
614 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
617 if ( ASDCP_SUCCESS(result) )
618 // loop through the frames
620 result = Parser.Reset();
623 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
625 if ( ! Options.do_repeat || duration == 1 )
627 result = Parser.ReadFrame(FrameBuffer);
629 if ( ASDCP_SUCCESS(result) )
631 if ( Options.verbose_flag )
632 FrameBuffer.Dump(stderr, Options.fb_dump_size);
634 if ( Options.encrypt_header_flag )
635 FrameBuffer.PlaintextOffset(0);
639 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
641 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
643 // The Writer class will forward the last block of ciphertext
644 // to the encryption context for use as the IV for the next
645 // frame. If you want to use non-sequitur IV values, un-comment
646 // the following line of code.
647 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
648 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
652 if ( result == RESULT_ENDOFFILE )
656 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
657 result = Writer.Finalize();
662 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
663 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
664 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
667 read_MPEG2_file(CommandOptions& Options)
669 AESDecContext* Context = 0;
670 HMACContext* HMAC = 0;
671 MPEG2::MXFReader Reader;
672 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
673 Kumu::FileWriter OutFile;
674 ui32_t frame_count = 0;
676 Result_t result = Reader.OpenRead(Options.filenames[0]);
678 if ( ASDCP_SUCCESS(result) )
680 MPEG2::VideoDescriptor VDesc;
681 Reader.FillVideoDescriptor(VDesc);
682 frame_count = VDesc.ContainerDuration;
684 if ( Options.verbose_flag )
686 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
687 MPEG2::VideoDescriptorDump(VDesc);
691 if ( ASDCP_SUCCESS(result) )
694 snprintf(filename, 256, "%s.ves", Options.file_root);
695 result = OutFile.OpenWrite(filename);
698 if ( ASDCP_SUCCESS(result) && Options.key_flag )
700 Context = new AESDecContext;
701 result = Context->InitKey(Options.key_value);
703 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
706 Reader.FillWriterInfo(Info);
710 HMAC = new HMACContext;
711 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
715 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
720 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
721 if ( last_frame > frame_count )
722 last_frame = frame_count;
724 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
726 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
728 if ( ASDCP_SUCCESS(result) )
730 if ( Options.verbose_flag )
731 FrameBuffer.Dump(stderr, Options.fb_dump_size);
733 ui32_t write_count = 0;
734 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
744 gop_start_test(CommandOptions& Options)
746 using namespace ASDCP::MPEG2;
749 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
750 ui32_t frame_count = 0;
752 Result_t result = Reader.OpenRead(Options.filenames[0]);
754 if ( ASDCP_SUCCESS(result) )
756 MPEG2::VideoDescriptor VDesc;
757 Reader.FillVideoDescriptor(VDesc);
758 frame_count = VDesc.ContainerDuration;
760 if ( Options.verbose_flag )
762 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
763 MPEG2::VideoDescriptorDump(VDesc);
767 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
768 if ( last_frame > frame_count )
769 last_frame = frame_count;
771 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
773 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
775 if ( ASDCP_SUCCESS(result) )
777 if ( Options.verbose_flag )
778 FrameBuffer.Dump(stderr, Options.fb_dump_size);
780 if ( FrameBuffer.FrameType() != FRAME_I )
781 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
783 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
790 //------------------------------------------------------------------------------------------
793 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
794 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
797 write_JP2K_S_file(CommandOptions& Options)
799 AESEncContext* Context = 0;
800 HMACContext* HMAC = 0;
801 JP2K::MXFSWriter Writer;
802 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
803 JP2K::PictureDescriptor PDesc;
804 JP2K::SequenceParser ParserLeft, ParserRight;
805 byte_t IV_buf[CBC_BLOCK_SIZE];
806 Kumu::FortunaRNG RNG;
808 if ( Options.file_count != 2 )
810 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
814 // set up essence parser
815 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
817 if ( ASDCP_SUCCESS(result) )
818 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
821 if ( ASDCP_SUCCESS(result) )
823 ParserLeft.FillPictureDescriptor(PDesc);
824 PDesc.EditRate = Options.PictureRate();
826 if ( Options.verbose_flag )
828 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
829 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
830 JP2K::PictureDescriptorDump(PDesc);
834 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
836 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
837 if ( Options.asset_id_flag )
838 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
840 Kumu::GenRandomUUID(Info.AssetUUID);
842 if ( Options.use_smpte_labels )
844 Info.LabelSetType = LS_MXF_SMPTE;
845 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
848 // configure encryption
849 if( Options.key_flag )
851 Kumu::GenRandomUUID(Info.ContextID);
852 Info.EncryptedEssence = true;
854 if ( Options.key_id_flag )
855 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
857 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
859 Context = new AESEncContext;
860 result = Context->InitKey(Options.key_value);
862 if ( ASDCP_SUCCESS(result) )
863 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
865 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
867 Info.UsesHMAC = true;
868 HMAC = new HMACContext;
869 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
873 if ( ASDCP_SUCCESS(result) )
874 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
877 if ( ASDCP_SUCCESS(result) )
880 result = ParserLeft.Reset();
881 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
883 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
885 result = ParserLeft.ReadFrame(FrameBuffer);
887 if ( ASDCP_SUCCESS(result) )
889 if ( Options.verbose_flag )
890 FrameBuffer.Dump(stderr, Options.fb_dump_size);
892 if ( Options.encrypt_header_flag )
893 FrameBuffer.PlaintextOffset(0);
896 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
897 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
899 if ( ASDCP_SUCCESS(result) )
900 result = ParserRight.ReadFrame(FrameBuffer);
902 if ( ASDCP_SUCCESS(result) )
904 if ( Options.verbose_flag )
905 FrameBuffer.Dump(stderr, Options.fb_dump_size);
907 if ( Options.encrypt_header_flag )
908 FrameBuffer.PlaintextOffset(0);
911 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
912 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
915 if ( result == RESULT_ENDOFFILE )
919 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
920 result = Writer.Finalize();
925 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
926 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
927 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
929 read_JP2K_S_file(CommandOptions& Options)
931 AESDecContext* Context = 0;
932 HMACContext* HMAC = 0;
933 JP2K::MXFSReader Reader;
934 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
935 ui32_t frame_count = 0;
937 Result_t result = Reader.OpenRead(Options.filenames[0]);
939 if ( ASDCP_SUCCESS(result) )
941 JP2K::PictureDescriptor PDesc;
942 Reader.FillPictureDescriptor(PDesc);
944 frame_count = PDesc.ContainerDuration;
946 if ( Options.verbose_flag )
948 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
949 JP2K::PictureDescriptorDump(PDesc);
953 if ( ASDCP_SUCCESS(result) && Options.key_flag )
955 Context = new AESDecContext;
956 result = Context->InitKey(Options.key_value);
958 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
961 Reader.FillWriterInfo(Info);
965 HMAC = new HMACContext;
966 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
970 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
975 const int filename_max = 1024;
976 char filename[filename_max];
977 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
978 if ( last_frame > frame_count )
979 last_frame = frame_count;
981 char left_format[64]; char right_format[64];
982 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
983 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
985 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
987 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
989 if ( ASDCP_SUCCESS(result) )
991 Kumu::FileWriter OutFile;
993 snprintf(filename, filename_max, left_format, Options.file_root, i);
994 result = OutFile.OpenWrite(filename);
996 if ( ASDCP_SUCCESS(result) )
997 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
999 if ( Options.verbose_flag )
1000 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1003 if ( ASDCP_SUCCESS(result) )
1004 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
1006 if ( ASDCP_SUCCESS(result) )
1008 Kumu::FileWriter OutFile;
1010 snprintf(filename, filename_max, right_format, Options.file_root, i);
1011 result = OutFile.OpenWrite(filename);
1013 if ( ASDCP_SUCCESS(result) )
1014 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1023 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1024 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1027 write_JP2K_file(CommandOptions& Options)
1029 AESEncContext* Context = 0;
1030 HMACContext* HMAC = 0;
1031 JP2K::MXFWriter Writer;
1032 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1033 JP2K::PictureDescriptor PDesc;
1034 JP2K::SequenceParser Parser;
1035 byte_t IV_buf[CBC_BLOCK_SIZE];
1036 Kumu::FortunaRNG RNG;
1038 // set up essence parser
1039 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1041 // set up MXF writer
1042 if ( ASDCP_SUCCESS(result) )
1044 Parser.FillPictureDescriptor(PDesc);
1045 PDesc.EditRate = Options.PictureRate();
1047 if ( Options.verbose_flag )
1049 fprintf(stderr, "JPEG 2000 pictures\n");
1050 fputs("PictureDescriptor:\n", stderr);
1051 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1052 JP2K::PictureDescriptorDump(PDesc);
1056 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1058 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1059 if ( Options.asset_id_flag )
1060 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1062 Kumu::GenRandomUUID(Info.AssetUUID);
1064 if ( Options.use_smpte_labels )
1066 Info.LabelSetType = LS_MXF_SMPTE;
1067 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1070 // configure encryption
1071 if( Options.key_flag )
1073 Kumu::GenRandomUUID(Info.ContextID);
1074 Info.EncryptedEssence = true;
1076 if ( Options.key_id_flag )
1077 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1079 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1081 Context = new AESEncContext;
1082 result = Context->InitKey(Options.key_value);
1084 if ( ASDCP_SUCCESS(result) )
1085 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1087 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1089 Info.UsesHMAC = true;
1090 HMAC = new HMACContext;
1091 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1095 if ( ASDCP_SUCCESS(result) )
1096 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1099 if ( ASDCP_SUCCESS(result) )
1101 ui32_t duration = 0;
1102 result = Parser.Reset();
1104 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1106 if ( ! Options.do_repeat || duration == 1 )
1108 result = Parser.ReadFrame(FrameBuffer);
1110 if ( ASDCP_SUCCESS(result) )
1112 if ( Options.verbose_flag )
1113 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1115 if ( Options.encrypt_header_flag )
1116 FrameBuffer.PlaintextOffset(0);
1120 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1122 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1124 // The Writer class will forward the last block of ciphertext
1125 // to the encryption context for use as the IV for the next
1126 // frame. If you want to use non-sequitur IV values, un-comment
1127 // the following line of code.
1128 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1129 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1133 if ( result == RESULT_ENDOFFILE )
1137 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1138 result = Writer.Finalize();
1143 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1144 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1145 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1148 read_JP2K_file(CommandOptions& Options)
1150 AESDecContext* Context = 0;
1151 HMACContext* HMAC = 0;
1152 JP2K::MXFReader Reader;
1153 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1154 ui32_t frame_count = 0;
1156 Result_t result = Reader.OpenRead(Options.filenames[0]);
1158 if ( ASDCP_SUCCESS(result) )
1160 JP2K::PictureDescriptor PDesc;
1161 Reader.FillPictureDescriptor(PDesc);
1163 frame_count = PDesc.ContainerDuration;
1165 if ( Options.verbose_flag )
1167 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1168 JP2K::PictureDescriptorDump(PDesc);
1172 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1174 Context = new AESDecContext;
1175 result = Context->InitKey(Options.key_value);
1177 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1180 Reader.FillWriterInfo(Info);
1182 if ( Info.UsesHMAC )
1184 HMAC = new HMACContext;
1185 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1189 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1194 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1195 if ( last_frame > frame_count )
1196 last_frame = frame_count;
1198 char name_format[64];
1199 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1201 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1203 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1205 if ( ASDCP_SUCCESS(result) )
1207 Kumu::FileWriter OutFile;
1210 snprintf(filename, 256, name_format, Options.file_root, i);
1211 result = OutFile.OpenWrite(filename);
1213 if ( ASDCP_SUCCESS(result) )
1214 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1216 if ( Options.verbose_flag )
1217 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1224 //------------------------------------------------------------------------------------------
1228 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1229 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1232 write_PCM_file(CommandOptions& Options)
1234 AESEncContext* Context = 0;
1235 HMACContext* HMAC = 0;
1236 PCMParserList Parser;
1237 PCM::MXFWriter Writer;
1238 PCM::FrameBuffer FrameBuffer;
1239 PCM::AudioDescriptor ADesc;
1240 Rational PictureRate = Options.PictureRate();
1241 byte_t IV_buf[CBC_BLOCK_SIZE];
1242 Kumu::FortunaRNG RNG;
1244 // set up essence parser
1245 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1247 // set up MXF writer
1248 if ( ASDCP_SUCCESS(result) )
1250 Parser.FillAudioDescriptor(ADesc);
1252 ADesc.EditRate = PictureRate;
1253 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1254 ADesc.ChannelFormat = Options.channel_fmt;
1256 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1258 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1261 if ( Options.verbose_flag )
1263 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1264 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1265 Options.szPictureRate(),
1266 PCM::CalcSamplesPerFrame(ADesc));
1267 fputs("AudioDescriptor:\n", stderr);
1268 PCM::AudioDescriptorDump(ADesc);
1272 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1274 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1275 if ( Options.asset_id_flag )
1276 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1278 Kumu::GenRandomUUID(Info.AssetUUID);
1280 if ( Options.use_smpte_labels )
1282 Info.LabelSetType = LS_MXF_SMPTE;
1283 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1286 // configure encryption
1287 if( Options.key_flag )
1289 Kumu::GenRandomUUID(Info.ContextID);
1290 Info.EncryptedEssence = true;
1292 if ( Options.key_id_flag )
1293 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1295 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1297 Context = new AESEncContext;
1298 result = Context->InitKey(Options.key_value);
1300 if ( ASDCP_SUCCESS(result) )
1301 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1303 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1305 Info.UsesHMAC = true;
1306 HMAC = new HMACContext;
1307 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1311 if ( ASDCP_SUCCESS(result) )
1312 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1315 if ( ASDCP_SUCCESS(result) )
1317 result = Parser.Reset();
1318 ui32_t duration = 0;
1320 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1322 result = Parser.ReadFrame(FrameBuffer);
1324 if ( ASDCP_SUCCESS(result) )
1326 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1328 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1329 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1330 result = RESULT_ENDOFFILE;
1334 if ( Options.verbose_flag )
1335 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1337 if ( ! Options.no_write_flag )
1339 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1341 // The Writer class will forward the last block of ciphertext
1342 // to the encryption context for use as the IV for the next
1343 // frame. If you want to use non-sequitur IV values, un-comment
1344 // the following line of code.
1345 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1346 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1351 if ( result == RESULT_ENDOFFILE )
1355 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1356 result = Writer.Finalize();
1361 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1362 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1363 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1366 read_PCM_file(CommandOptions& Options)
1368 AESDecContext* Context = 0;
1369 HMACContext* HMAC = 0;
1370 PCM::MXFReader Reader;
1371 PCM::FrameBuffer FrameBuffer;
1372 WavFileWriter OutWave;
1373 PCM::AudioDescriptor ADesc;
1374 ui32_t last_frame = 0;
1376 Result_t result = Reader.OpenRead(Options.filenames[0]);
1378 if ( ASDCP_SUCCESS(result) )
1380 Reader.FillAudioDescriptor(ADesc);
1382 if ( ADesc.EditRate != EditRate_23_98
1383 && ADesc.EditRate != EditRate_24
1384 && ADesc.EditRate != EditRate_25
1385 && ADesc.EditRate != EditRate_30
1386 && ADesc.EditRate != EditRate_48
1387 && ADesc.EditRate != EditRate_50
1388 && ADesc.EditRate != EditRate_60 )
1389 ADesc.EditRate = Options.PictureRate();
1391 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1393 if ( Options.verbose_flag )
1394 PCM::AudioDescriptorDump(ADesc);
1397 if ( ASDCP_SUCCESS(result) )
1399 last_frame = ADesc.ContainerDuration;
1401 if ( Options.duration > 0 && Options.duration < last_frame )
1402 last_frame = Options.duration;
1404 if ( Options.start_frame > 0 )
1406 if ( Options.start_frame > ADesc.ContainerDuration )
1408 fprintf(stderr, "Start value greater than file duration.\n");
1412 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1415 ADesc.ContainerDuration = last_frame - Options.start_frame;
1416 OutWave.OpenWrite(ADesc, Options.file_root,
1417 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1418 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1421 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1423 Context = new AESDecContext;
1424 result = Context->InitKey(Options.key_value);
1426 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1429 Reader.FillWriterInfo(Info);
1431 if ( Info.UsesHMAC )
1433 HMAC = new HMACContext;
1434 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1438 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1443 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1445 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1447 if ( ASDCP_SUCCESS(result) )
1449 if ( Options.verbose_flag )
1450 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1452 result = OutWave.WriteFrame(FrameBuffer);
1460 //------------------------------------------------------------------------------------------
1461 // TimedText essence
1464 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1465 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1468 write_timed_text_file(CommandOptions& Options)
1470 AESEncContext* Context = 0;
1471 HMACContext* HMAC = 0;
1472 TimedText::DCSubtitleParser Parser;
1473 TimedText::MXFWriter Writer;
1474 TimedText::FrameBuffer FrameBuffer;
1475 TimedText::TimedTextDescriptor TDesc;
1476 byte_t IV_buf[CBC_BLOCK_SIZE];
1477 Kumu::FortunaRNG RNG;
1479 // set up essence parser
1480 Result_t result = Parser.OpenRead(Options.filenames[0]);
1482 // set up MXF writer
1483 if ( ASDCP_SUCCESS(result) )
1485 Parser.FillTimedTextDescriptor(TDesc);
1486 FrameBuffer.Capacity(Options.fb_size);
1488 if ( Options.verbose_flag )
1490 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1491 TimedText::DescriptorDump(TDesc);
1495 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1497 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1498 if ( Options.asset_id_flag )
1499 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1501 Kumu::GenRandomUUID(Info.AssetUUID);
1503 if ( Options.use_smpte_labels )
1505 Info.LabelSetType = LS_MXF_SMPTE;
1506 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1509 // configure encryption
1510 if( Options.key_flag )
1512 Kumu::GenRandomUUID(Info.ContextID);
1513 Info.EncryptedEssence = true;
1515 if ( Options.key_id_flag )
1516 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1518 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1520 Context = new AESEncContext;
1521 result = Context->InitKey(Options.key_value);
1523 if ( ASDCP_SUCCESS(result) )
1524 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1526 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1528 Info.UsesHMAC = true;
1529 HMAC = new HMACContext;
1530 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1534 if ( ASDCP_SUCCESS(result) )
1535 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1538 if ( ASDCP_FAILURE(result) )
1542 TimedText::ResourceList_t::const_iterator ri;
1544 result = Parser.ReadTimedTextResource(XMLDoc);
1546 if ( ASDCP_SUCCESS(result) )
1547 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1549 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1551 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1553 if ( ASDCP_SUCCESS(result) )
1555 if ( Options.verbose_flag )
1556 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1558 if ( ! Options.no_write_flag )
1560 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1562 // The Writer class will forward the last block of ciphertext
1563 // to the encryption context for use as the IV for the next
1564 // frame. If you want to use non-sequitur IV values, un-comment
1565 // the following line of code.
1566 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1567 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1571 if ( result == RESULT_ENDOFFILE )
1575 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1576 result = Writer.Finalize();
1582 // Read one or more timed text streams from a plaintext ASDCP file
1583 // Read one or more timed text streams from a ciphertext ASDCP file
1584 // Read one or more timed text streams from a ciphertext ASDCP file
1587 read_timed_text_file(CommandOptions& Options)
1589 AESDecContext* Context = 0;
1590 HMACContext* HMAC = 0;
1591 TimedText::MXFReader Reader;
1592 TimedText::FrameBuffer FrameBuffer;
1593 TimedText::TimedTextDescriptor TDesc;
1595 Result_t result = Reader.OpenRead(Options.filenames[0]);
1597 if ( ASDCP_SUCCESS(result) )
1599 Reader.FillTimedTextDescriptor(TDesc);
1600 FrameBuffer.Capacity(Options.fb_size);
1602 if ( Options.verbose_flag )
1603 TimedText::DescriptorDump(TDesc);
1606 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1608 Context = new AESDecContext;
1609 result = Context->InitKey(Options.key_value);
1611 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1614 Reader.FillWriterInfo(Info);
1616 if ( Info.UsesHMAC )
1618 HMAC = new HMACContext;
1619 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1623 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1628 if ( ASDCP_FAILURE(result) )
1632 std::string out_path = Kumu::PathDirname(Options.file_root);
1635 TimedText::ResourceList_t::const_iterator ri;
1637 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1639 if ( ASDCP_SUCCESS(result) )
1641 Kumu::FileWriter Writer;
1642 result = Writer.OpenWrite(Options.file_root);
1644 if ( ASDCP_SUCCESS(result) )
1645 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1648 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1650 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1652 if ( ASDCP_SUCCESS(result) )
1654 Kumu::FileWriter Writer;
1655 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1657 if ( ASDCP_SUCCESS(result) )
1659 if ( Options.verbose_flag )
1660 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1662 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1670 //------------------------------------------------------------------------------------------
1674 // These classes wrap the irregular names in the asdcplib API
1675 // so that I can use a template to simplify the implementation
1676 // of show_file_info()
1678 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1681 void FillDescriptor(MPEG2::MXFReader& Reader) {
1682 Reader.FillVideoDescriptor(*this);
1685 void Dump(FILE* stream) {
1686 MPEG2::VideoDescriptorDump(*this, stream);
1690 class MyPictureDescriptor : public JP2K::PictureDescriptor
1693 void FillDescriptor(JP2K::MXFReader& Reader) {
1694 Reader.FillPictureDescriptor(*this);
1697 void Dump(FILE* stream) {
1698 JP2K::PictureDescriptorDump(*this, stream);
1702 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1705 void FillDescriptor(JP2K::MXFSReader& Reader) {
1706 Reader.FillPictureDescriptor(*this);
1709 void Dump(FILE* stream) {
1710 JP2K::PictureDescriptorDump(*this, stream);
1714 class MyAudioDescriptor : public PCM::AudioDescriptor
1717 void FillDescriptor(PCM::MXFReader& Reader) {
1718 Reader.FillAudioDescriptor(*this);
1721 void Dump(FILE* stream) {
1722 PCM::AudioDescriptorDump(*this, stream);
1726 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1729 void FillDescriptor(TimedText::MXFReader& Reader) {
1730 Reader.FillTimedTextDescriptor(*this);
1733 void Dump(FILE* stream) {
1734 TimedText::DescriptorDump(*this, stream);
1738 // MSVC didn't like the function template, so now it's a static class method
1739 template<class ReaderT, class DescriptorT>
1740 class FileInfoWrapper
1744 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1746 assert(type_string);
1750 Result_t result = RESULT_OK;
1752 if ( Options.verbose_flag || Options.showheader_flag )
1755 result = Reader.OpenRead(Options.filenames[0]);
1757 if ( ASDCP_SUCCESS(result) )
1759 fprintf(stdout, "File essence type is %s.\n", type_string);
1761 if ( Options.showheader_flag )
1762 Reader.DumpHeaderMetadata(stream);
1765 Reader.FillWriterInfo(WI);
1766 WriterInfoDump(WI, stream);
1769 Desc.FillDescriptor(Reader);
1772 if ( Options.showindex_flag )
1773 Reader.DumpIndex(stream);
1775 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1777 Reader.DumpHeaderMetadata(stream);
1785 // Read header metadata from an ASDCP file
1788 show_file_info(CommandOptions& Options)
1790 EssenceType_t EssenceType;
1791 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1793 if ( ASDCP_FAILURE(result) )
1796 if ( EssenceType == ESS_MPEG2_VES )
1798 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1800 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1802 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1804 if ( ASDCP_SUCCESS(result) )
1806 const Dictionary* Dict = &DefaultCompositeDict();
1807 PCM::MXFReader Reader;
1808 MXF::OP1aHeader Header(Dict);
1809 MXF::WaveAudioDescriptor *descriptor = 0;
1811 result = Reader.OpenRead(Options.filenames[0]);
1813 if ( ASDCP_SUCCESS(result) )
1814 result = Reader.OP1aHeader().GetMDObjectByType(Dict->ul(MDD_WaveAudioDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
1816 if ( ASDCP_SUCCESS(result) )
1819 fprintf(stdout, " ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
1823 else if ( EssenceType == ESS_JPEG_2000 )
1825 if ( Options.stereo_image_flag )
1827 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1828 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1832 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1833 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1836 else if ( EssenceType == ESS_JPEG_2000_S )
1838 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1839 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1841 else if ( EssenceType == ESS_TIMED_TEXT )
1843 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1847 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1848 Kumu::FileReader Reader;
1849 const Dictionary* Dict = &DefaultCompositeDict();
1850 MXF::OP1aHeader TestHeader(Dict);
1852 result = Reader.OpenRead(Options.filenames[0]);
1854 if ( ASDCP_SUCCESS(result) )
1855 result = TestHeader.InitFromFile(Reader); // test UL and OP
1857 if ( ASDCP_SUCCESS(result) )
1859 TestHeader.Partition::Dump(stdout);
1861 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1864 fputs("File contains no Identification object.\n", stdout);
1866 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1869 fputs("File contains no SourcePackage object.\n", stdout);
1873 fputs("File is not MXF.\n", stdout);
1883 digest_file(const char* filename)
1885 using namespace Kumu;
1887 ASDCP_TEST_NULL_STR(filename);
1891 ByteString Buf(8192);
1893 Result_t result = Reader.OpenRead(filename);
1895 while ( ASDCP_SUCCESS(result) )
1897 ui32_t read_count = 0;
1898 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1900 if ( result == RESULT_ENDOFFILE )
1906 if ( ASDCP_SUCCESS(result) )
1907 SHA1_Update(&Ctx, Buf.Data(), read_count);
1910 if ( ASDCP_SUCCESS(result) )
1912 const ui32_t sha_len = 20;
1913 byte_t bin_buf[sha_len];
1915 SHA1_Final(bin_buf, &Ctx);
1917 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1925 main(int argc, const char** argv)
1927 Result_t result = RESULT_OK;
1929 CommandOptions Options(argc, argv);
1931 if ( Options.version_flag )
1934 if ( Options.help_flag )
1937 if ( Options.version_flag || Options.help_flag )
1940 if ( Options.error_flag )
1942 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1946 if ( Options.mode == MMT_INFO )
1948 result = show_file_info(Options);
1950 for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1952 Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1953 result = show_file_info(Options);
1956 else if ( Options.mode == MMT_GOP_START )
1958 result = gop_start_test(Options);
1960 else if ( Options.mode == MMT_GEN_KEY )
1962 Kumu::FortunaRNG RNG;
1963 byte_t bin_buf[KeyLen];
1965 RNG.FillRandom(bin_buf, KeyLen);
1966 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1968 else if ( Options.mode == MMT_GEN_ID )
1971 Kumu::GenRandomValue(TmpID);
1972 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1974 else if ( Options.mode == MMT_DIGEST )
1976 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1977 result = digest_file(Options.filenames[i]);
1979 else if ( Options.mode == MMT_UL_LIST )
1981 if ( Options.use_smpte_labels )
1982 DefaultSMPTEDict().Dump(stdout);
1984 DefaultInteropDict().Dump(stdout);
1986 else if ( Options.mode == MMT_EXTRACT )
1988 EssenceType_t EssenceType;
1989 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1991 if ( ASDCP_SUCCESS(result) )
1993 switch ( EssenceType )
1996 result = read_MPEG2_file(Options);
2000 if ( Options.stereo_image_flag )
2001 result = read_JP2K_S_file(Options);
2003 result = read_JP2K_file(Options);
2006 case ESS_JPEG_2000_S:
2007 result = read_JP2K_S_file(Options);
2010 case ESS_PCM_24b_48k:
2011 case ESS_PCM_24b_96k:
2012 result = read_PCM_file(Options);
2015 case ESS_TIMED_TEXT:
2016 result = read_timed_text_file(Options);
2020 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
2025 else if ( Options.mode == MMT_CREATE )
2027 if ( Options.do_repeat && ! Options.duration_flag )
2029 fputs("Option -R requires -d <duration>\n", stderr);
2033 EssenceType_t EssenceType;
2034 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
2036 if ( ASDCP_SUCCESS(result) )
2038 switch ( EssenceType )
2041 result = write_MPEG2_file(Options);
2045 if ( Options.stereo_image_flag )
2046 result = write_JP2K_S_file(Options);
2049 result = write_JP2K_file(Options);
2053 case ESS_PCM_24b_48k:
2054 case ESS_PCM_24b_96k:
2055 result = write_PCM_file(Options);
2058 case ESS_TIMED_TEXT:
2059 result = write_timed_text_file(Options);
2063 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2064 Options.filenames[0]);
2071 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2075 if ( ASDCP_FAILURE(result) )
2077 fputs("Program stopped on error.\n", stderr);
2079 if ( result == RESULT_SFORMAT )
2081 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2083 else if ( result != RESULT_FAIL )
2085 fputs(result, stderr);
2086 fputc('\n', stderr);
2097 // end asdcp-test.cpp