high frame rate stereo
[asdcplib.git] / src / asdcp-test.cpp
1 /*
2 Copyright (c) 2003-2011, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
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.
15
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.
26 */
27 /*! \file    asdcp-test.cpp
28     \version $Id$       
29     \brief   AS-DCP file manipulation utility
30
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.
34
35   For more information about asdcplib, please refer to the header file AS_DCP.h
36
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.
41
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
47   these features.
48 */
49
50 #include <KM_fileio.h>
51 #include <KM_prng.h>
52 #include <PCMParserList.h>
53 #include <WavFileWriter.h>
54 #include <MXF.h>
55 #include <Metadata.h>
56 #include <openssl/sha.h>
57
58 #include <iostream>
59 #include <assert.h>
60
61 using namespace ASDCP;
62
63 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
64
65 //------------------------------------------------------------------------------------------
66 //
67 // command line option parser class
68
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
72
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
75 {
76 public:
77   MyInfo()
78   {
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 };
82       
83       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84       CompanyName = "WidgetCo";
85       ProductName = "asdcp-test";
86       ProductVersion = ASDCP::Version();
87   }
88 } s_MyInfo;
89
90
91
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] == '-' ) \
96                                  { \
97                                    fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
98                                    return; \
99                                  }
100 //
101 void
102 banner(FILE* stream = stdout)
103 {
104   fprintf(stream, "\n\
105 %s (asdcplib %s)\n\n\
106 Copyright (c) 2003-2011 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);
111 }
112
113 //
114 void
115 usage(FILE* stream = stdout)
116 {
117   fprintf(stream, "\
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\
122 \n\
123        %s [-h|-help] [-V]\n\
124 \n\
125        %s -i [-H] [-n] [-v] <input-file>\n\
126 \n\
127        %s -g | -u\n\
128 \n\
129        %s -G [-v] <input-file>\n\
130 \n\
131        %s -t <input-file>\n\
132 \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);
137
138   fprintf(stream, "\
139 Major modes:\n\
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\
144                       track file.\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\
155 \n");
156
157   fprintf(stream, "\
158 Security Options:\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\
165 \n");
166
167   fprintf(stream, "\
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', '7.1DS', 'WTF'.\n\
176                       Default 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|25|30|48|50|60], 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\
191 \n");
192
193   fprintf(stream, "\
194 Info Options:\n\
195   -H                - Show MXF header metadata, used with option -i\n\
196   -n                - Show index, used with option -i\n\
197 \n\
198 Other Options:\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\
202 \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\
207 \n");
208 }
209
210 //
211 enum MajorMode_t
212 {
213   MMT_NONE,
214   MMT_INFO,
215   MMT_CREATE,
216   MMT_EXTRACT,
217   MMT_GEN_ID,
218   MMT_GEN_KEY,
219   MMT_GOP_START,
220   MMT_DIGEST,
221   MMT_UL_LIST,
222 };
223
224 //
225 PCM::ChannelFormat_t
226 decode_channel_fmt(const std::string& label_name)
227 {
228   if ( label_name == "5.1" )
229     return PCM::CF_CFG_1;
230
231   else if ( label_name == "6.1" )
232     return PCM::CF_CFG_2;
233   
234   else if ( label_name == "7.1" )
235     return PCM::CF_CFG_3;
236
237   else if ( label_name == "WTF" )
238     return PCM::CF_CFG_4;
239
240   else if ( label_name == "7.1DS" )
241     return PCM::CF_CFG_5;
242
243   fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
244   fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
245   return PCM::CF_NONE;
246 }
247
248 //
249 //
250 class CommandOptions
251 {
252   CommandOptions();
253
254 public:
255   MajorMode_t mode;
256   bool   error_flag;     // true if the given options are in error or not complete
257   bool   key_flag;       // true if an encryption key was given
258   bool   key_id_flag;    // true if a key ID was given
259   bool   asset_id_flag;  // true if an asset ID was given
260   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
261   bool   write_hmac;     // true if HMAC values are to be generated and written
262   bool   read_hmac;      // true if HMAC values are to be validated
263   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
264   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
265   bool   verbose_flag;   // true if the verbose option was selected
266   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
267   bool   showindex_flag; // true if index is to be displayed
268   bool   showheader_flag; // true if MXF file header is to be displayed
269   bool   no_write_flag;  // true if no output files are to be written
270   bool   version_flag;   // true if the version display option was selected
271   bool   help_flag;      // true if the help display option was selected
272   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
273   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
274   ui32_t start_frame;    // frame number to begin processing
275   ui32_t duration;       // number of frames to be processed
276   bool   duration_flag;  // true if duration argument given
277   bool   do_repeat;      // if true and -c -d, repeat first input frame
278   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
279   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
280   ui32_t picture_rate;   // fps of picture when wrapping PCM
281   ui32_t fb_size;        // size of picture frame buffer
282   ui32_t file_count;     // number of elements in filenames[]
283   const char* file_root; // filename pre for files written by the extract mode
284   const char* out_file;  // name of mxf file created by create mode
285   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
286   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
287   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
288   const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
289   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
290
291   //
292   Rational PictureRate()
293   {
294     if ( picture_rate == 23 ) return EditRate_23_98;
295     if ( picture_rate == 24 ) return EditRate_24;
296     if ( picture_rate == 25 ) return EditRate_25;
297     if ( picture_rate == 30 ) return EditRate_30;
298     if ( picture_rate == 48 ) return EditRate_48;
299     if ( picture_rate == 50 ) return EditRate_50;
300     if ( picture_rate == 60 ) return EditRate_60;
301     if ( picture_rate == 96 ) return EditRate_96;
302     if ( picture_rate == 100 ) return EditRate_100;
303     if ( picture_rate == 120 ) return EditRate_120;
304     return EditRate_24;
305   }
306
307   //
308   const char* szPictureRate()
309   {
310     if ( picture_rate == 23 ) return "23.976";
311     if ( picture_rate == 24 ) return "24";
312     if ( picture_rate == 25 ) return "25";
313     if ( picture_rate == 30 ) return "30";
314     if ( picture_rate == 48 ) return "48";
315     if ( picture_rate == 50 ) return "50";
316     if ( picture_rate == 60 ) return "60";
317     if ( picture_rate == 96 ) return "96";
318     if ( picture_rate == 100 ) return "100";
319     if ( picture_rate == 120 ) return "120";
320     return "24";
321   }
322
323   //
324   CommandOptions(int argc, const char** argv) :
325     mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
326     encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
327     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
328     no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
329     number_width(6), start_frame(0),
330     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
331     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
332     channel_fmt(PCM::CF_NONE)
333   {
334     memset(key_value, 0, KeyLen);
335     memset(key_id_value, 0, UUIDlen);
336
337     for ( int i = 1; i < argc; i++ )
338       {
339
340         if ( (strcmp( argv[i], "-help") == 0) )
341           {
342             help_flag = true;
343             continue;
344           }
345          
346         if ( argv[i][0] == '-'
347              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
348              && argv[i][2] == 0 )
349           {
350             switch ( argv[i][1] )
351               {
352               case '1': mono_wav = true; break;
353               case '2': split_wav = true; break;
354               case '3': stereo_image_flag = true; break;
355
356               case 'a':
357                 asset_id_flag = true;
358                 TEST_EXTRA_ARG(i, 'a');
359                 {
360                   ui32_t length;
361                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
362
363                   if ( length != UUIDlen )
364                     {
365                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
366                       return;
367                     }
368                 }
369                 break;
370
371               case 'b':
372                 TEST_EXTRA_ARG(i, 'b');
373                 fb_size = abs(atoi(argv[i]));
374
375                 if ( verbose_flag )
376                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
377
378                 break;
379
380               case 'c':
381                 TEST_EXTRA_ARG(i, 'c');
382                 mode = MMT_CREATE;
383                 out_file = argv[i];
384                 break;
385
386               case 'd':
387                 TEST_EXTRA_ARG(i, 'd');
388                 duration_flag = true;
389                 duration = abs(atoi(argv[i]));
390                 break;
391
392               case 'E': encrypt_header_flag = false; break;
393               case 'e': encrypt_header_flag = true; break;
394
395               case 'f':
396                 TEST_EXTRA_ARG(i, 'f');
397                 start_frame = abs(atoi(argv[i]));
398                 break;
399
400               case 'G': mode = MMT_GOP_START; break;
401               case 'g': mode = MMT_GEN_KEY; break;
402               case 'H': showheader_flag = true; break;
403               case 'h': help_flag = true; break;
404               case 'i': mode = MMT_INFO;        break;
405
406               case 'j': key_id_flag = true;
407                 TEST_EXTRA_ARG(i, 'j');
408                 {
409                   ui32_t length;
410                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
411
412                   if ( length != UUIDlen )
413                     {
414                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
415                       return;
416                     }
417                 }
418                 break;
419
420               case 'k': key_flag = true;
421                 TEST_EXTRA_ARG(i, 'k');
422                 {
423                   ui32_t length;
424                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
425
426                   if ( length != KeyLen )
427                     {
428                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
429                       return;
430                     }
431                 }
432                 break;
433
434               case 'l':
435                 TEST_EXTRA_ARG(i, 'l');
436                 channel_fmt = decode_channel_fmt(argv[i]);
437                 break;
438
439               case 'L': use_smpte_labels = true; break;
440               case 'M': write_hmac = false; break;
441               case 'm': read_hmac = true; break;
442               case 'n': showindex_flag = true; break;
443
444               case 'p':
445                 TEST_EXTRA_ARG(i, 'p');
446                 picture_rate = abs(atoi(argv[i]));
447                 break;
448
449               case 'R': do_repeat = true; break;
450               case 'S': split_wav = true; break;
451
452               case 's':
453                 TEST_EXTRA_ARG(i, 's');
454                 fb_dump_size = abs(atoi(argv[i]));
455                 break;
456
457               case 't': mode = MMT_DIGEST; break;
458               case 'U': mode = MMT_UL_LIST; break;
459               case 'u': mode = MMT_GEN_ID; break;
460               case 'V': version_flag = true; break;
461               case 'v': verbose_flag = true; break;
462               case 'W': no_write_flag = true; break;
463
464               case 'w':
465                 TEST_EXTRA_ARG(i, 'w');
466                 number_width = abs(atoi(argv[i]));
467                 break;
468
469               case 'x':
470                 TEST_EXTRA_ARG(i, 'x');
471                 mode = MMT_EXTRACT;
472                 file_root = argv[i];
473                 break;
474
475               case 'Z': j2c_pedantic = false; break;
476               case 'z': j2c_pedantic = true; break;
477
478               default:
479                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
480                 return;
481               }
482           }
483         else
484           {
485
486             if ( argv[i][0] != '-' )
487               {
488                 filenames[file_count++] = argv[i];
489               }
490             else
491               {
492                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
493                 return;
494               }
495
496             if ( file_count >= MAX_IN_FILES )
497               {
498                 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
499                 return;
500               }
501           }
502       }
503
504     if ( help_flag || version_flag )
505       return;
506     
507     if ( ( mode == MMT_INFO
508            || mode == MMT_CREATE
509            || mode == MMT_EXTRACT
510            || mode == MMT_GOP_START
511            || mode == MMT_DIGEST ) && file_count == 0 )
512       {
513         fputs("Option requires at least one filename argument.\n", stderr);
514         return;
515       }
516
517     if ( mode == MMT_NONE && ! help_flag && ! version_flag )
518       {
519         fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
520         return;
521       }
522
523     error_flag = false;
524   }
525 };
526
527 //------------------------------------------------------------------------------------------
528 // MPEG2 essence
529
530 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
531 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
532 //
533 Result_t
534 write_MPEG2_file(CommandOptions& Options)
535 {
536   AESEncContext*     Context = 0;
537   HMACContext*       HMAC = 0;
538   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
539   MPEG2::Parser      Parser;
540   MPEG2::MXFWriter   Writer;
541   MPEG2::VideoDescriptor VDesc;
542   byte_t             IV_buf[CBC_BLOCK_SIZE];
543   Kumu::FortunaRNG   RNG;
544
545   // set up essence parser
546   Result_t result = Parser.OpenRead(Options.filenames[0]);
547
548   // set up MXF writer
549   if ( ASDCP_SUCCESS(result) )
550     {
551       Parser.FillVideoDescriptor(VDesc);
552
553       if ( Options.verbose_flag )
554         {
555           fputs("MPEG-2 Pictures\n", stderr);
556           fputs("VideoDescriptor:\n", stderr);
557           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
558           MPEG2::VideoDescriptorDump(VDesc);
559         }
560     }
561
562   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
563     {
564       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
565       if ( Options.asset_id_flag )
566         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
567       else
568         Kumu::GenRandomUUID(Info.AssetUUID);
569
570       if ( Options.use_smpte_labels )
571         {
572           Info.LabelSetType = LS_MXF_SMPTE;
573           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
574         }
575
576       // configure encryption
577       if( Options.key_flag )
578         {
579           Kumu::GenRandomUUID(Info.ContextID);
580           Info.EncryptedEssence = true;
581
582           if ( Options.key_id_flag )
583             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
584           else
585             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
586
587           Context = new AESEncContext;
588           result = Context->InitKey(Options.key_value);
589
590           if ( ASDCP_SUCCESS(result) )
591             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
592
593           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
594             {
595               Info.UsesHMAC = true;
596               HMAC = new HMACContext;
597               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
598             }
599         }
600
601       if ( ASDCP_SUCCESS(result) )
602         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
603     }
604
605   if ( ASDCP_SUCCESS(result) )
606     // loop through the frames
607     {
608       result = Parser.Reset();
609       ui32_t duration = 0;
610
611       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
612         {
613           if ( ! Options.do_repeat || duration == 1 )
614             {
615               result = Parser.ReadFrame(FrameBuffer);
616
617               if ( ASDCP_SUCCESS(result) )
618                 {
619                   if ( Options.verbose_flag )
620                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
621                   
622                   if ( Options.encrypt_header_flag )
623                     FrameBuffer.PlaintextOffset(0);
624                 }
625             }
626
627           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
628             {
629               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
630
631               // The Writer class will forward the last block of ciphertext
632               // to the encryption context for use as the IV for the next
633               // frame. If you want to use non-sequitur IV values, un-comment
634               // the following  line of code.
635               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
636               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
637             }
638         }
639
640       if ( result == RESULT_ENDOFFILE )
641         result = RESULT_OK;
642     }
643
644   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
645     result = Writer.Finalize();
646
647   return result;
648 }
649
650 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
651 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
652 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
653 //
654 Result_t
655 read_MPEG2_file(CommandOptions& Options)
656 {
657   AESDecContext*     Context = 0;
658   HMACContext*       HMAC = 0;
659   MPEG2::MXFReader   Reader;
660   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
661   Kumu::FileWriter   OutFile;
662   ui32_t             frame_count = 0;
663
664   Result_t result = Reader.OpenRead(Options.filenames[0]);
665
666   if ( ASDCP_SUCCESS(result) )
667     {
668       MPEG2::VideoDescriptor VDesc;
669       Reader.FillVideoDescriptor(VDesc);
670       frame_count = VDesc.ContainerDuration;
671
672       if ( Options.verbose_flag )
673         {
674           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
675           MPEG2::VideoDescriptorDump(VDesc);
676         }
677     }
678
679   if ( ASDCP_SUCCESS(result) )
680     {
681       char filename[256];
682       snprintf(filename, 256, "%s.ves", Options.file_root);
683       result = OutFile.OpenWrite(filename);
684     }
685
686   if ( ASDCP_SUCCESS(result) && Options.key_flag )
687     {
688       Context = new AESDecContext;
689       result = Context->InitKey(Options.key_value);
690
691       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
692         {
693           WriterInfo Info;
694           Reader.FillWriterInfo(Info);
695
696           if ( Info.UsesHMAC )
697             {
698               HMAC = new HMACContext;
699               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
700             }
701           else
702             {
703               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
704             }
705         }
706     }
707
708   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
709   if ( last_frame > frame_count )
710     last_frame = frame_count;
711
712   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
713     {
714       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
715
716       if ( ASDCP_SUCCESS(result) )
717         {
718           if ( Options.verbose_flag )
719             FrameBuffer.Dump(stderr, Options.fb_dump_size);
720
721           ui32_t write_count = 0;
722           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
723         }
724     }
725
726   return result;
727 }
728
729
730 //
731 Result_t
732 gop_start_test(CommandOptions& Options)
733 {
734   using namespace ASDCP::MPEG2;
735
736   MXFReader   Reader;
737   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
738   ui32_t      frame_count = 0;
739
740   Result_t result = Reader.OpenRead(Options.filenames[0]);
741
742   if ( ASDCP_SUCCESS(result) )
743     {
744       MPEG2::VideoDescriptor VDesc;
745       Reader.FillVideoDescriptor(VDesc);
746       frame_count = VDesc.ContainerDuration;
747
748       if ( Options.verbose_flag )
749         {
750           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
751           MPEG2::VideoDescriptorDump(VDesc);
752         }
753     }
754
755   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
756   if ( last_frame > frame_count )
757     last_frame = frame_count;
758
759   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
760     {
761       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
762
763       if ( ASDCP_SUCCESS(result) )
764         {
765           if ( Options.verbose_flag )
766             FrameBuffer.Dump(stderr, Options.fb_dump_size);
767
768           if ( FrameBuffer.FrameType() != FRAME_I )
769             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
770
771           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
772         }
773     }
774
775   return result;
776 }
777
778 //------------------------------------------------------------------------------------------
779 // JPEG 2000 essence
780
781 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
782 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
783 //
784 Result_t
785 write_JP2K_S_file(CommandOptions& Options)
786 {
787   AESEncContext*          Context = 0;
788   HMACContext*            HMAC = 0;
789   JP2K::MXFSWriter        Writer;
790   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
791   JP2K::PictureDescriptor PDesc;
792   JP2K::SequenceParser    ParserLeft, ParserRight;
793   byte_t                  IV_buf[CBC_BLOCK_SIZE];
794   Kumu::FortunaRNG        RNG;
795
796   if ( Options.file_count != 2 )
797     {
798       fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
799       return RESULT_FAIL;
800     }
801
802   // set up essence parser
803   Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
804
805   if ( ASDCP_SUCCESS(result) )
806     result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
807
808   // set up MXF writer
809   if ( ASDCP_SUCCESS(result) )
810     {
811       ParserLeft.FillPictureDescriptor(PDesc);
812       PDesc.EditRate = Options.PictureRate();
813
814       if ( Options.verbose_flag )
815         {
816           fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
817           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
818           JP2K::PictureDescriptorDump(PDesc);
819         }
820     }
821
822   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
823     {
824       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
825       if ( Options.asset_id_flag )
826         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
827       else
828         Kumu::GenRandomUUID(Info.AssetUUID);
829
830       if ( Options.use_smpte_labels )
831         {
832           Info.LabelSetType = LS_MXF_SMPTE;
833           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
834         }
835
836       // configure encryption
837       if( Options.key_flag )
838         {
839           Kumu::GenRandomUUID(Info.ContextID);
840           Info.EncryptedEssence = true;
841
842           if ( Options.key_id_flag )
843             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
844           else
845             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
846
847           Context = new AESEncContext;
848           result = Context->InitKey(Options.key_value);
849
850           if ( ASDCP_SUCCESS(result) )
851             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
852
853           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
854             {
855               Info.UsesHMAC = true;
856               HMAC = new HMACContext;
857               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
858             }
859         }
860
861       if ( ASDCP_SUCCESS(result) )
862         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
863     }
864
865   if ( ASDCP_SUCCESS(result) )
866     {
867       ui32_t duration = 0;
868       result = ParserLeft.Reset();
869       if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
870
871       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
872         {
873           result = ParserLeft.ReadFrame(FrameBuffer);
874
875           if ( ASDCP_SUCCESS(result) )
876             {
877               if ( Options.verbose_flag )
878                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
879                   
880               if ( Options.encrypt_header_flag )
881                 FrameBuffer.PlaintextOffset(0);
882             }
883
884           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
885             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
886
887           if ( ASDCP_SUCCESS(result) )
888             result = ParserRight.ReadFrame(FrameBuffer);
889
890           if ( ASDCP_SUCCESS(result) )
891             {
892               if ( Options.verbose_flag )
893                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
894                   
895               if ( Options.encrypt_header_flag )
896                 FrameBuffer.PlaintextOffset(0);
897             }
898
899           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
900             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
901         }
902
903       if ( result == RESULT_ENDOFFILE )
904         result = RESULT_OK;
905     }
906
907   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
908     result = Writer.Finalize();
909
910   return result;
911 }
912
913 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
914 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
915 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
916 Result_t
917 read_JP2K_S_file(CommandOptions& Options)
918 {
919   AESDecContext*     Context = 0;
920   HMACContext*       HMAC = 0;
921   JP2K::MXFSReader    Reader;
922   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
923   ui32_t             frame_count = 0;
924
925   Result_t result = Reader.OpenRead(Options.filenames[0]);
926
927   if ( ASDCP_SUCCESS(result) )
928     {
929       JP2K::PictureDescriptor PDesc;
930       Reader.FillPictureDescriptor(PDesc);
931
932       frame_count = PDesc.ContainerDuration;
933
934       if ( Options.verbose_flag )
935         {
936           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
937           JP2K::PictureDescriptorDump(PDesc);
938         }
939     }
940
941   if ( ASDCP_SUCCESS(result) && Options.key_flag )
942     {
943       Context = new AESDecContext;
944       result = Context->InitKey(Options.key_value);
945
946       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
947         {
948           WriterInfo Info;
949           Reader.FillWriterInfo(Info);
950
951           if ( Info.UsesHMAC )
952             {
953               HMAC = new HMACContext;
954               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
955             }
956           else
957             {
958               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
959             }
960         }
961     }
962
963   const int filename_max = 1024;
964   char filename[filename_max];
965   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
966   if ( last_frame > frame_count )
967     last_frame = frame_count;
968
969   char left_format[64];  char right_format[64];
970   snprintf(left_format,  64, "%%s%%0%duL.j2c", Options.number_width);
971   snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
972
973   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
974     {
975       result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
976
977       if ( ASDCP_SUCCESS(result) )
978         {
979           Kumu::FileWriter OutFile;
980           ui32_t write_count;
981           snprintf(filename, filename_max, left_format, Options.file_root, i);
982           result = OutFile.OpenWrite(filename);
983
984           if ( ASDCP_SUCCESS(result) )
985             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
986
987           if ( Options.verbose_flag )
988             FrameBuffer.Dump(stderr, Options.fb_dump_size);
989         }
990
991       if ( ASDCP_SUCCESS(result) )
992         result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
993
994       if ( ASDCP_SUCCESS(result) )
995         {
996           Kumu::FileWriter OutFile;
997           ui32_t write_count;
998           snprintf(filename, filename_max, right_format, Options.file_root, i);
999           result = OutFile.OpenWrite(filename);
1000
1001           if ( ASDCP_SUCCESS(result) )
1002             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1003         }
1004     }
1005
1006   return result;
1007 }
1008
1009
1010
1011 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1012 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1013 //
1014 Result_t
1015 write_JP2K_file(CommandOptions& Options)
1016 {
1017   AESEncContext*          Context = 0;
1018   HMACContext*            HMAC = 0;
1019   JP2K::MXFWriter         Writer;
1020   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
1021   JP2K::PictureDescriptor PDesc;
1022   JP2K::SequenceParser    Parser;
1023   byte_t                  IV_buf[CBC_BLOCK_SIZE];
1024   Kumu::FortunaRNG        RNG;
1025
1026   // set up essence parser
1027   Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1028
1029   // set up MXF writer
1030   if ( ASDCP_SUCCESS(result) )
1031     {
1032       Parser.FillPictureDescriptor(PDesc);
1033       PDesc.EditRate = Options.PictureRate();
1034
1035       if ( Options.verbose_flag )
1036         {
1037           fprintf(stderr, "JPEG 2000 pictures\n");
1038           fputs("PictureDescriptor:\n", stderr);
1039           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1040           JP2K::PictureDescriptorDump(PDesc);
1041         }
1042     }
1043
1044   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1045     {
1046       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1047       if ( Options.asset_id_flag )
1048         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1049       else
1050         Kumu::GenRandomUUID(Info.AssetUUID);
1051
1052       if ( Options.use_smpte_labels )
1053         {
1054           Info.LabelSetType = LS_MXF_SMPTE;
1055           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1056         }
1057
1058       // configure encryption
1059       if( Options.key_flag )
1060         {
1061           Kumu::GenRandomUUID(Info.ContextID);
1062           Info.EncryptedEssence = true;
1063
1064           if ( Options.key_id_flag )
1065             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1066           else
1067             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1068
1069           Context = new AESEncContext;
1070           result = Context->InitKey(Options.key_value);
1071
1072           if ( ASDCP_SUCCESS(result) )
1073             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1074
1075           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1076             {
1077               Info.UsesHMAC = true;
1078               HMAC = new HMACContext;
1079               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1080             }
1081         }
1082
1083       if ( ASDCP_SUCCESS(result) )
1084         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1085     }
1086
1087   if ( ASDCP_SUCCESS(result) )
1088     {
1089       ui32_t duration = 0;
1090       result = Parser.Reset();
1091
1092       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1093         {
1094           if ( ! Options.do_repeat || duration == 1 )
1095             {
1096               result = Parser.ReadFrame(FrameBuffer);
1097
1098               if ( ASDCP_SUCCESS(result) )
1099                 {
1100                   if ( Options.verbose_flag )
1101                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
1102                   
1103                   if ( Options.encrypt_header_flag )
1104                     FrameBuffer.PlaintextOffset(0);
1105                 }
1106             }
1107
1108           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1109             {
1110               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1111
1112               // The Writer class will forward the last block of ciphertext
1113               // to the encryption context for use as the IV for the next
1114               // frame. If you want to use non-sequitur IV values, un-comment
1115               // the following  line of code.
1116               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1117               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1118             }
1119         }
1120
1121       if ( result == RESULT_ENDOFFILE )
1122         result = RESULT_OK;
1123     }
1124
1125   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1126     result = Writer.Finalize();
1127
1128   return result;
1129 }
1130
1131 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1132 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1133 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1134 //
1135 Result_t
1136 read_JP2K_file(CommandOptions& Options)
1137 {
1138   AESDecContext*     Context = 0;
1139   HMACContext*       HMAC = 0;
1140   JP2K::MXFReader    Reader;
1141   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
1142   ui32_t             frame_count = 0;
1143
1144   Result_t result = Reader.OpenRead(Options.filenames[0]);
1145
1146   if ( ASDCP_SUCCESS(result) )
1147     {
1148       JP2K::PictureDescriptor PDesc;
1149       Reader.FillPictureDescriptor(PDesc);
1150
1151       frame_count = PDesc.ContainerDuration;
1152
1153       if ( Options.verbose_flag )
1154         {
1155           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1156           JP2K::PictureDescriptorDump(PDesc);
1157         }
1158     }
1159
1160   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1161     {
1162       Context = new AESDecContext;
1163       result = Context->InitKey(Options.key_value);
1164
1165       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1166         {
1167           WriterInfo Info;
1168           Reader.FillWriterInfo(Info);
1169
1170           if ( Info.UsesHMAC )
1171             {
1172               HMAC = new HMACContext;
1173               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1174             }
1175           else
1176             {
1177               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1178             }
1179         }
1180     }
1181
1182   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1183   if ( last_frame > frame_count )
1184     last_frame = frame_count;
1185
1186   char name_format[64];
1187   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
1188
1189   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1190     {
1191       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1192
1193       if ( ASDCP_SUCCESS(result) )
1194         {
1195           Kumu::FileWriter OutFile;
1196           char filename[256];
1197           ui32_t write_count;
1198           snprintf(filename, 256, name_format, Options.file_root, i);
1199           result = OutFile.OpenWrite(filename);
1200
1201           if ( ASDCP_SUCCESS(result) )
1202             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1203
1204           if ( Options.verbose_flag )
1205             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1206         }
1207     }
1208
1209   return result;
1210 }
1211
1212 //------------------------------------------------------------------------------------------
1213 // PCM essence
1214
1215
1216 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1217 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1218 //
1219 Result_t
1220 write_PCM_file(CommandOptions& Options)
1221 {
1222   AESEncContext*    Context = 0;
1223   HMACContext*      HMAC = 0;
1224   PCMParserList     Parser;
1225   PCM::MXFWriter    Writer;
1226   PCM::FrameBuffer  FrameBuffer;
1227   PCM::AudioDescriptor ADesc;
1228   Rational          PictureRate = Options.PictureRate();
1229   byte_t            IV_buf[CBC_BLOCK_SIZE];
1230   Kumu::FortunaRNG  RNG;
1231
1232   // set up essence parser
1233   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1234
1235   // set up MXF writer
1236   if ( ASDCP_SUCCESS(result) )
1237     {
1238       Parser.FillAudioDescriptor(ADesc);
1239
1240       ADesc.EditRate = PictureRate;
1241       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1242       ADesc.ChannelFormat = Options.channel_fmt;
1243
1244       if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1245         {
1246           fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1247         }
1248
1249       if ( Options.verbose_flag )
1250         {
1251           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1252                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
1253                   Options.szPictureRate(),
1254                   PCM::CalcSamplesPerFrame(ADesc));
1255           fputs("AudioDescriptor:\n", stderr);
1256           PCM::AudioDescriptorDump(ADesc);
1257         }
1258     }
1259
1260   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1261     {
1262       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1263       if ( Options.asset_id_flag )
1264         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1265       else
1266         Kumu::GenRandomUUID(Info.AssetUUID);
1267
1268       if ( Options.use_smpte_labels )
1269         {
1270           Info.LabelSetType = LS_MXF_SMPTE;
1271           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1272         }
1273
1274       // configure encryption
1275       if( Options.key_flag )
1276         {
1277           Kumu::GenRandomUUID(Info.ContextID);
1278           Info.EncryptedEssence = true;
1279
1280           if ( Options.key_id_flag )
1281             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1282           else
1283             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1284
1285           Context = new AESEncContext;
1286           result = Context->InitKey(Options.key_value);
1287
1288           if ( ASDCP_SUCCESS(result) )
1289             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1290
1291           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1292             {
1293               Info.UsesHMAC = true;
1294               HMAC = new HMACContext;
1295               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1296             }
1297         }
1298
1299       if ( ASDCP_SUCCESS(result) )
1300         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1301     }
1302
1303   if ( ASDCP_SUCCESS(result) )
1304     {
1305       result = Parser.Reset();
1306       ui32_t duration = 0;
1307
1308       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1309         {
1310           result = Parser.ReadFrame(FrameBuffer);
1311
1312           if ( ASDCP_SUCCESS(result) )
1313             {
1314               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1315                 {
1316                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1317                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1318                   result = RESULT_ENDOFFILE;
1319                   continue;
1320                 }
1321
1322               if ( Options.verbose_flag )
1323                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1324
1325               if ( ! Options.no_write_flag )
1326                 {
1327                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1328
1329                   // The Writer class will forward the last block of ciphertext
1330                   // to the encryption context for use as the IV for the next
1331                   // frame. If you want to use non-sequitur IV values, un-comment
1332                   // the following  line of code.
1333                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1334                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1335                 }
1336             }
1337         }
1338
1339       if ( result == RESULT_ENDOFFILE )
1340         result = RESULT_OK;
1341     }
1342
1343   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1344     result = Writer.Finalize();
1345
1346   return result;
1347 }
1348
1349 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1350 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1351 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1352 //
1353 Result_t
1354 read_PCM_file(CommandOptions& Options)
1355 {
1356   AESDecContext*     Context = 0;
1357   HMACContext*       HMAC = 0;
1358   PCM::MXFReader     Reader;
1359   PCM::FrameBuffer   FrameBuffer;
1360   WavFileWriter      OutWave;
1361   PCM::AudioDescriptor ADesc;
1362   ui32_t last_frame = 0;
1363
1364   Result_t result = Reader.OpenRead(Options.filenames[0]);
1365
1366   if ( ASDCP_SUCCESS(result) )
1367     {
1368       Reader.FillAudioDescriptor(ADesc);
1369
1370       if ( ADesc.EditRate != EditRate_23_98
1371            && ADesc.EditRate != EditRate_24
1372            && ADesc.EditRate != EditRate_25
1373            && ADesc.EditRate != EditRate_30
1374            && ADesc.EditRate != EditRate_48
1375            && ADesc.EditRate != EditRate_50
1376            && ADesc.EditRate != EditRate_60 )
1377         ADesc.EditRate = Options.PictureRate();
1378
1379       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1380
1381       if ( Options.verbose_flag )
1382         PCM::AudioDescriptorDump(ADesc);
1383     }
1384
1385   if ( ASDCP_SUCCESS(result) )
1386     {
1387       last_frame = ADesc.ContainerDuration;
1388
1389       if ( Options.duration > 0 && Options.duration < last_frame )
1390         last_frame = Options.duration;
1391
1392       if ( Options.start_frame > 0 )
1393         {
1394           if ( Options.start_frame > ADesc.ContainerDuration )
1395             {
1396               fprintf(stderr, "Start value greater than file duration.\n");
1397               return RESULT_FAIL;
1398             }
1399
1400           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1401         }
1402
1403       ADesc.ContainerDuration = last_frame - Options.start_frame;
1404       OutWave.OpenWrite(ADesc, Options.file_root,
1405                         ( Options.split_wav ? WavFileWriter::ST_STEREO : 
1406                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1407     }
1408
1409   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1410     {
1411       Context = new AESDecContext;
1412       result = Context->InitKey(Options.key_value);
1413
1414       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1415         {
1416           WriterInfo Info;
1417           Reader.FillWriterInfo(Info);
1418
1419           if ( Info.UsesHMAC )
1420             {
1421               HMAC = new HMACContext;
1422               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1423             }
1424           else
1425             {
1426               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1427             }
1428         }
1429     }
1430
1431   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1432     {
1433       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1434
1435       if ( ASDCP_SUCCESS(result) )
1436         {
1437           if ( Options.verbose_flag )
1438             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1439
1440           result = OutWave.WriteFrame(FrameBuffer);
1441         }
1442     }
1443
1444   return result;
1445 }
1446
1447
1448 //------------------------------------------------------------------------------------------
1449 // TimedText essence
1450
1451
1452 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1453 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1454 //
1455 Result_t
1456 write_timed_text_file(CommandOptions& Options)
1457 {
1458   AESEncContext*    Context = 0;
1459   HMACContext*      HMAC = 0;
1460   TimedText::DCSubtitleParser  Parser;
1461   TimedText::MXFWriter    Writer;
1462   TimedText::FrameBuffer  FrameBuffer;
1463   TimedText::TimedTextDescriptor TDesc;
1464   byte_t            IV_buf[CBC_BLOCK_SIZE];
1465   Kumu::FortunaRNG  RNG;
1466
1467   // set up essence parser
1468   Result_t result = Parser.OpenRead(Options.filenames[0]);
1469
1470   // set up MXF writer
1471   if ( ASDCP_SUCCESS(result) )
1472     {
1473       Parser.FillTimedTextDescriptor(TDesc);
1474       FrameBuffer.Capacity(Options.fb_size);
1475
1476       if ( Options.verbose_flag )
1477         {
1478           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1479           TimedText::DescriptorDump(TDesc);
1480         }
1481     }
1482
1483   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1484     {
1485       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1486       if ( Options.asset_id_flag )
1487         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1488       else
1489         Kumu::GenRandomUUID(Info.AssetUUID);
1490
1491       if ( Options.use_smpte_labels )
1492         {
1493           Info.LabelSetType = LS_MXF_SMPTE;
1494           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1495         }
1496
1497       // configure encryption
1498       if( Options.key_flag )
1499         {
1500           Kumu::GenRandomUUID(Info.ContextID);
1501           Info.EncryptedEssence = true;
1502
1503           if ( Options.key_id_flag )
1504             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1505           else
1506             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1507
1508           Context = new AESEncContext;
1509           result = Context->InitKey(Options.key_value);
1510
1511           if ( ASDCP_SUCCESS(result) )
1512             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1513
1514           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1515             {
1516               Info.UsesHMAC = true;
1517               HMAC = new HMACContext;
1518               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1519             }
1520         }
1521
1522       if ( ASDCP_SUCCESS(result) )
1523         result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1524     }
1525
1526   if ( ASDCP_FAILURE(result) )
1527     return result;
1528
1529   std::string XMLDoc;
1530   TimedText::ResourceList_t::const_iterator ri;
1531
1532   result = Parser.ReadTimedTextResource(XMLDoc);
1533
1534   if ( ASDCP_SUCCESS(result) )
1535     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1536
1537   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1538     {
1539       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1540
1541       if ( ASDCP_SUCCESS(result) )
1542         {
1543           if ( Options.verbose_flag )
1544             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1545
1546           if ( ! Options.no_write_flag )
1547             {
1548               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1549               
1550               // The Writer class will forward the last block of ciphertext
1551               // to the encryption context for use as the IV for the next
1552               // frame. If you want to use non-sequitur IV values, un-comment
1553               // the following  line of code.
1554               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1555               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1556             }
1557         }
1558
1559       if ( result == RESULT_ENDOFFILE )
1560         result = RESULT_OK;
1561     }
1562
1563   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1564     result = Writer.Finalize();
1565
1566   return result;
1567 }
1568
1569
1570 // Read one or more timed text streams from a plaintext ASDCP file
1571 // Read one or more timed text streams from a ciphertext ASDCP file
1572 // Read one or more timed text streams from a ciphertext ASDCP file
1573 //
1574 Result_t
1575 read_timed_text_file(CommandOptions& Options)
1576 {
1577   AESDecContext*     Context = 0;
1578   HMACContext*       HMAC = 0;
1579   TimedText::MXFReader     Reader;
1580   TimedText::FrameBuffer   FrameBuffer;
1581   TimedText::TimedTextDescriptor TDesc;
1582
1583   Result_t result = Reader.OpenRead(Options.filenames[0]);
1584
1585   if ( ASDCP_SUCCESS(result) )
1586     {
1587       Reader.FillTimedTextDescriptor(TDesc);
1588       FrameBuffer.Capacity(Options.fb_size);
1589
1590       if ( Options.verbose_flag )
1591         TimedText::DescriptorDump(TDesc);
1592     }
1593
1594   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1595     {
1596       Context = new AESDecContext;
1597       result = Context->InitKey(Options.key_value);
1598
1599       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1600         {
1601           WriterInfo Info;
1602           Reader.FillWriterInfo(Info);
1603
1604           if ( Info.UsesHMAC )
1605             {
1606               HMAC = new HMACContext;
1607               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1608             }
1609           else
1610             {
1611               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1612             }
1613         }
1614     }
1615
1616   if ( ASDCP_FAILURE(result) )
1617     return result;
1618
1619   std::string XMLDoc;
1620   std::string out_path = Kumu::PathDirname(Options.file_root);
1621   ui32_t write_count;
1622   char buf[64];
1623   TimedText::ResourceList_t::const_iterator ri;
1624
1625   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1626
1627   if ( ASDCP_SUCCESS(result) )
1628     {
1629       Kumu::FileWriter Writer;
1630       result = Writer.OpenWrite(Options.file_root);
1631
1632       if ( ASDCP_SUCCESS(result) )
1633         result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1634     }
1635
1636   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1637     {
1638       result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1639
1640       if ( ASDCP_SUCCESS(result) )
1641         {
1642           Kumu::FileWriter Writer;
1643           result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1644
1645           if ( ASDCP_SUCCESS(result) )
1646             {
1647               if ( Options.verbose_flag )
1648                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1649
1650               result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1651             }
1652         }
1653     }
1654
1655   return result;
1656 }
1657
1658 //------------------------------------------------------------------------------------------
1659 //
1660
1661 //
1662 // These classes wrap the irregular names in the asdcplib API
1663 // so that I can use a template to simplify the implementation
1664 // of show_file_info()
1665
1666 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1667 {
1668  public:
1669   void FillDescriptor(MPEG2::MXFReader& Reader) {
1670     Reader.FillVideoDescriptor(*this);
1671   }
1672
1673   void Dump(FILE* stream) {
1674     MPEG2::VideoDescriptorDump(*this, stream);
1675   }
1676 };
1677
1678 class MyPictureDescriptor : public JP2K::PictureDescriptor
1679 {
1680  public:
1681   void FillDescriptor(JP2K::MXFReader& Reader) {
1682     Reader.FillPictureDescriptor(*this);
1683   }
1684
1685   void Dump(FILE* stream) {
1686     JP2K::PictureDescriptorDump(*this, stream);
1687   }
1688 };
1689
1690 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1691 {
1692  public:
1693   void FillDescriptor(JP2K::MXFSReader& Reader) {
1694     Reader.FillPictureDescriptor(*this);
1695   }
1696
1697   void Dump(FILE* stream) {
1698     JP2K::PictureDescriptorDump(*this, stream);
1699   }
1700 };
1701
1702 class MyAudioDescriptor : public PCM::AudioDescriptor
1703 {
1704  public:
1705   void FillDescriptor(PCM::MXFReader& Reader) {
1706     Reader.FillAudioDescriptor(*this);
1707   }
1708
1709   void Dump(FILE* stream) {
1710     PCM::AudioDescriptorDump(*this, stream);
1711   }
1712 };
1713
1714 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1715 {
1716  public:
1717   void FillDescriptor(TimedText::MXFReader& Reader) {
1718     Reader.FillTimedTextDescriptor(*this);
1719   }
1720
1721   void Dump(FILE* stream) {
1722     TimedText::DescriptorDump(*this, stream);
1723   }
1724 };
1725
1726 // MSVC didn't like the function template, so now it's a static class method
1727 template<class ReaderT, class DescriptorT>
1728 class FileInfoWrapper
1729 {
1730 public:
1731   static Result_t
1732   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1733   {
1734     assert(type_string);
1735     if ( stream == 0 )
1736       stream = stdout;
1737
1738     Result_t result = RESULT_OK;
1739
1740     if ( Options.verbose_flag || Options.showheader_flag )
1741       {
1742         ReaderT     Reader;
1743         result = Reader.OpenRead(Options.filenames[0]);
1744
1745         if ( ASDCP_SUCCESS(result) )
1746           {
1747             fprintf(stdout, "File essence type is %s.\n", type_string);
1748
1749             if ( Options.showheader_flag )
1750               Reader.DumpHeaderMetadata(stream);
1751
1752             WriterInfo WI;
1753             Reader.FillWriterInfo(WI);
1754             WriterInfoDump(WI, stream);
1755
1756             DescriptorT Desc;
1757             Desc.FillDescriptor(Reader);
1758             Desc.Dump(stream);
1759
1760             if ( Options.showindex_flag )
1761               Reader.DumpIndex(stream);
1762           }
1763         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1764           {
1765             Reader.DumpHeaderMetadata(stream);
1766           }
1767       }
1768
1769     return result;
1770   }
1771 };
1772
1773 // Read header metadata from an ASDCP file
1774 //
1775 Result_t
1776 show_file_info(CommandOptions& Options)
1777 {
1778   EssenceType_t EssenceType;
1779   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1780
1781   if ( ASDCP_FAILURE(result) )
1782     return result;
1783
1784   if ( EssenceType == ESS_MPEG2_VES )
1785     result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1786
1787   else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1788     result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1789
1790   else if ( EssenceType == ESS_JPEG_2000 )
1791     {
1792       if ( Options.stereo_image_flag )
1793         result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1794         MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1795
1796       else
1797         result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1798         MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1799     }
1800   else if ( EssenceType == ESS_JPEG_2000_S )
1801     result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1802     MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1803
1804   else if ( EssenceType == ESS_TIMED_TEXT )
1805     result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1806
1807   else
1808     {
1809       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1810       Kumu::FileReader   Reader;
1811       const Dictionary* Dict = &DefaultCompositeDict();
1812       MXF::OPAtomHeader TestHeader(Dict);
1813
1814       result = Reader.OpenRead(Options.filenames[0]);
1815
1816       if ( ASDCP_SUCCESS(result) )
1817         result = TestHeader.InitFromFile(Reader); // test UL and OP
1818
1819       if ( ASDCP_SUCCESS(result) )
1820         {
1821           TestHeader.Partition::Dump(stdout);
1822
1823           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1824             ID->Dump(stdout);
1825           else
1826             fputs("File contains no Identification object.\n", stdout);
1827
1828           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1829             SP->Dump(stdout);
1830           else
1831             fputs("File contains no SourcePackage object.\n", stdout);
1832         }
1833       else
1834         {
1835           fputs("File is not MXF.\n", stdout);
1836         }
1837     }
1838
1839   return result;
1840 }
1841
1842
1843 //
1844 Result_t
1845 digest_file(const char* filename)
1846 {
1847   using namespace Kumu;
1848
1849   ASDCP_TEST_NULL_STR(filename);
1850   FileReader Reader;
1851   SHA_CTX Ctx;
1852   SHA1_Init(&Ctx);
1853   ByteString Buf(8192);
1854
1855   Result_t result = Reader.OpenRead(filename);
1856
1857   while ( ASDCP_SUCCESS(result) )
1858     {
1859       ui32_t read_count = 0;
1860       result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1861
1862       if ( result == RESULT_ENDOFFILE )
1863         {
1864           result = RESULT_OK;
1865           break;
1866         }
1867
1868       if ( ASDCP_SUCCESS(result) )
1869         SHA1_Update(&Ctx, Buf.Data(), read_count);
1870     }
1871
1872   if ( ASDCP_SUCCESS(result) )
1873     {
1874       const ui32_t sha_len = 20;
1875       byte_t bin_buf[sha_len];
1876       char sha_buf[64];
1877       SHA1_Final(bin_buf, &Ctx);
1878
1879       fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1880     }
1881
1882   return result;
1883 }
1884
1885 //
1886 int
1887 main(int argc, const char** argv)
1888 {
1889   Result_t result = RESULT_OK;
1890   char     str_buf[64];
1891   CommandOptions Options(argc, argv);
1892
1893   if ( Options.version_flag )
1894     banner();
1895
1896   if ( Options.help_flag )
1897     usage();
1898
1899   if ( Options.version_flag || Options.help_flag )
1900     return 0;
1901
1902   if ( Options.error_flag )
1903     {
1904       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1905       return 3;
1906     }
1907
1908   if ( Options.mode == MMT_INFO )
1909     {
1910       result = show_file_info(Options);
1911
1912       for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1913         {
1914           Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1915           result = show_file_info(Options);
1916         }
1917     }
1918   else if ( Options.mode == MMT_GOP_START )
1919     {
1920       result = gop_start_test(Options);
1921     }
1922   else if ( Options.mode == MMT_GEN_KEY )
1923     {
1924       Kumu::FortunaRNG RNG;
1925       byte_t bin_buf[KeyLen];
1926
1927       RNG.FillRandom(bin_buf, KeyLen);
1928       printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1929     }
1930   else if ( Options.mode == MMT_GEN_ID )
1931     {
1932       UUID TmpID;
1933       Kumu::GenRandomValue(TmpID);
1934       printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1935     }
1936   else if ( Options.mode == MMT_DIGEST )
1937     {
1938       for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1939         result = digest_file(Options.filenames[i]);
1940     }
1941   else if ( Options.mode == MMT_UL_LIST )
1942     {
1943       if ( Options.use_smpte_labels )
1944         DefaultSMPTEDict().Dump(stdout);
1945       else
1946         DefaultInteropDict().Dump(stdout);
1947     }
1948   else if ( Options.mode == MMT_EXTRACT )
1949     {
1950       EssenceType_t EssenceType;
1951       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1952
1953       if ( ASDCP_SUCCESS(result) )
1954         {
1955           switch ( EssenceType )
1956             {
1957             case ESS_MPEG2_VES:
1958               result = read_MPEG2_file(Options);
1959               break;
1960
1961             case ESS_JPEG_2000:
1962               if ( Options.stereo_image_flag )
1963                 result = read_JP2K_S_file(Options);
1964               else
1965                 result = read_JP2K_file(Options);
1966               break;
1967
1968             case ESS_JPEG_2000_S:
1969               result = read_JP2K_S_file(Options);
1970               break;
1971
1972             case ESS_PCM_24b_48k:
1973             case ESS_PCM_24b_96k:
1974               result = read_PCM_file(Options);
1975               break;
1976
1977             case ESS_TIMED_TEXT:
1978               result = read_timed_text_file(Options);
1979               break;
1980
1981             default:
1982               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1983               return 5;
1984             }
1985         }
1986     }
1987   else if ( Options.mode == MMT_CREATE )
1988     {
1989       if ( Options.do_repeat && ! Options.duration_flag )
1990         {
1991           fputs("Option -R requires -d <duration>\n", stderr);
1992           return RESULT_FAIL;
1993         }
1994
1995       EssenceType_t EssenceType;
1996       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1997
1998       if ( ASDCP_SUCCESS(result) )
1999         {
2000           switch ( EssenceType )
2001             {
2002             case ESS_MPEG2_VES:
2003               result = write_MPEG2_file(Options);
2004               break;
2005
2006             case ESS_JPEG_2000:
2007               if ( Options.stereo_image_flag )
2008                 result = write_JP2K_S_file(Options);
2009
2010               else
2011                 result = write_JP2K_file(Options);
2012
2013               break;
2014
2015             case ESS_PCM_24b_48k:
2016             case ESS_PCM_24b_96k:
2017               result = write_PCM_file(Options);
2018               break;
2019
2020             case ESS_TIMED_TEXT:
2021               result = write_timed_text_file(Options);
2022               break;
2023
2024             default:
2025               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2026                       Options.filenames[0]);
2027               return 5;
2028             }
2029         }
2030     }
2031   else
2032     {
2033       fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2034       return 6;
2035     }
2036
2037   if ( ASDCP_FAILURE(result) )
2038     {
2039       fputs("Program stopped on error.\n", stderr);
2040
2041       if ( result == RESULT_SFORMAT )
2042         {
2043           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2044         }
2045       else if ( result != RESULT_FAIL )
2046         {
2047           fputs(result, stderr);
2048           fputc('\n', stderr);
2049         }
2050
2051       return 1;
2052     }
2053
2054   return 0;
2055 }
2056
2057
2058 //
2059 // end asdcp-test.cpp
2060 //