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