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