2 Copyright (c) 2003-2010, 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-2010 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\
136 \n", PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
140 -3 - With -c, create a stereoscopic image file. Expects two\n\
141 directories of JP2K codestreams (directories must have\n\
142 an equal number of frames; left eye is first).\n\
143 - With -x, force stereoscopic interpretation of a JP2K\n\
145 -c <output-file> - Create an AS-DCP track file from input(s)\n\
146 -g - Generate a random 16 byte value to stdout\n\
147 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
148 -h | -help - Show help\n\
149 -i - Show file info\n\
150 -t - Calculate message digest of input file\n\
151 -U - Dump UL catalog to stdout\n\
152 -u - Generate a random UUID value to stdout\n\
153 -V - Show version information\n\
154 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
159 -e - Encrypt MPEG or JP2K headers (default)\n\
160 -E - Do not encrypt MPEG or JP2K headers\n\
161 -j <key-id-str> - Write key ID instead of creating a random value\n\
162 -k <key-string> - Use key for ciphertext operations\n\
163 -m - verify HMAC values when reading\n\
164 -M - Do not create HMAC values when writing\n\
168 Read/Write Options:\n\
169 -a <UUID> - Specify the Asset ID of a file (with -c)\n\
170 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
171 Defaults to 4,194,304 (4MB)\n\
172 -d <duration> - Number of frames to process, default all\n\
173 -f <start-frame> - Starting frame number, default 0\n\
174 -l <label> - Use given channel format label when writing MXF sound\n\
175 files. SMPTE 429-2 labels: '5.1', '6.1', '7.1'. Default\n\
176 is no label (valid for Interop only).\n\
177 -L - Write SMPTE UL values instead of MXF Interop\n\
178 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
179 Use one of [23|24|48], 24 is default\n\
180 -R - Repeat the first frame over the entire file (picture\n\
181 essence only, requires -c, -d)\n\
182 -S - Split Wave essence to stereo WAV files during extract.\n\
183 Default is multichannel WAV\n\
184 -1 - Split Wave essence to mono WAV files during extract.\n\
185 Default is multichannel WAV\n\
186 -W - Read input file only, do not write source file\n\
187 -w <width> - Width of numeric element in a series of frame file names\n\
188 (use with -x, default 6).\n\
189 -z - Fail if j2c inputs have unequal parameters (default)\n\
190 -Z - Ignore unequal parameters in j2c inputs\n\
195 -H - Show MXF header metadata, used with option -i\n\
196 -n - Show index, used with option -i\n\
199 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
200 stderr, used with option -v\n\
201 -v - Verbose, prints informative messages to stderr\n\
203 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
204 o All option arguments must be separated from the option by whitespace.\n\
205 o An argument of \"23\" to the -p option will be interpreted\n\
206 as 24000/1001 fps.\n\
226 decode_channel_fmt(const std::string& label_name)
228 if ( label_name == "5.1" )
229 return PCM::CF_CFG_1;
231 else if ( label_name == "6.1" )
232 return PCM::CF_CFG_2;
234 else if ( label_name == "7.1" )
235 return PCM::CF_CFG_3;
237 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
238 fprintf(stderr, "Expecting '5.1', '6.1', or '7.1'\n");
250 bool error_flag; // true if the given options are in error or not complete
251 bool key_flag; // true if an encryption key was given
252 bool key_id_flag; // true if a key ID was given
253 bool asset_id_flag; // true if an asset ID was given
254 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
255 bool write_hmac; // true if HMAC values are to be generated and written
256 bool read_hmac; // true if HMAC values are to be validated
257 bool split_wav; // true if PCM is to be extracted to stereo WAV files
258 bool mono_wav; // true if PCM is to be extracted to mono WAV files
259 bool verbose_flag; // true if the verbose option was selected
260 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
261 bool showindex_flag; // true if index is to be displayed
262 bool showheader_flag; // true if MXF file header is to be displayed
263 bool no_write_flag; // true if no output files are to be written
264 bool version_flag; // true if the version display option was selected
265 bool help_flag; // true if the help display option was selected
266 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
267 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
268 ui32_t start_frame; // frame number to begin processing
269 ui32_t duration; // number of frames to be processed
270 bool duration_flag; // true if duration argument given
271 bool do_repeat; // if true and -c -d, repeat first input frame
272 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
273 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
274 ui32_t picture_rate; // fps of picture when wrapping PCM
275 ui32_t fb_size; // size of picture frame buffer
276 ui32_t file_count; // number of elements in filenames[]
277 const char* file_root; // filename pre for files written by the extract mode
278 const char* out_file; // name of mxf file created by create mode
279 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
280 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
281 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
282 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
283 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
286 Rational PictureRate()
288 if ( picture_rate == 23 ) return EditRate_23_98;
289 if ( picture_rate == 25 ) return EditRate_25;
290 if ( picture_rate == 30 ) return EditRate_30;
291 if ( picture_rate == 48 ) return EditRate_48;
292 if ( picture_rate == 50 ) return EditRate_50;
293 if ( picture_rate == 60 ) return EditRate_60;
298 const char* szPictureRate()
300 if ( picture_rate == 23 ) return "23.976";
301 if ( picture_rate == 25 ) return "25";
302 if ( picture_rate == 30 ) return "30";
303 if ( picture_rate == 48 ) return "48";
304 if ( picture_rate == 50 ) return "50";
305 if ( picture_rate == 60 ) return "60";
310 CommandOptions(int argc, const char** argv) :
311 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
312 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
313 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
314 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
315 number_width(6), start_frame(0),
316 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
317 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
318 channel_fmt(PCM::CF_NONE)
320 memset(key_value, 0, KeyLen);
321 memset(key_id_value, 0, UUIDlen);
323 for ( int i = 1; i < argc; i++ )
326 if ( (strcmp( argv[i], "-help") == 0) )
332 if ( argv[i][0] == '-'
333 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
336 switch ( argv[i][1] )
338 case '1': mono_wav = true; break;
339 case '2': split_wav = true; break;
340 case '3': stereo_image_flag = true; break;
343 asset_id_flag = true;
344 TEST_EXTRA_ARG(i, 'a');
347 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
349 if ( length != UUIDlen )
351 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
358 TEST_EXTRA_ARG(i, 'b');
359 fb_size = abs(atoi(argv[i]));
362 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
367 TEST_EXTRA_ARG(i, 'c');
373 TEST_EXTRA_ARG(i, 'd');
374 duration_flag = true;
375 duration = abs(atoi(argv[i]));
378 case 'E': encrypt_header_flag = false; break;
379 case 'e': encrypt_header_flag = true; break;
382 TEST_EXTRA_ARG(i, 'f');
383 start_frame = abs(atoi(argv[i]));
386 case 'G': mode = MMT_GOP_START; break;
387 case 'g': mode = MMT_GEN_KEY; break;
388 case 'H': showheader_flag = true; break;
389 case 'h': help_flag = true; break;
390 case 'i': mode = MMT_INFO; break;
392 case 'j': key_id_flag = true;
393 TEST_EXTRA_ARG(i, 'j');
396 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
398 if ( length != UUIDlen )
400 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
406 case 'k': key_flag = true;
407 TEST_EXTRA_ARG(i, 'k');
410 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
412 if ( length != KeyLen )
414 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
421 TEST_EXTRA_ARG(i, 'l');
422 channel_fmt = decode_channel_fmt(argv[i]);
425 case 'L': use_smpte_labels = true; break;
426 case 'M': write_hmac = false; break;
427 case 'm': read_hmac = true; break;
428 case 'n': showindex_flag = true; break;
431 TEST_EXTRA_ARG(i, 'p');
432 picture_rate = abs(atoi(argv[i]));
435 case 'R': do_repeat = true; break;
436 case 'S': split_wav = true; break;
439 TEST_EXTRA_ARG(i, 's');
440 fb_dump_size = abs(atoi(argv[i]));
443 case 't': mode = MMT_DIGEST; break;
444 case 'U': mode = MMT_UL_LIST; break;
445 case 'u': mode = MMT_GEN_ID; break;
446 case 'V': version_flag = true; break;
447 case 'v': verbose_flag = true; break;
448 case 'W': no_write_flag = true; break;
451 TEST_EXTRA_ARG(i, 'w');
452 number_width = abs(atoi(argv[i]));
456 TEST_EXTRA_ARG(i, 'x');
461 case 'Z': j2c_pedantic = false; break;
462 case 'z': j2c_pedantic = true; break;
465 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
472 if ( argv[i][0] != '-' )
474 filenames[file_count++] = argv[i];
478 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
482 if ( file_count >= MAX_IN_FILES )
484 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
490 if ( help_flag || version_flag )
493 if ( ( mode == MMT_INFO
494 || mode == MMT_CREATE
495 || mode == MMT_EXTRACT
496 || mode == MMT_GOP_START
497 || mode == MMT_DIGEST ) && file_count == 0 )
499 fputs("Option requires at least one filename argument.\n", stderr);
503 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
505 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
513 //------------------------------------------------------------------------------------------
516 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
517 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
520 write_MPEG2_file(CommandOptions& Options)
522 AESEncContext* Context = 0;
523 HMACContext* HMAC = 0;
524 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
525 MPEG2::Parser Parser;
526 MPEG2::MXFWriter Writer;
527 MPEG2::VideoDescriptor VDesc;
528 byte_t IV_buf[CBC_BLOCK_SIZE];
529 Kumu::FortunaRNG RNG;
531 // set up essence parser
532 Result_t result = Parser.OpenRead(Options.filenames[0]);
535 if ( ASDCP_SUCCESS(result) )
537 Parser.FillVideoDescriptor(VDesc);
539 if ( Options.verbose_flag )
541 fputs("MPEG-2 Pictures\n", stderr);
542 fputs("VideoDescriptor:\n", stderr);
543 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
544 MPEG2::VideoDescriptorDump(VDesc);
548 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
550 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
551 if ( Options.asset_id_flag )
552 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
554 Kumu::GenRandomUUID(Info.AssetUUID);
556 if ( Options.use_smpte_labels )
558 Info.LabelSetType = LS_MXF_SMPTE;
559 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
562 // configure encryption
563 if( Options.key_flag )
565 Kumu::GenRandomUUID(Info.ContextID);
566 Info.EncryptedEssence = true;
568 if ( Options.key_id_flag )
569 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
571 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
573 Context = new AESEncContext;
574 result = Context->InitKey(Options.key_value);
576 if ( ASDCP_SUCCESS(result) )
577 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
579 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
581 Info.UsesHMAC = true;
582 HMAC = new HMACContext;
583 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
587 if ( ASDCP_SUCCESS(result) )
588 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
591 if ( ASDCP_SUCCESS(result) )
592 // loop through the frames
594 result = Parser.Reset();
597 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
599 if ( ! Options.do_repeat || duration == 1 )
601 result = Parser.ReadFrame(FrameBuffer);
603 if ( ASDCP_SUCCESS(result) )
605 if ( Options.verbose_flag )
606 FrameBuffer.Dump(stderr, Options.fb_dump_size);
608 if ( Options.encrypt_header_flag )
609 FrameBuffer.PlaintextOffset(0);
613 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
615 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
617 // The Writer class will forward the last block of ciphertext
618 // to the encryption context for use as the IV for the next
619 // frame. If you want to use non-sequitur IV values, un-comment
620 // the following line of code.
621 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
622 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
626 if ( result == RESULT_ENDOFFILE )
630 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
631 result = Writer.Finalize();
636 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
637 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
638 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
641 read_MPEG2_file(CommandOptions& Options)
643 AESDecContext* Context = 0;
644 HMACContext* HMAC = 0;
645 MPEG2::MXFReader Reader;
646 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
647 Kumu::FileWriter OutFile;
648 ui32_t frame_count = 0;
650 Result_t result = Reader.OpenRead(Options.filenames[0]);
652 if ( ASDCP_SUCCESS(result) )
654 MPEG2::VideoDescriptor VDesc;
655 Reader.FillVideoDescriptor(VDesc);
656 frame_count = VDesc.ContainerDuration;
658 if ( Options.verbose_flag )
660 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
661 MPEG2::VideoDescriptorDump(VDesc);
665 if ( ASDCP_SUCCESS(result) )
668 snprintf(filename, 256, "%s.ves", Options.file_root);
669 result = OutFile.OpenWrite(filename);
672 if ( ASDCP_SUCCESS(result) && Options.key_flag )
674 Context = new AESDecContext;
675 result = Context->InitKey(Options.key_value);
677 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
680 Reader.FillWriterInfo(Info);
684 HMAC = new HMACContext;
685 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
689 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
694 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
695 if ( last_frame > frame_count )
696 last_frame = frame_count;
698 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
700 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
702 if ( ASDCP_SUCCESS(result) )
704 if ( Options.verbose_flag )
705 FrameBuffer.Dump(stderr, Options.fb_dump_size);
707 ui32_t write_count = 0;
708 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
718 gop_start_test(CommandOptions& Options)
720 using namespace ASDCP::MPEG2;
723 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
724 ui32_t frame_count = 0;
726 Result_t result = Reader.OpenRead(Options.filenames[0]);
728 if ( ASDCP_SUCCESS(result) )
730 MPEG2::VideoDescriptor VDesc;
731 Reader.FillVideoDescriptor(VDesc);
732 frame_count = VDesc.ContainerDuration;
734 if ( Options.verbose_flag )
736 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
737 MPEG2::VideoDescriptorDump(VDesc);
741 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
742 if ( last_frame > frame_count )
743 last_frame = frame_count;
745 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
747 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
749 if ( ASDCP_SUCCESS(result) )
751 if ( Options.verbose_flag )
752 FrameBuffer.Dump(stderr, Options.fb_dump_size);
754 if ( FrameBuffer.FrameType() != FRAME_I )
755 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
757 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
764 //------------------------------------------------------------------------------------------
767 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
768 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
771 write_JP2K_S_file(CommandOptions& Options)
773 AESEncContext* Context = 0;
774 HMACContext* HMAC = 0;
775 JP2K::MXFSWriter Writer;
776 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
777 JP2K::PictureDescriptor PDesc;
778 JP2K::SequenceParser ParserLeft, ParserRight;
779 byte_t IV_buf[CBC_BLOCK_SIZE];
780 Kumu::FortunaRNG RNG;
782 if ( Options.file_count != 2 )
784 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
788 // set up essence parser
789 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
791 if ( ASDCP_SUCCESS(result) )
792 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
795 if ( ASDCP_SUCCESS(result) )
797 ParserLeft.FillPictureDescriptor(PDesc);
798 PDesc.EditRate = Options.PictureRate();
800 if ( Options.verbose_flag )
802 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
803 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
804 JP2K::PictureDescriptorDump(PDesc);
808 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
810 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
811 if ( Options.asset_id_flag )
812 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
814 Kumu::GenRandomUUID(Info.AssetUUID);
816 if ( Options.use_smpte_labels )
818 Info.LabelSetType = LS_MXF_SMPTE;
819 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
822 // configure encryption
823 if( Options.key_flag )
825 Kumu::GenRandomUUID(Info.ContextID);
826 Info.EncryptedEssence = true;
828 if ( Options.key_id_flag )
829 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
831 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
833 Context = new AESEncContext;
834 result = Context->InitKey(Options.key_value);
836 if ( ASDCP_SUCCESS(result) )
837 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
839 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
841 Info.UsesHMAC = true;
842 HMAC = new HMACContext;
843 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
847 if ( ASDCP_SUCCESS(result) )
848 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
851 if ( ASDCP_SUCCESS(result) )
854 result = ParserLeft.Reset();
855 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
857 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
859 result = ParserLeft.ReadFrame(FrameBuffer);
861 if ( ASDCP_SUCCESS(result) )
863 if ( Options.verbose_flag )
864 FrameBuffer.Dump(stderr, Options.fb_dump_size);
866 if ( Options.encrypt_header_flag )
867 FrameBuffer.PlaintextOffset(0);
870 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
871 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
873 if ( ASDCP_SUCCESS(result) )
874 result = ParserRight.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_RIGHT, Context, HMAC);
889 if ( result == RESULT_ENDOFFILE )
893 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
894 result = Writer.Finalize();
899 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
900 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
901 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
903 read_JP2K_S_file(CommandOptions& Options)
905 AESDecContext* Context = 0;
906 HMACContext* HMAC = 0;
907 JP2K::MXFSReader Reader;
908 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
909 ui32_t frame_count = 0;
911 Result_t result = Reader.OpenRead(Options.filenames[0]);
913 if ( ASDCP_SUCCESS(result) )
915 JP2K::PictureDescriptor PDesc;
916 Reader.FillPictureDescriptor(PDesc);
918 frame_count = PDesc.ContainerDuration;
920 if ( Options.verbose_flag )
922 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
923 JP2K::PictureDescriptorDump(PDesc);
927 if ( ASDCP_SUCCESS(result) && Options.key_flag )
929 Context = new AESDecContext;
930 result = Context->InitKey(Options.key_value);
932 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
935 Reader.FillWriterInfo(Info);
939 HMAC = new HMACContext;
940 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
944 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
949 const int filename_max = 1024;
950 char filename[filename_max];
951 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
952 if ( last_frame > frame_count )
953 last_frame = frame_count;
955 char left_format[64]; char right_format[64];
956 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
957 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
959 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
961 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
963 if ( ASDCP_SUCCESS(result) )
965 Kumu::FileWriter OutFile;
967 snprintf(filename, filename_max, left_format, Options.file_root, i);
968 result = OutFile.OpenWrite(filename);
970 if ( ASDCP_SUCCESS(result) )
971 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
973 if ( Options.verbose_flag )
974 FrameBuffer.Dump(stderr, Options.fb_dump_size);
977 if ( ASDCP_SUCCESS(result) )
978 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
980 if ( ASDCP_SUCCESS(result) )
982 Kumu::FileWriter OutFile;
984 snprintf(filename, filename_max, right_format, Options.file_root, i);
985 result = OutFile.OpenWrite(filename);
987 if ( ASDCP_SUCCESS(result) )
988 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
997 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
998 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1001 write_JP2K_file(CommandOptions& Options)
1003 AESEncContext* Context = 0;
1004 HMACContext* HMAC = 0;
1005 JP2K::MXFWriter Writer;
1006 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1007 JP2K::PictureDescriptor PDesc;
1008 JP2K::SequenceParser Parser;
1009 byte_t IV_buf[CBC_BLOCK_SIZE];
1010 Kumu::FortunaRNG RNG;
1012 // set up essence parser
1013 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1015 // set up MXF writer
1016 if ( ASDCP_SUCCESS(result) )
1018 Parser.FillPictureDescriptor(PDesc);
1019 PDesc.EditRate = Options.PictureRate();
1021 if ( Options.verbose_flag )
1023 fprintf(stderr, "JPEG 2000 pictures\n");
1024 fputs("PictureDescriptor:\n", stderr);
1025 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1026 JP2K::PictureDescriptorDump(PDesc);
1030 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1032 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1033 if ( Options.asset_id_flag )
1034 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1036 Kumu::GenRandomUUID(Info.AssetUUID);
1038 if ( Options.use_smpte_labels )
1040 Info.LabelSetType = LS_MXF_SMPTE;
1041 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1044 // configure encryption
1045 if( Options.key_flag )
1047 Kumu::GenRandomUUID(Info.ContextID);
1048 Info.EncryptedEssence = true;
1050 if ( Options.key_id_flag )
1051 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1053 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1055 Context = new AESEncContext;
1056 result = Context->InitKey(Options.key_value);
1058 if ( ASDCP_SUCCESS(result) )
1059 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1061 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1063 Info.UsesHMAC = true;
1064 HMAC = new HMACContext;
1065 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1069 if ( ASDCP_SUCCESS(result) )
1070 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1073 if ( ASDCP_SUCCESS(result) )
1075 ui32_t duration = 0;
1076 result = Parser.Reset();
1078 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1080 if ( ! Options.do_repeat || duration == 1 )
1082 result = Parser.ReadFrame(FrameBuffer);
1084 if ( ASDCP_SUCCESS(result) )
1086 if ( Options.verbose_flag )
1087 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1089 if ( Options.encrypt_header_flag )
1090 FrameBuffer.PlaintextOffset(0);
1094 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1096 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1098 // The Writer class will forward the last block of ciphertext
1099 // to the encryption context for use as the IV for the next
1100 // frame. If you want to use non-sequitur IV values, un-comment
1101 // the following line of code.
1102 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1103 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1107 if ( result == RESULT_ENDOFFILE )
1111 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1112 result = Writer.Finalize();
1117 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1118 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1119 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1122 read_JP2K_file(CommandOptions& Options)
1124 AESDecContext* Context = 0;
1125 HMACContext* HMAC = 0;
1126 JP2K::MXFReader Reader;
1127 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1128 ui32_t frame_count = 0;
1130 Result_t result = Reader.OpenRead(Options.filenames[0]);
1132 if ( ASDCP_SUCCESS(result) )
1134 JP2K::PictureDescriptor PDesc;
1135 Reader.FillPictureDescriptor(PDesc);
1137 frame_count = PDesc.ContainerDuration;
1139 if ( Options.verbose_flag )
1141 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1142 JP2K::PictureDescriptorDump(PDesc);
1146 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1148 Context = new AESDecContext;
1149 result = Context->InitKey(Options.key_value);
1151 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1154 Reader.FillWriterInfo(Info);
1156 if ( Info.UsesHMAC )
1158 HMAC = new HMACContext;
1159 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1163 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1168 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1169 if ( last_frame > frame_count )
1170 last_frame = frame_count;
1172 char name_format[64];
1173 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1175 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1177 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1179 if ( ASDCP_SUCCESS(result) )
1181 Kumu::FileWriter OutFile;
1184 snprintf(filename, 256, name_format, Options.file_root, i);
1185 result = OutFile.OpenWrite(filename);
1187 if ( ASDCP_SUCCESS(result) )
1188 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1190 if ( Options.verbose_flag )
1191 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1198 //------------------------------------------------------------------------------------------
1202 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1203 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1206 write_PCM_file(CommandOptions& Options)
1208 AESEncContext* Context = 0;
1209 HMACContext* HMAC = 0;
1210 PCMParserList Parser;
1211 PCM::MXFWriter Writer;
1212 PCM::FrameBuffer FrameBuffer;
1213 PCM::AudioDescriptor ADesc;
1214 Rational PictureRate = Options.PictureRate();
1215 byte_t IV_buf[CBC_BLOCK_SIZE];
1216 Kumu::FortunaRNG RNG;
1218 // set up essence parser
1219 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1221 // set up MXF writer
1222 if ( ASDCP_SUCCESS(result) )
1224 Parser.FillAudioDescriptor(ADesc);
1226 ADesc.EditRate = PictureRate;
1227 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1228 ADesc.ChannelFormat = Options.channel_fmt;
1230 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1232 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1235 if ( Options.verbose_flag )
1237 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1238 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1239 Options.szPictureRate(),
1240 PCM::CalcSamplesPerFrame(ADesc));
1241 fputs("AudioDescriptor:\n", stderr);
1242 PCM::AudioDescriptorDump(ADesc);
1246 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1248 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1249 if ( Options.asset_id_flag )
1250 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1252 Kumu::GenRandomUUID(Info.AssetUUID);
1254 if ( Options.use_smpte_labels )
1256 Info.LabelSetType = LS_MXF_SMPTE;
1257 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1260 // configure encryption
1261 if( Options.key_flag )
1263 Kumu::GenRandomUUID(Info.ContextID);
1264 Info.EncryptedEssence = true;
1266 if ( Options.key_id_flag )
1267 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1269 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1271 Context = new AESEncContext;
1272 result = Context->InitKey(Options.key_value);
1274 if ( ASDCP_SUCCESS(result) )
1275 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1277 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1279 Info.UsesHMAC = true;
1280 HMAC = new HMACContext;
1281 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1285 if ( ASDCP_SUCCESS(result) )
1286 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1289 if ( ASDCP_SUCCESS(result) )
1291 result = Parser.Reset();
1292 ui32_t duration = 0;
1294 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1296 result = Parser.ReadFrame(FrameBuffer);
1298 if ( ASDCP_SUCCESS(result) )
1300 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1302 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1303 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1304 result = RESULT_ENDOFFILE;
1308 if ( Options.verbose_flag )
1309 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1311 if ( ! Options.no_write_flag )
1313 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1315 // The Writer class will forward the last block of ciphertext
1316 // to the encryption context for use as the IV for the next
1317 // frame. If you want to use non-sequitur IV values, un-comment
1318 // the following line of code.
1319 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1320 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1325 if ( result == RESULT_ENDOFFILE )
1329 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1330 result = Writer.Finalize();
1335 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1336 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1337 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1340 read_PCM_file(CommandOptions& Options)
1342 AESDecContext* Context = 0;
1343 HMACContext* HMAC = 0;
1344 PCM::MXFReader Reader;
1345 PCM::FrameBuffer FrameBuffer;
1346 WavFileWriter OutWave;
1347 PCM::AudioDescriptor ADesc;
1348 ui32_t last_frame = 0;
1350 Result_t result = Reader.OpenRead(Options.filenames[0]);
1352 if ( ASDCP_SUCCESS(result) )
1354 Reader.FillAudioDescriptor(ADesc);
1356 if ( ADesc.EditRate != EditRate_23_98
1357 && ADesc.EditRate != EditRate_24
1358 && ADesc.EditRate != EditRate_25
1359 && ADesc.EditRate != EditRate_48
1360 && ADesc.EditRate != EditRate_50
1361 && ADesc.EditRate != EditRate_60 )
1362 ADesc.EditRate = Options.PictureRate();
1364 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1366 if ( Options.verbose_flag )
1367 PCM::AudioDescriptorDump(ADesc);
1370 if ( ASDCP_SUCCESS(result) )
1372 last_frame = ADesc.ContainerDuration;
1374 if ( Options.duration > 0 && Options.duration < last_frame )
1375 last_frame = Options.duration;
1377 if ( Options.start_frame > 0 )
1379 if ( Options.start_frame > ADesc.ContainerDuration )
1381 fprintf(stderr, "Start value greater than file duration.\n");
1385 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1388 ADesc.ContainerDuration = last_frame - Options.start_frame;
1389 OutWave.OpenWrite(ADesc, Options.file_root,
1390 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1391 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1394 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1396 Context = new AESDecContext;
1397 result = Context->InitKey(Options.key_value);
1399 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1402 Reader.FillWriterInfo(Info);
1404 if ( Info.UsesHMAC )
1406 HMAC = new HMACContext;
1407 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1411 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1416 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1418 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1420 if ( ASDCP_SUCCESS(result) )
1422 if ( Options.verbose_flag )
1423 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1425 result = OutWave.WriteFrame(FrameBuffer);
1433 //------------------------------------------------------------------------------------------
1434 // TimedText essence
1437 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1438 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1441 write_timed_text_file(CommandOptions& Options)
1443 AESEncContext* Context = 0;
1444 HMACContext* HMAC = 0;
1445 TimedText::DCSubtitleParser Parser;
1446 TimedText::MXFWriter Writer;
1447 TimedText::FrameBuffer FrameBuffer;
1448 TimedText::TimedTextDescriptor TDesc;
1449 byte_t IV_buf[CBC_BLOCK_SIZE];
1450 Kumu::FortunaRNG RNG;
1452 // set up essence parser
1453 Result_t result = Parser.OpenRead(Options.filenames[0]);
1455 // set up MXF writer
1456 if ( ASDCP_SUCCESS(result) )
1458 Parser.FillTimedTextDescriptor(TDesc);
1459 FrameBuffer.Capacity(Options.fb_size);
1461 if ( Options.verbose_flag )
1463 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1464 TimedText::DescriptorDump(TDesc);
1468 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1470 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1471 if ( Options.asset_id_flag )
1472 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1474 Kumu::GenRandomUUID(Info.AssetUUID);
1476 if ( Options.use_smpte_labels )
1478 Info.LabelSetType = LS_MXF_SMPTE;
1479 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1482 // configure encryption
1483 if( Options.key_flag )
1485 Kumu::GenRandomUUID(Info.ContextID);
1486 Info.EncryptedEssence = true;
1488 if ( Options.key_id_flag )
1489 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1491 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1493 Context = new AESEncContext;
1494 result = Context->InitKey(Options.key_value);
1496 if ( ASDCP_SUCCESS(result) )
1497 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1499 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1501 Info.UsesHMAC = true;
1502 HMAC = new HMACContext;
1503 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1507 if ( ASDCP_SUCCESS(result) )
1508 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1511 if ( ASDCP_FAILURE(result) )
1515 TimedText::ResourceList_t::const_iterator ri;
1517 result = Parser.ReadTimedTextResource(XMLDoc);
1519 if ( ASDCP_SUCCESS(result) )
1520 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1522 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1524 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1526 if ( ASDCP_SUCCESS(result) )
1528 if ( Options.verbose_flag )
1529 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1531 if ( ! Options.no_write_flag )
1533 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1535 // The Writer class will forward the last block of ciphertext
1536 // to the encryption context for use as the IV for the next
1537 // frame. If you want to use non-sequitur IV values, un-comment
1538 // the following line of code.
1539 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1540 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1544 if ( result == RESULT_ENDOFFILE )
1548 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1549 result = Writer.Finalize();
1555 // Read one or more timed text streams from a plaintext ASDCP file
1556 // Read one or more timed text streams from a ciphertext ASDCP file
1557 // Read one or more timed text streams from a ciphertext ASDCP file
1560 read_timed_text_file(CommandOptions& Options)
1562 AESDecContext* Context = 0;
1563 HMACContext* HMAC = 0;
1564 TimedText::MXFReader Reader;
1565 TimedText::FrameBuffer FrameBuffer;
1566 TimedText::TimedTextDescriptor TDesc;
1568 Result_t result = Reader.OpenRead(Options.filenames[0]);
1570 if ( ASDCP_SUCCESS(result) )
1572 Reader.FillTimedTextDescriptor(TDesc);
1573 FrameBuffer.Capacity(Options.fb_size);
1575 if ( Options.verbose_flag )
1576 TimedText::DescriptorDump(TDesc);
1579 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1581 Context = new AESDecContext;
1582 result = Context->InitKey(Options.key_value);
1584 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1587 Reader.FillWriterInfo(Info);
1589 if ( Info.UsesHMAC )
1591 HMAC = new HMACContext;
1592 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1596 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1601 if ( ASDCP_FAILURE(result) )
1605 std::string out_path = Kumu::PathDirname(Options.file_root);
1608 TimedText::ResourceList_t::const_iterator ri;
1610 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1612 if ( ASDCP_SUCCESS(result) )
1614 Kumu::FileWriter Writer;
1615 result = Writer.OpenWrite(Options.file_root);
1617 if ( ASDCP_SUCCESS(result) )
1618 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1621 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1623 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1625 if ( ASDCP_SUCCESS(result) )
1627 Kumu::FileWriter Writer;
1628 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1630 if ( ASDCP_SUCCESS(result) )
1632 if ( Options.verbose_flag )
1633 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1635 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1643 //------------------------------------------------------------------------------------------
1647 // These classes wrap the irregular names in the asdcplib API
1648 // so that I can use a template to simplify the implementation
1649 // of show_file_info()
1651 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1654 void FillDescriptor(MPEG2::MXFReader& Reader) {
1655 Reader.FillVideoDescriptor(*this);
1658 void Dump(FILE* stream) {
1659 MPEG2::VideoDescriptorDump(*this, stream);
1663 class MyPictureDescriptor : public JP2K::PictureDescriptor
1666 void FillDescriptor(JP2K::MXFReader& Reader) {
1667 Reader.FillPictureDescriptor(*this);
1670 void Dump(FILE* stream) {
1671 JP2K::PictureDescriptorDump(*this, stream);
1675 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1678 void FillDescriptor(JP2K::MXFSReader& Reader) {
1679 Reader.FillPictureDescriptor(*this);
1682 void Dump(FILE* stream) {
1683 JP2K::PictureDescriptorDump(*this, stream);
1687 class MyAudioDescriptor : public PCM::AudioDescriptor
1690 void FillDescriptor(PCM::MXFReader& Reader) {
1691 Reader.FillAudioDescriptor(*this);
1694 void Dump(FILE* stream) {
1695 PCM::AudioDescriptorDump(*this, stream);
1699 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1702 void FillDescriptor(TimedText::MXFReader& Reader) {
1703 Reader.FillTimedTextDescriptor(*this);
1706 void Dump(FILE* stream) {
1707 TimedText::DescriptorDump(*this, stream);
1711 // MSVC didn't like the function template, so now it's a static class method
1712 template<class ReaderT, class DescriptorT>
1713 class FileInfoWrapper
1717 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1719 assert(type_string);
1723 Result_t result = RESULT_OK;
1725 if ( Options.verbose_flag || Options.showheader_flag )
1728 result = Reader.OpenRead(Options.filenames[0]);
1730 if ( ASDCP_SUCCESS(result) )
1732 fprintf(stdout, "File essence type is %s.\n", type_string);
1734 if ( Options.showheader_flag )
1735 Reader.DumpHeaderMetadata(stream);
1738 Reader.FillWriterInfo(WI);
1739 WriterInfoDump(WI, stream);
1742 Desc.FillDescriptor(Reader);
1745 if ( Options.showindex_flag )
1746 Reader.DumpIndex(stream);
1748 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1750 Reader.DumpHeaderMetadata(stream);
1758 // Read header metadata from an ASDCP file
1761 show_file_info(CommandOptions& Options)
1763 EssenceType_t EssenceType;
1764 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1766 if ( ASDCP_FAILURE(result) )
1769 if ( EssenceType == ESS_MPEG2_VES )
1770 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1772 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1773 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1775 else if ( EssenceType == ESS_JPEG_2000 )
1777 if ( Options.stereo_image_flag )
1778 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1779 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1782 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1783 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1785 else if ( EssenceType == ESS_JPEG_2000_S )
1786 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1787 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1789 else if ( EssenceType == ESS_TIMED_TEXT )
1790 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1794 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1795 Kumu::FileReader Reader;
1796 const Dictionary* Dict = &DefaultCompositeDict();
1797 MXF::OPAtomHeader TestHeader(Dict);
1799 result = Reader.OpenRead(Options.filenames[0]);
1801 if ( ASDCP_SUCCESS(result) )
1802 result = TestHeader.InitFromFile(Reader); // test UL and OP
1804 if ( ASDCP_SUCCESS(result) )
1806 TestHeader.Partition::Dump(stdout);
1808 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1811 fputs("File contains no Identification object.\n", stdout);
1813 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1816 fputs("File contains no SourcePackage object.\n", stdout);
1820 fputs("File is not MXF.\n", stdout);
1830 digest_file(const char* filename)
1832 using namespace Kumu;
1834 ASDCP_TEST_NULL_STR(filename);
1838 ByteString Buf(8192);
1840 Result_t result = Reader.OpenRead(filename);
1842 while ( ASDCP_SUCCESS(result) )
1844 ui32_t read_count = 0;
1845 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1847 if ( result == RESULT_ENDOFFILE )
1853 if ( ASDCP_SUCCESS(result) )
1854 SHA1_Update(&Ctx, Buf.Data(), read_count);
1857 if ( ASDCP_SUCCESS(result) )
1859 const ui32_t sha_len = 20;
1860 byte_t bin_buf[sha_len];
1862 SHA1_Final(bin_buf, &Ctx);
1864 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1872 main(int argc, const char** argv)
1874 Result_t result = RESULT_OK;
1876 CommandOptions Options(argc, argv);
1878 if ( Options.version_flag )
1881 if ( Options.help_flag )
1884 if ( Options.version_flag || Options.help_flag )
1887 if ( Options.error_flag )
1889 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1893 if ( Options.mode == MMT_INFO )
1895 result = show_file_info(Options);
1897 else if ( Options.mode == MMT_GOP_START )
1899 result = gop_start_test(Options);
1901 else if ( Options.mode == MMT_GEN_KEY )
1903 Kumu::FortunaRNG RNG;
1904 byte_t bin_buf[KeyLen];
1906 RNG.FillRandom(bin_buf, KeyLen);
1907 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1909 else if ( Options.mode == MMT_GEN_ID )
1912 Kumu::GenRandomValue(TmpID);
1913 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1915 else if ( Options.mode == MMT_DIGEST )
1917 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1918 result = digest_file(Options.filenames[i]);
1920 else if ( Options.mode == MMT_UL_LIST )
1922 if ( Options.use_smpte_labels )
1923 DefaultSMPTEDict().Dump(stdout);
1925 DefaultInteropDict().Dump(stdout);
1927 else if ( Options.mode == MMT_EXTRACT )
1929 EssenceType_t EssenceType;
1930 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1932 if ( ASDCP_SUCCESS(result) )
1934 switch ( EssenceType )
1937 result = read_MPEG2_file(Options);
1941 if ( Options.stereo_image_flag )
1942 result = read_JP2K_S_file(Options);
1944 result = read_JP2K_file(Options);
1947 case ESS_JPEG_2000_S:
1948 result = read_JP2K_S_file(Options);
1951 case ESS_PCM_24b_48k:
1952 case ESS_PCM_24b_96k:
1953 result = read_PCM_file(Options);
1956 case ESS_TIMED_TEXT:
1957 result = read_timed_text_file(Options);
1961 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1966 else if ( Options.mode == MMT_CREATE )
1968 if ( Options.do_repeat && ! Options.duration_flag )
1970 fputs("Option -R requires -d <duration>\n", stderr);
1974 EssenceType_t EssenceType;
1975 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1977 if ( ASDCP_SUCCESS(result) )
1979 switch ( EssenceType )
1982 result = write_MPEG2_file(Options);
1986 if ( Options.stereo_image_flag )
1987 result = write_JP2K_S_file(Options);
1990 result = write_JP2K_file(Options);
1994 case ESS_PCM_24b_48k:
1995 case ESS_PCM_24b_96k:
1996 result = write_PCM_file(Options);
1999 case ESS_TIMED_TEXT:
2000 result = write_timed_text_file(Options);
2004 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2005 Options.filenames[0]);
2012 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2016 if ( ASDCP_FAILURE(result) )
2018 fputs("Program stopped on error.\n", stderr);
2020 if ( result == RESULT_SFORMAT )
2022 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2024 else if ( result != RESULT_FAIL )
2026 fputs(result, stderr);
2027 fputc('\n', stderr);
2038 // end asdcp-test.cpp