2 Copyright (c) 2003-2014, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14 derived from this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /*! \file asdcp-test.cpp
29 \brief AS-DCP file manipulation utility
31 This program provides command line access to the major features of the asdcplib
32 library, and serves as a library unit test which provides the functionality of
33 the supported use cases.
35 For more information about asdcplib, please refer to the header file AS_DCP.h
37 WARNING: While the asdcplib library attempts to provide a complete and secure
38 implementation of the cryptographic features of the AS-DCP file formats, this
39 unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40 production environment without some modification.
42 In particular, this program uses weak IV generation and externally generated
43 plaintext keys. These shortcomings exist because cryptographic-quality
44 random number generation and key management are outside the scope of the
45 asdcplib library. Developers using asdcplib for commercial implementations
46 claiming SMPTE conformance are expected to provide proper implementations of
50 #include <KM_fileio.h>
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-2015 John Hurst\n\n\
107 asdcplib may be copied only under the terms of the license found at\n\
108 the top of every file in the asdcplib distribution kit.\n\n\
109 Specify the -h (help) option for further information about %s\n\n",
110 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
115 usage(FILE* stream = stdout)
118 USAGE: %s -c <output-file> [-3] [-a <uuid>] [-b <buffer-size>]\n\
119 [-d <duration>] [-e|-E] [-f <start-frame>] [-j <key-id-string>]\n\
120 [-k <key-string>] [-l <label>] [-L] [-M] [-p <frame-rate>] [-R]\n\
121 [-s <num>] [-v] [-W] [-z|-Z] <input-file> [<input-file-2> ...]\n\
123 %s [-h|-help] [-V]\n\
125 %s -i [-H] [-n] [-v] <input-file>\n\
129 %s -G [-v] <input-file>\n\
131 %s -t <input-file>\n\
133 %s -x <file-prefix> [-3] [-b <buffer-size>] [-d <duration>]\n\
134 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
135 [-v] [-W] [-w] <input-file>\n\n",
136 PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME,
137 PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
141 -3 - With -c, create a stereoscopic image file. Expects two\n\
142 directories of JP2K codestreams (directories must have\n\
143 an equal number of frames; left eye is first).\n\
144 - With -x, force stereoscopic interpretation of a JP2K\n\
146 -c <output-file> - Create an AS-DCP track file from input(s)\n\
147 -g - Generate a random 16 byte value to stdout\n\
148 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
149 -h | -help - Show help\n\
150 -i - Show file info\n\
151 -t - Calculate message digest of input file\n\
152 -U - Dump UL catalog to stdout\n\
153 -u - Generate a random UUID value to stdout\n\
154 -V - Show version information\n\
155 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
160 -e - Encrypt MPEG or JP2K headers (default)\n\
161 -E - Do not encrypt MPEG or JP2K headers\n\
162 -j <key-id-str> - Write key ID instead of creating a random value\n\
163 -k <key-string> - Use key for ciphertext operations\n\
164 -m - verify HMAC values when reading\n\
165 -M - Do not create HMAC values when writing\n\
169 Read/Write Options:\n\
170 -a <UUID> - Specify the Asset ID of a file (with -c)\n\
171 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
172 Defaults to 4,194,304 (4MB)\n\
173 -d <duration> - Number of frames to process, default all\n\
174 -f <start-frame> - Starting frame number, default 0\n\
175 -l <label> - Use given channel format label when writing MXF sound\n\
176 files. SMPTE 429-2 labels: '5.1', '6.1', '7.1', '7.1DS', 'WTF'.\n\
177 Default is no label (valid for Interop only).\n\
178 -L - Write SMPTE UL values instead of MXF Interop\n\
179 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
180 Use one of [23|24|25|30|48|50|60], 24 is default\n\
181 -R - Repeat the first frame over the entire file (picture\n\
182 essence only, requires -c, -d)\n\
183 -S - Split Wave essence to stereo WAV files during extract.\n\
184 Default is multichannel WAV\n\
185 -1 - Split Wave essence to mono WAV files during extract.\n\
186 Default is multichannel WAV\n\
187 -W - Read input file only, do not write source file\n\
188 -w <width> - Width of numeric element in a series of frame file names\n\
189 (use with -x, default 6).\n\
190 -z - Fail if j2c inputs have unequal parameters (default)\n\
191 -Z - Ignore unequal parameters in j2c inputs\n\
196 -H - Show MXF header metadata, used with option -i\n\
197 -n - Show index, used with option -i\n\
200 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
201 stderr, used with option -v\n\
202 -v - Verbose, prints informative messages to stderr\n\
204 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
205 o All option arguments must be separated from the option by whitespace.\n\
206 o An argument of \"23\" to the -p option will be interpreted\n\
207 as 24000/1001 fps.\n\
227 decode_channel_fmt(const std::string& label_name)
229 if ( label_name == "5.1" )
230 return PCM::CF_CFG_1;
232 else if ( label_name == "6.1" )
233 return PCM::CF_CFG_2;
235 else if ( label_name == "7.1" )
236 return PCM::CF_CFG_3;
238 else if ( label_name == "WTF" )
239 return PCM::CF_CFG_4;
241 else if ( label_name == "7.1DS" )
242 return PCM::CF_CFG_5;
244 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
245 fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
257 bool error_flag; // true if the given options are in error or not complete
258 bool key_flag; // true if an encryption key was given
259 bool key_id_flag; // true if a key ID was given
260 bool asset_id_flag; // true if an asset ID was given
261 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
262 bool write_hmac; // true if HMAC values are to be generated and written
263 bool read_hmac; // true if HMAC values are to be validated
264 bool split_wav; // true if PCM is to be extracted to stereo WAV files
265 bool mono_wav; // true if PCM is to be extracted to mono WAV files
266 bool verbose_flag; // true if the verbose option was selected
267 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
268 bool showindex_flag; // true if index is to be displayed
269 bool showheader_flag; // true if MXF file header is to be displayed
270 bool no_write_flag; // true if no output files are to be written
271 bool version_flag; // true if the version display option was selected
272 bool help_flag; // true if the help display option was selected
273 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
274 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
275 ui32_t start_frame; // frame number to begin processing
276 ui32_t duration; // number of frames to be processed
277 bool duration_flag; // true if duration argument given
278 bool do_repeat; // if true and -c -d, repeat first input frame
279 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
280 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
281 ui32_t picture_rate; // fps of picture when wrapping PCM
282 ui32_t fb_size; // size of picture frame buffer
283 ui32_t file_count; // number of elements in filenames[]
284 const char* file_root; // filename pre for files written by the extract mode
285 const char* out_file; // name of mxf file created by create mode
286 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
287 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
288 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
289 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
290 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
293 Rational PictureRate()
295 if ( picture_rate == 16 ) return EditRate_16;
296 if ( picture_rate == 18 ) return EditRate_18;
297 if ( picture_rate == 20 ) return EditRate_20;
298 if ( picture_rate == 22 ) return EditRate_22;
299 if ( picture_rate == 23 ) return EditRate_23_98;
300 if ( picture_rate == 24 ) return EditRate_24;
301 if ( picture_rate == 25 ) return EditRate_25;
302 if ( picture_rate == 30 ) return EditRate_30;
303 if ( picture_rate == 48 ) return EditRate_48;
304 if ( picture_rate == 50 ) return EditRate_50;
305 if ( picture_rate == 60 ) return EditRate_60;
306 if ( picture_rate == 96 ) return EditRate_96;
307 if ( picture_rate == 100 ) return EditRate_100;
308 if ( picture_rate == 120 ) return EditRate_120;
309 if ( picture_rate == 192 ) return EditRate_192;
310 if ( picture_rate == 200 ) return EditRate_200;
311 if ( picture_rate == 240 ) return EditRate_240;
316 const char* szPictureRate()
318 if ( picture_rate == 16 ) return "16";
319 if ( picture_rate == 18 ) return "18.182";
320 if ( picture_rate == 20 ) return "20";
321 if ( picture_rate == 22 ) return "21.818";
322 if ( picture_rate == 23 ) return "23.976";
323 if ( picture_rate == 24 ) return "24";
324 if ( picture_rate == 25 ) return "25";
325 if ( picture_rate == 30 ) return "30";
326 if ( picture_rate == 48 ) return "48";
327 if ( picture_rate == 50 ) return "50";
328 if ( picture_rate == 60 ) return "60";
329 if ( picture_rate == 96 ) return "96";
330 if ( picture_rate == 100 ) return "100";
331 if ( picture_rate == 120 ) return "120";
332 if ( picture_rate == 192 ) return "192";
333 if ( picture_rate == 200 ) return "200";
334 if ( picture_rate == 240 ) return "240";
339 CommandOptions(int argc, const char** argv) :
340 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
341 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
342 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
343 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
344 number_width(6), start_frame(0),
345 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
346 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
347 channel_fmt(PCM::CF_NONE)
349 memset(key_value, 0, KeyLen);
350 memset(key_id_value, 0, UUIDlen);
352 for ( int i = 1; i < argc; i++ )
355 if ( (strcmp( argv[i], "-help") == 0) )
361 if ( argv[i][0] == '-'
362 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
365 switch ( argv[i][1] )
367 case '1': mono_wav = true; break;
368 case '2': split_wav = true; break;
369 case '3': stereo_image_flag = true; break;
372 asset_id_flag = true;
373 TEST_EXTRA_ARG(i, 'a');
376 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
378 if ( length != UUIDlen )
380 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
387 TEST_EXTRA_ARG(i, 'b');
388 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
391 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
396 TEST_EXTRA_ARG(i, 'c');
402 TEST_EXTRA_ARG(i, 'd');
403 duration_flag = true;
404 duration = Kumu::xabs(strtol(argv[i], 0, 10));
407 case 'E': encrypt_header_flag = false; break;
408 case 'e': encrypt_header_flag = true; break;
411 TEST_EXTRA_ARG(i, 'f');
412 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
415 case 'G': mode = MMT_GOP_START; break;
416 case 'g': mode = MMT_GEN_KEY; break;
417 case 'H': showheader_flag = true; break;
418 case 'h': help_flag = true; break;
419 case 'i': mode = MMT_INFO; break;
421 case 'j': key_id_flag = true;
422 TEST_EXTRA_ARG(i, 'j');
425 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
427 if ( length != UUIDlen )
429 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
435 case 'k': key_flag = true;
436 TEST_EXTRA_ARG(i, 'k');
439 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
441 if ( length != KeyLen )
443 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
450 TEST_EXTRA_ARG(i, 'l');
451 channel_fmt = decode_channel_fmt(argv[i]);
454 case 'L': use_smpte_labels = true; break;
455 case 'M': write_hmac = false; break;
456 case 'm': read_hmac = true; break;
457 case 'n': showindex_flag = true; break;
460 TEST_EXTRA_ARG(i, 'p');
461 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
464 case 'R': do_repeat = true; break;
465 case 'S': split_wav = true; break;
468 TEST_EXTRA_ARG(i, 's');
469 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
472 case 't': mode = MMT_DIGEST; break;
473 case 'U': mode = MMT_UL_LIST; break;
474 case 'u': mode = MMT_GEN_ID; break;
475 case 'V': version_flag = true; break;
476 case 'v': verbose_flag = true; break;
477 case 'W': no_write_flag = true; break;
480 TEST_EXTRA_ARG(i, 'w');
481 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
485 TEST_EXTRA_ARG(i, 'x');
490 case 'Z': j2c_pedantic = false; break;
491 case 'z': j2c_pedantic = true; break;
494 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
501 if ( argv[i][0] != '-' )
503 filenames[file_count++] = argv[i];
507 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
511 if ( file_count >= MAX_IN_FILES )
513 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
519 if ( help_flag || version_flag )
522 if ( ( mode == MMT_INFO
523 || mode == MMT_CREATE
524 || mode == MMT_EXTRACT
525 || mode == MMT_GOP_START
526 || mode == MMT_DIGEST ) && file_count == 0 )
528 fputs("Option requires at least one filename argument.\n", stderr);
532 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
534 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
542 //------------------------------------------------------------------------------------------
545 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
546 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
549 write_MPEG2_file(CommandOptions& Options)
551 AESEncContext* Context = 0;
552 HMACContext* HMAC = 0;
553 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
554 MPEG2::Parser Parser;
555 MPEG2::MXFWriter Writer;
556 MPEG2::VideoDescriptor VDesc;
557 byte_t IV_buf[CBC_BLOCK_SIZE];
558 Kumu::FortunaRNG RNG;
560 // set up essence parser
561 Result_t result = Parser.OpenRead(Options.filenames[0]);
564 if ( ASDCP_SUCCESS(result) )
566 Parser.FillVideoDescriptor(VDesc);
568 if ( Options.verbose_flag )
570 fputs("MPEG-2 Pictures\n", stderr);
571 fputs("VideoDescriptor:\n", stderr);
572 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
573 MPEG2::VideoDescriptorDump(VDesc);
577 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
579 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
580 if ( Options.asset_id_flag )
581 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
583 Kumu::GenRandomUUID(Info.AssetUUID);
585 if ( Options.use_smpte_labels )
587 Info.LabelSetType = LS_MXF_SMPTE;
588 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
591 // configure encryption
592 if( Options.key_flag )
594 Kumu::GenRandomUUID(Info.ContextID);
595 Info.EncryptedEssence = true;
597 if ( Options.key_id_flag )
598 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
600 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
602 Context = new AESEncContext;
603 result = Context->InitKey(Options.key_value);
605 if ( ASDCP_SUCCESS(result) )
606 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
608 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
610 Info.UsesHMAC = true;
611 HMAC = new HMACContext;
612 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
616 if ( ASDCP_SUCCESS(result) )
617 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
620 if ( ASDCP_SUCCESS(result) )
621 // loop through the frames
623 result = Parser.Reset();
626 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
628 if ( ! Options.do_repeat || duration == 1 )
630 result = Parser.ReadFrame(FrameBuffer);
632 if ( ASDCP_SUCCESS(result) )
634 if ( Options.verbose_flag )
635 FrameBuffer.Dump(stderr, Options.fb_dump_size);
637 if ( Options.encrypt_header_flag )
638 FrameBuffer.PlaintextOffset(0);
642 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
644 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
646 // The Writer class will forward the last block of ciphertext
647 // to the encryption context for use as the IV for the next
648 // frame. If you want to use non-sequitur IV values, un-comment
649 // the following line of code.
650 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
651 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
655 if ( result == RESULT_ENDOFFILE )
659 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
660 result = Writer.Finalize();
665 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
666 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
667 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
670 read_MPEG2_file(CommandOptions& Options)
672 AESDecContext* Context = 0;
673 HMACContext* HMAC = 0;
674 MPEG2::MXFReader Reader;
675 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
676 Kumu::FileWriter OutFile;
677 ui32_t frame_count = 0;
679 Result_t result = Reader.OpenRead(Options.filenames[0]);
681 if ( ASDCP_SUCCESS(result) )
683 MPEG2::VideoDescriptor VDesc;
684 Reader.FillVideoDescriptor(VDesc);
685 frame_count = VDesc.ContainerDuration;
687 if ( Options.verbose_flag )
689 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
690 MPEG2::VideoDescriptorDump(VDesc);
694 if ( ASDCP_SUCCESS(result) )
697 snprintf(filename, 256, "%s.ves", Options.file_root);
698 result = OutFile.OpenWrite(filename);
701 if ( ASDCP_SUCCESS(result) && Options.key_flag )
703 Context = new AESDecContext;
704 result = Context->InitKey(Options.key_value);
706 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
709 Reader.FillWriterInfo(Info);
713 HMAC = new HMACContext;
714 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
718 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
723 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
724 if ( last_frame > frame_count )
725 last_frame = frame_count;
727 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
729 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
731 if ( ASDCP_SUCCESS(result) )
733 if ( Options.verbose_flag )
734 FrameBuffer.Dump(stderr, Options.fb_dump_size);
736 ui32_t write_count = 0;
737 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
747 gop_start_test(CommandOptions& Options)
749 using namespace ASDCP::MPEG2;
752 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
753 ui32_t frame_count = 0;
755 Result_t result = Reader.OpenRead(Options.filenames[0]);
757 if ( ASDCP_SUCCESS(result) )
759 MPEG2::VideoDescriptor VDesc;
760 Reader.FillVideoDescriptor(VDesc);
761 frame_count = VDesc.ContainerDuration;
763 if ( Options.verbose_flag )
765 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
766 MPEG2::VideoDescriptorDump(VDesc);
770 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
771 if ( last_frame > frame_count )
772 last_frame = frame_count;
774 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
776 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
778 if ( ASDCP_SUCCESS(result) )
780 if ( Options.verbose_flag )
781 FrameBuffer.Dump(stderr, Options.fb_dump_size);
783 if ( FrameBuffer.FrameType() != FRAME_I )
784 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
786 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
793 //------------------------------------------------------------------------------------------
796 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
797 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
800 write_JP2K_S_file(CommandOptions& Options)
802 AESEncContext* Context = 0;
803 HMACContext* HMAC = 0;
804 JP2K::MXFSWriter Writer;
805 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
806 JP2K::PictureDescriptor PDesc;
807 JP2K::SequenceParser ParserLeft, ParserRight;
808 byte_t IV_buf[CBC_BLOCK_SIZE];
809 Kumu::FortunaRNG RNG;
811 if ( Options.file_count != 2 )
813 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
817 // set up essence parser
818 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
820 if ( ASDCP_SUCCESS(result) )
821 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
824 if ( ASDCP_SUCCESS(result) )
826 ParserLeft.FillPictureDescriptor(PDesc);
827 PDesc.EditRate = Options.PictureRate();
829 if ( Options.verbose_flag )
831 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
832 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
833 JP2K::PictureDescriptorDump(PDesc);
837 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
839 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
840 if ( Options.asset_id_flag )
841 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
843 Kumu::GenRandomUUID(Info.AssetUUID);
845 if ( Options.use_smpte_labels )
847 Info.LabelSetType = LS_MXF_SMPTE;
848 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
851 // configure encryption
852 if( Options.key_flag )
854 Kumu::GenRandomUUID(Info.ContextID);
855 Info.EncryptedEssence = true;
857 if ( Options.key_id_flag )
858 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
860 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
862 Context = new AESEncContext;
863 result = Context->InitKey(Options.key_value);
865 if ( ASDCP_SUCCESS(result) )
866 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
868 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
870 Info.UsesHMAC = true;
871 HMAC = new HMACContext;
872 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
876 if ( ASDCP_SUCCESS(result) )
877 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
880 if ( ASDCP_SUCCESS(result) )
883 result = ParserLeft.Reset();
884 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
886 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
888 result = ParserLeft.ReadFrame(FrameBuffer);
890 if ( ASDCP_SUCCESS(result) )
892 if ( Options.verbose_flag )
893 FrameBuffer.Dump(stderr, Options.fb_dump_size);
895 if ( Options.encrypt_header_flag )
896 FrameBuffer.PlaintextOffset(0);
899 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
900 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
902 if ( ASDCP_SUCCESS(result) )
903 result = ParserRight.ReadFrame(FrameBuffer);
905 if ( ASDCP_SUCCESS(result) )
907 if ( Options.verbose_flag )
908 FrameBuffer.Dump(stderr, Options.fb_dump_size);
910 if ( Options.encrypt_header_flag )
911 FrameBuffer.PlaintextOffset(0);
914 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
915 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
918 if ( result == RESULT_ENDOFFILE )
922 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
923 result = Writer.Finalize();
928 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
929 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
930 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
932 read_JP2K_S_file(CommandOptions& Options)
934 AESDecContext* Context = 0;
935 HMACContext* HMAC = 0;
936 JP2K::MXFSReader Reader;
937 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
938 ui32_t frame_count = 0;
940 Result_t result = Reader.OpenRead(Options.filenames[0]);
942 if ( ASDCP_SUCCESS(result) )
944 JP2K::PictureDescriptor PDesc;
945 Reader.FillPictureDescriptor(PDesc);
947 frame_count = PDesc.ContainerDuration;
949 if ( Options.verbose_flag )
951 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
952 JP2K::PictureDescriptorDump(PDesc);
956 if ( ASDCP_SUCCESS(result) && Options.key_flag )
958 Context = new AESDecContext;
959 result = Context->InitKey(Options.key_value);
961 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
964 Reader.FillWriterInfo(Info);
968 HMAC = new HMACContext;
969 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
973 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
978 const int filename_max = 1024;
979 char filename[filename_max];
980 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
981 if ( last_frame > frame_count )
982 last_frame = frame_count;
984 char left_format[64]; char right_format[64];
985 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
986 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
988 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
990 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
992 if ( ASDCP_SUCCESS(result) )
994 Kumu::FileWriter OutFile;
996 snprintf(filename, filename_max, left_format, Options.file_root, i);
997 result = OutFile.OpenWrite(filename);
999 if ( ASDCP_SUCCESS(result) )
1000 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1002 if ( Options.verbose_flag )
1003 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1006 if ( ASDCP_SUCCESS(result) )
1007 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
1009 if ( ASDCP_SUCCESS(result) )
1011 Kumu::FileWriter OutFile;
1013 snprintf(filename, filename_max, right_format, Options.file_root, i);
1014 result = OutFile.OpenWrite(filename);
1016 if ( ASDCP_SUCCESS(result) )
1017 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1026 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1027 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1030 write_JP2K_file(CommandOptions& Options)
1032 AESEncContext* Context = 0;
1033 HMACContext* HMAC = 0;
1034 JP2K::MXFWriter Writer;
1035 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1036 JP2K::PictureDescriptor PDesc;
1037 JP2K::SequenceParser Parser;
1038 byte_t IV_buf[CBC_BLOCK_SIZE];
1039 Kumu::FortunaRNG RNG;
1041 // set up essence parser
1042 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1044 // set up MXF writer
1045 if ( ASDCP_SUCCESS(result) )
1047 Parser.FillPictureDescriptor(PDesc);
1048 PDesc.EditRate = Options.PictureRate();
1050 if ( Options.verbose_flag )
1052 fprintf(stderr, "JPEG 2000 pictures\n");
1053 fputs("PictureDescriptor:\n", stderr);
1054 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1055 JP2K::PictureDescriptorDump(PDesc);
1059 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1061 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1062 if ( Options.asset_id_flag )
1063 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1065 Kumu::GenRandomUUID(Info.AssetUUID);
1067 if ( Options.use_smpte_labels )
1069 Info.LabelSetType = LS_MXF_SMPTE;
1070 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1073 // configure encryption
1074 if( Options.key_flag )
1076 Kumu::GenRandomUUID(Info.ContextID);
1077 Info.EncryptedEssence = true;
1079 if ( Options.key_id_flag )
1080 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1082 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1084 Context = new AESEncContext;
1085 result = Context->InitKey(Options.key_value);
1087 if ( ASDCP_SUCCESS(result) )
1088 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1090 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1092 Info.UsesHMAC = true;
1093 HMAC = new HMACContext;
1094 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1098 if ( ASDCP_SUCCESS(result) )
1099 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1102 if ( ASDCP_SUCCESS(result) )
1104 ui32_t duration = 0;
1105 result = Parser.Reset();
1107 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1109 if ( ! Options.do_repeat || duration == 1 )
1111 result = Parser.ReadFrame(FrameBuffer);
1113 if ( ASDCP_SUCCESS(result) )
1115 if ( Options.verbose_flag )
1116 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1118 if ( Options.encrypt_header_flag )
1119 FrameBuffer.PlaintextOffset(0);
1123 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1125 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1127 // The Writer class will forward the last block of ciphertext
1128 // to the encryption context for use as the IV for the next
1129 // frame. If you want to use non-sequitur IV values, un-comment
1130 // the following line of code.
1131 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1132 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1136 if ( result == RESULT_ENDOFFILE )
1140 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1141 result = Writer.Finalize();
1146 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1147 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1148 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1151 read_JP2K_file(CommandOptions& Options)
1153 AESDecContext* Context = 0;
1154 HMACContext* HMAC = 0;
1155 JP2K::MXFReader Reader;
1156 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1157 ui32_t frame_count = 0;
1159 Result_t result = Reader.OpenRead(Options.filenames[0]);
1161 if ( ASDCP_SUCCESS(result) )
1163 JP2K::PictureDescriptor PDesc;
1164 Reader.FillPictureDescriptor(PDesc);
1166 frame_count = PDesc.ContainerDuration;
1168 if ( Options.verbose_flag )
1170 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1171 JP2K::PictureDescriptorDump(PDesc);
1175 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1177 Context = new AESDecContext;
1178 result = Context->InitKey(Options.key_value);
1180 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1183 Reader.FillWriterInfo(Info);
1185 if ( Info.UsesHMAC )
1187 HMAC = new HMACContext;
1188 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1192 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1197 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1198 if ( last_frame > frame_count )
1199 last_frame = frame_count;
1201 char name_format[64];
1202 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1204 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1206 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1208 if ( ASDCP_SUCCESS(result) )
1210 Kumu::FileWriter OutFile;
1213 snprintf(filename, 256, name_format, Options.file_root, i);
1214 result = OutFile.OpenWrite(filename);
1216 if ( ASDCP_SUCCESS(result) )
1217 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1219 if ( Options.verbose_flag )
1220 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1227 //------------------------------------------------------------------------------------------
1231 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1232 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1235 write_PCM_file(CommandOptions& Options)
1237 AESEncContext* Context = 0;
1238 HMACContext* HMAC = 0;
1239 PCMParserList Parser;
1240 PCM::MXFWriter Writer;
1241 PCM::FrameBuffer FrameBuffer;
1242 PCM::AudioDescriptor ADesc;
1243 Rational PictureRate = Options.PictureRate();
1244 byte_t IV_buf[CBC_BLOCK_SIZE];
1245 Kumu::FortunaRNG RNG;
1247 // set up essence parser
1248 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1250 // set up MXF writer
1251 if ( ASDCP_SUCCESS(result) )
1253 Parser.FillAudioDescriptor(ADesc);
1255 ADesc.EditRate = PictureRate;
1256 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1257 ADesc.ChannelFormat = Options.channel_fmt;
1259 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1261 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1264 if ( Options.verbose_flag )
1266 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1267 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1268 Options.szPictureRate(),
1269 PCM::CalcSamplesPerFrame(ADesc));
1270 fputs("AudioDescriptor:\n", stderr);
1271 PCM::AudioDescriptorDump(ADesc);
1275 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1277 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1278 if ( Options.asset_id_flag )
1279 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1281 Kumu::GenRandomUUID(Info.AssetUUID);
1283 if ( Options.use_smpte_labels )
1285 Info.LabelSetType = LS_MXF_SMPTE;
1286 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1289 // configure encryption
1290 if( Options.key_flag )
1292 Kumu::GenRandomUUID(Info.ContextID);
1293 Info.EncryptedEssence = true;
1295 if ( Options.key_id_flag )
1296 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1298 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1300 Context = new AESEncContext;
1301 result = Context->InitKey(Options.key_value);
1303 if ( ASDCP_SUCCESS(result) )
1304 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1306 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1308 Info.UsesHMAC = true;
1309 HMAC = new HMACContext;
1310 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1314 if ( ASDCP_SUCCESS(result) )
1315 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1318 if ( ASDCP_SUCCESS(result) )
1320 result = Parser.Reset();
1321 ui32_t duration = 0;
1323 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1325 result = Parser.ReadFrame(FrameBuffer);
1327 if ( ASDCP_SUCCESS(result) )
1329 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1331 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1332 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1333 result = RESULT_ENDOFFILE;
1337 if ( Options.verbose_flag )
1338 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1340 if ( ! Options.no_write_flag )
1342 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1344 // The Writer class will forward the last block of ciphertext
1345 // to the encryption context for use as the IV for the next
1346 // frame. If you want to use non-sequitur IV values, un-comment
1347 // the following line of code.
1348 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1349 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1354 if ( result == RESULT_ENDOFFILE )
1358 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1359 result = Writer.Finalize();
1364 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1365 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1366 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1369 read_PCM_file(CommandOptions& Options)
1371 AESDecContext* Context = 0;
1372 HMACContext* HMAC = 0;
1373 PCM::MXFReader Reader;
1374 PCM::FrameBuffer FrameBuffer;
1375 WavFileWriter OutWave;
1376 PCM::AudioDescriptor ADesc;
1377 ui32_t last_frame = 0;
1379 Result_t result = Reader.OpenRead(Options.filenames[0]);
1381 if ( ASDCP_SUCCESS(result) )
1383 Reader.FillAudioDescriptor(ADesc);
1385 if ( ADesc.EditRate != EditRate_23_98
1386 && ADesc.EditRate != EditRate_24
1387 && ADesc.EditRate != EditRate_25
1388 && ADesc.EditRate != EditRate_30
1389 && ADesc.EditRate != EditRate_48
1390 && ADesc.EditRate != EditRate_50
1391 && ADesc.EditRate != EditRate_60 )
1392 ADesc.EditRate = Options.PictureRate();
1394 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1396 if ( Options.verbose_flag )
1397 PCM::AudioDescriptorDump(ADesc);
1400 if ( ASDCP_SUCCESS(result) )
1402 last_frame = ADesc.ContainerDuration;
1404 if ( Options.duration > 0 && Options.duration < last_frame )
1405 last_frame = Options.duration;
1407 if ( Options.start_frame > 0 )
1409 if ( Options.start_frame > ADesc.ContainerDuration )
1411 fprintf(stderr, "Start value greater than file duration.\n");
1415 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1418 ADesc.ContainerDuration = last_frame - Options.start_frame;
1419 OutWave.OpenWrite(ADesc, Options.file_root,
1420 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1421 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1424 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1426 Context = new AESDecContext;
1427 result = Context->InitKey(Options.key_value);
1429 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1432 Reader.FillWriterInfo(Info);
1434 if ( Info.UsesHMAC )
1436 HMAC = new HMACContext;
1437 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1441 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1446 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1448 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1450 if ( ASDCP_SUCCESS(result) )
1452 if ( Options.verbose_flag )
1453 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1455 result = OutWave.WriteFrame(FrameBuffer);
1463 //------------------------------------------------------------------------------------------
1464 // TimedText essence
1467 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1468 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1471 write_timed_text_file(CommandOptions& Options)
1473 AESEncContext* Context = 0;
1474 HMACContext* HMAC = 0;
1475 TimedText::DCSubtitleParser Parser;
1476 TimedText::MXFWriter Writer;
1477 TimedText::FrameBuffer FrameBuffer;
1478 TimedText::TimedTextDescriptor TDesc;
1479 byte_t IV_buf[CBC_BLOCK_SIZE];
1480 Kumu::FortunaRNG RNG;
1482 // set up essence parser
1483 Result_t result = Parser.OpenRead(Options.filenames[0]);
1485 // set up MXF writer
1486 if ( ASDCP_SUCCESS(result) )
1488 Parser.FillTimedTextDescriptor(TDesc);
1489 FrameBuffer.Capacity(Options.fb_size);
1491 if ( Options.verbose_flag )
1493 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1494 TimedText::DescriptorDump(TDesc);
1498 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1500 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1501 if ( Options.asset_id_flag )
1502 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1504 Kumu::GenRandomUUID(Info.AssetUUID);
1506 if ( Options.use_smpte_labels )
1508 Info.LabelSetType = LS_MXF_SMPTE;
1509 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1512 // configure encryption
1513 if( Options.key_flag )
1515 Kumu::GenRandomUUID(Info.ContextID);
1516 Info.EncryptedEssence = true;
1518 if ( Options.key_id_flag )
1519 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1521 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1523 Context = new AESEncContext;
1524 result = Context->InitKey(Options.key_value);
1526 if ( ASDCP_SUCCESS(result) )
1527 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1529 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1531 Info.UsesHMAC = true;
1532 HMAC = new HMACContext;
1533 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1537 if ( ASDCP_SUCCESS(result) )
1538 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1541 if ( ASDCP_FAILURE(result) )
1545 TimedText::ResourceList_t::const_iterator ri;
1547 result = Parser.ReadTimedTextResource(XMLDoc);
1549 if ( ASDCP_SUCCESS(result) )
1550 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1552 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1554 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1556 if ( ASDCP_SUCCESS(result) )
1558 if ( Options.verbose_flag )
1559 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1561 if ( ! Options.no_write_flag )
1563 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1565 // The Writer class will forward the last block of ciphertext
1566 // to the encryption context for use as the IV for the next
1567 // frame. If you want to use non-sequitur IV values, un-comment
1568 // the following line of code.
1569 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1570 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1574 if ( result == RESULT_ENDOFFILE )
1578 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1579 result = Writer.Finalize();
1585 // Read one or more timed text streams from a plaintext ASDCP file
1586 // Read one or more timed text streams from a ciphertext ASDCP file
1587 // Read one or more timed text streams from a ciphertext ASDCP file
1590 read_timed_text_file(CommandOptions& Options)
1592 AESDecContext* Context = 0;
1593 HMACContext* HMAC = 0;
1594 TimedText::MXFReader Reader;
1595 TimedText::FrameBuffer FrameBuffer;
1596 TimedText::TimedTextDescriptor TDesc;
1598 Result_t result = Reader.OpenRead(Options.filenames[0]);
1600 if ( ASDCP_SUCCESS(result) )
1602 Reader.FillTimedTextDescriptor(TDesc);
1603 FrameBuffer.Capacity(Options.fb_size);
1605 if ( Options.verbose_flag )
1606 TimedText::DescriptorDump(TDesc);
1609 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1611 Context = new AESDecContext;
1612 result = Context->InitKey(Options.key_value);
1614 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1617 Reader.FillWriterInfo(Info);
1619 if ( Info.UsesHMAC )
1621 HMAC = new HMACContext;
1622 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1626 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1631 if ( ASDCP_FAILURE(result) )
1635 std::string out_path = Kumu::PathDirname(Options.file_root);
1638 TimedText::ResourceList_t::const_iterator ri;
1640 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1642 if ( ASDCP_SUCCESS(result) )
1644 Kumu::FileWriter Writer;
1645 result = Writer.OpenWrite(Options.file_root);
1647 if ( ASDCP_SUCCESS(result) )
1648 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1651 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1653 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1655 if ( ASDCP_SUCCESS(result) )
1657 Kumu::FileWriter Writer;
1658 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1660 if ( ASDCP_SUCCESS(result) )
1662 if ( Options.verbose_flag )
1663 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1665 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1673 //------------------------------------------------------------------------------------------
1677 // These classes wrap the irregular names in the asdcplib API
1678 // so that I can use a template to simplify the implementation
1679 // of show_file_info()
1681 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1684 void FillDescriptor(MPEG2::MXFReader& Reader) {
1685 Reader.FillVideoDescriptor(*this);
1688 void Dump(FILE* stream) {
1689 MPEG2::VideoDescriptorDump(*this, stream);
1693 class MyPictureDescriptor : public JP2K::PictureDescriptor
1696 void FillDescriptor(JP2K::MXFReader& Reader) {
1697 Reader.FillPictureDescriptor(*this);
1700 void Dump(FILE* stream) {
1701 JP2K::PictureDescriptorDump(*this, stream);
1705 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1708 void FillDescriptor(JP2K::MXFSReader& Reader) {
1709 Reader.FillPictureDescriptor(*this);
1712 void Dump(FILE* stream) {
1713 JP2K::PictureDescriptorDump(*this, stream);
1717 class MyAudioDescriptor : public PCM::AudioDescriptor
1720 void FillDescriptor(PCM::MXFReader& Reader) {
1721 Reader.FillAudioDescriptor(*this);
1724 void Dump(FILE* stream) {
1725 PCM::AudioDescriptorDump(*this, stream);
1729 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1732 void FillDescriptor(TimedText::MXFReader& Reader) {
1733 Reader.FillTimedTextDescriptor(*this);
1736 void Dump(FILE* stream) {
1737 TimedText::DescriptorDump(*this, stream);
1741 // MSVC didn't like the function template, so now it's a static class method
1742 template<class ReaderT, class DescriptorT>
1743 class FileInfoWrapper
1747 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1749 assert(type_string);
1753 Result_t result = RESULT_OK;
1755 if ( Options.verbose_flag || Options.showheader_flag )
1758 result = Reader.OpenRead(Options.filenames[0]);
1760 if ( ASDCP_SUCCESS(result) )
1762 fprintf(stdout, "File essence type is %s.\n", type_string);
1764 if ( Options.showheader_flag )
1765 Reader.DumpHeaderMetadata(stream);
1768 Reader.FillWriterInfo(WI);
1769 WriterInfoDump(WI, stream);
1772 Desc.FillDescriptor(Reader);
1775 if ( Options.showindex_flag )
1776 Reader.DumpIndex(stream);
1778 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1780 Reader.DumpHeaderMetadata(stream);
1788 // Read header metadata from an ASDCP file
1791 show_file_info(CommandOptions& Options)
1793 EssenceType_t EssenceType;
1794 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1796 if ( ASDCP_FAILURE(result) )
1799 if ( EssenceType == ESS_MPEG2_VES )
1801 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1803 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1805 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1807 if ( ASDCP_SUCCESS(result) )
1809 const Dictionary* Dict = &DefaultCompositeDict();
1810 PCM::MXFReader Reader;
1811 MXF::OP1aHeader Header(Dict);
1812 MXF::WaveAudioDescriptor *descriptor = 0;
1814 result = Reader.OpenRead(Options.filenames[0]);
1816 if ( ASDCP_SUCCESS(result) )
1817 result = Reader.OP1aHeader().GetMDObjectByType(Dict->ul(MDD_WaveAudioDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
1819 if ( ASDCP_SUCCESS(result) )
1822 fprintf(stdout, " ChannelAssignment: %s\n", descriptor->ChannelAssignment.const_get().EncodeString(buf, 64));
1826 else if ( EssenceType == ESS_JPEG_2000 )
1828 if ( Options.stereo_image_flag )
1830 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1831 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1835 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1836 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1839 else if ( EssenceType == ESS_JPEG_2000_S )
1841 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1842 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1844 else if ( EssenceType == ESS_TIMED_TEXT )
1846 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1850 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1851 Kumu::FileReader Reader;
1852 const Dictionary* Dict = &DefaultCompositeDict();
1853 MXF::OP1aHeader TestHeader(Dict);
1855 result = Reader.OpenRead(Options.filenames[0]);
1857 if ( ASDCP_SUCCESS(result) )
1858 result = TestHeader.InitFromFile(Reader); // test UL and OP
1860 if ( ASDCP_SUCCESS(result) )
1862 TestHeader.Partition::Dump(stdout);
1864 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1867 fputs("File contains no Identification object.\n", stdout);
1869 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1872 fputs("File contains no SourcePackage object.\n", stdout);
1876 fputs("File is not MXF.\n", stdout);
1886 digest_file(const char* filename)
1888 using namespace Kumu;
1890 ASDCP_TEST_NULL_STR(filename);
1894 ByteString Buf(8192);
1896 Result_t result = Reader.OpenRead(filename);
1898 while ( ASDCP_SUCCESS(result) )
1900 ui32_t read_count = 0;
1901 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1903 if ( result == RESULT_ENDOFFILE )
1909 if ( ASDCP_SUCCESS(result) )
1910 SHA1_Update(&Ctx, Buf.Data(), read_count);
1913 if ( ASDCP_SUCCESS(result) )
1915 const ui32_t sha_len = 20;
1916 byte_t bin_buf[sha_len];
1918 SHA1_Final(bin_buf, &Ctx);
1920 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1928 main(int argc, const char** argv)
1930 Result_t result = RESULT_OK;
1932 CommandOptions Options(argc, argv);
1934 if ( Options.version_flag )
1937 if ( Options.help_flag )
1940 if ( Options.version_flag || Options.help_flag )
1943 if ( Options.error_flag )
1945 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1949 if ( Options.mode == MMT_INFO )
1951 result = show_file_info(Options);
1953 for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1955 Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1956 result = show_file_info(Options);
1959 else if ( Options.mode == MMT_GOP_START )
1961 result = gop_start_test(Options);
1963 else if ( Options.mode == MMT_GEN_KEY )
1965 Kumu::FortunaRNG RNG;
1966 byte_t bin_buf[KeyLen];
1968 RNG.FillRandom(bin_buf, KeyLen);
1969 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1971 else if ( Options.mode == MMT_GEN_ID )
1974 Kumu::GenRandomValue(TmpID);
1975 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1977 else if ( Options.mode == MMT_DIGEST )
1979 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1980 result = digest_file(Options.filenames[i]);
1982 else if ( Options.mode == MMT_UL_LIST )
1984 if ( Options.use_smpte_labels )
1985 DefaultSMPTEDict().Dump(stdout);
1987 DefaultInteropDict().Dump(stdout);
1989 else if ( Options.mode == MMT_EXTRACT )
1991 EssenceType_t EssenceType;
1992 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1994 if ( ASDCP_SUCCESS(result) )
1996 switch ( EssenceType )
1999 result = read_MPEG2_file(Options);
2003 if ( Options.stereo_image_flag )
2004 result = read_JP2K_S_file(Options);
2006 result = read_JP2K_file(Options);
2009 case ESS_JPEG_2000_S:
2010 result = read_JP2K_S_file(Options);
2013 case ESS_PCM_24b_48k:
2014 case ESS_PCM_24b_96k:
2015 result = read_PCM_file(Options);
2018 case ESS_TIMED_TEXT:
2019 result = read_timed_text_file(Options);
2023 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
2028 else if ( Options.mode == MMT_CREATE )
2030 if ( Options.do_repeat && ! Options.duration_flag )
2032 fputs("Option -R requires -d <duration>\n", stderr);
2036 EssenceType_t EssenceType;
2037 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
2039 if ( ASDCP_SUCCESS(result) )
2041 switch ( EssenceType )
2044 result = write_MPEG2_file(Options);
2048 if ( Options.stereo_image_flag )
2049 result = write_JP2K_S_file(Options);
2052 result = write_JP2K_file(Options);
2056 case ESS_PCM_24b_48k:
2057 case ESS_PCM_24b_96k:
2058 result = write_PCM_file(Options);
2061 case ESS_TIMED_TEXT:
2062 result = write_timed_text_file(Options);
2066 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2067 Options.filenames[0]);
2074 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2078 if ( ASDCP_FAILURE(result) )
2080 fputs("Program stopped on error.\n", stderr);
2082 if ( result == RESULT_SFORMAT )
2084 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2086 else if ( result != RESULT_FAIL )
2088 fputs(result, stderr);
2089 fputc('\n', stderr);
2100 // end asdcp-test.cpp