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