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