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