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