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