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