4a1a58633b9f89d575bc410179279498806366b5
[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> [-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-file2> ...]\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   -c <output-file>  - Create AS-DCP track file from input(s)\n\
149   -g                - Generate a random 16 byte value to stdout\n\
150   -G                - Perform GOP start lookup test on MXF+Interop MPEG file\n\
151   -h | -help        - Show help\n\
152   -i                - Show file info\n\
153   -t                - Calculate message digest of input file\n\
154   -u                - Generate a random UUID value to stdout\n\
155   -V                - Show version information\n\
156   -x <root-name>    - Extract essence from AS-DCP file to named file(s)\n\
157 \n");
158
159   fprintf(stream, "\
160 Security Options:\n\
161   -e                - Encrypt MPEG or JP2K headers (default)\n\
162   -E                - Do not encrypt MPEG or JP2K headers\n\
163   -j <key-id-str>   - Write key ID instead of creating a random value\n\
164   -k <key-string>   - Use key for ciphertext operations\n\
165   -m                - verify HMAC values when reading\n\
166   -M                - Do not create HMAC values when writing\n\
167 \n");
168
169   fprintf(stream, "\
170 Read/Write Options:\n\
171   -b <buffer-size>  - Specify size in bytes of picture frame buffer.\n\
172                       Defaults to 4,194,304 (4MB)\n\
173   -d <duration>     - Number of frames to process, default all\n\
174   -f <start-frame>  - Starting frame number, default 0\n\
175   -L                - Write SMPTE UL values instead of MXF Interop\n\
176   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
177                       Use one of [23|24|48], 24 is default\n\
178   -R                - Repeat the first frame over the entire file (picture\n\
179                       essence only, requires -c, -d)\n\
180   -S                - Split Wave essence to stereo WAV files during extract.\n\
181                       Default is multichannel WAV\n\
182   -1                - Split Wave essence to mono WAV files during extract.\n\
183                       Default is multichannel WAV\n\
184   -W                - Read input file only, do not write source file\n\
185 \n");
186
187   fprintf(stream, "\
188 Info Options:\n\
189   -H                - Show MXF header metadata, used with option -i\n\
190   -n                - Show index, used with option -i\n\
191 \n\
192 Other Options:\n\
193   -s <num>          - Number of bytes of frame buffer to be dumped as hex to\n\
194                       stderr, used with option -v\n\
195   -v                - Verbose, prints informative messages to stderr\n\
196 \n\
197   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
198          o All option arguments must be separated from the option by whitespace.\n\
199          o An argument of \"23\" to the -p option will be interpreted\n\
200            as 23000/1001 fps.\n\
201 \n");
202 }
203
204 //
205 enum MajorMode_t
206 {
207   MMT_NONE,
208   MMT_INFO,
209   MMT_CREATE,
210   MMT_EXTRACT,
211   MMT_GEN_ID,
212   MMT_GEN_KEY,
213   MMT_GOP_START,
214   MMT_DIGEST
215 };
216
217
218 //
219 //
220 class CommandOptions
221 {
222   CommandOptions();
223
224 public:
225   MajorMode_t mode;
226   bool   error_flag;     // true if the given options are in error or not complete
227   bool   key_flag;       // true if an encryption key was given
228   bool   key_id_flag;    // true if a key ID was given
229   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
230   bool   write_hmac;     // true if HMAC values are to be generated and written
231   bool   read_hmac;      // true if HMAC values are to be validated
232   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
233   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
234   bool   verbose_flag;   // true if the verbose option was selected
235   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
236   bool   showindex_flag; // true if index is to be displayed
237   bool   showheader_flag; // true if MXF file header is to be displayed
238   bool   no_write_flag;  // true if no output files are to be written
239   bool   version_flag;   // true if the version display option was selected
240   bool   help_flag;      // true if the help display option was selected
241   ui32_t start_frame;    // frame number to begin processing
242   ui32_t duration;       // number of frames to be processed
243   bool   duration_flag;  // true if duration argument given
244   bool   do_repeat;      // if true and -c -d, repeat first input frame
245   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
246   ui32_t picture_rate;   // fps of picture when wrapping PCM
247   ui32_t fb_size;        // size of picture frame buffer
248   ui32_t file_count;     // number of elements in filenames[]
249   const char* file_root; // filename pre for files written by the extract mode
250   const char* out_file;  // name of mxf file created by create mode
251   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
252   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
253   const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
254
255   //
256   Rational PictureRate()
257   {
258     if ( picture_rate == 23 ) return EditRate_23_98;
259     if ( picture_rate == 48 ) return EditRate_48;
260     return EditRate_24;
261   }
262
263   //
264   const char* szPictureRate()
265   {
266     if ( picture_rate == 23 ) return "23.976";
267     if ( picture_rate == 48 ) return "48";
268     return "24";
269   }
270
271   //
272   CommandOptions(int argc, const char** argv) :
273     mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
274     write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
275     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
276     no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
277     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
278     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
279   {
280     memset(key_value, 0, KeyLen);
281     memset(key_id_value, 0, UUIDlen);
282
283     for ( int i = 1; i < argc; i++ )
284       {
285
286         if ( (strcmp( argv[i], "-help") == 0) )
287           {
288             help_flag = true;
289             continue;
290           }
291          
292         if ( argv[i][0] == '-'
293              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
294              && argv[i][2] == 0 )
295           {
296             switch ( argv[i][1] )
297               {
298               case '1': mono_wav = true; break;
299               case '2': split_wav = 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 codestreams to a plaintext ASDCP file
691 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
692 //
693 Result_t
694 write_JP2K_file(CommandOptions& Options)
695 {
696   AESEncContext*          Context = 0;
697   HMACContext*            HMAC = 0;
698   JP2K::MXFWriter         Writer;
699   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
700   JP2K::PictureDescriptor PDesc;
701   JP2K::SequenceParser    Parser;
702   byte_t                  IV_buf[CBC_BLOCK_SIZE];
703   Kumu::FortunaRNG        RNG;
704
705   // set up essence parser
706   Result_t result = Parser.OpenRead(Options.filenames[0]);
707
708   // set up MXF writer
709   if ( ASDCP_SUCCESS(result) )
710     {
711       Parser.FillPictureDescriptor(PDesc);
712       PDesc.EditRate = Options.PictureRate();
713
714       if ( Options.verbose_flag )
715         {
716           fprintf(stderr, "JPEG 2000 pictures\n");
717           fputs("PictureDescriptor:\n", stderr);
718           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
719           JP2K::PictureDescriptorDump(PDesc);
720         }
721     }
722
723   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
724     {
725       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
726       Kumu::GenRandomUUID(Info.AssetUUID);
727
728       if ( Options.use_smpte_labels )
729         {
730           Info.LabelSetType = LS_MXF_SMPTE;
731           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
732         }
733
734       // configure encryption
735       if( Options.key_flag )
736         {
737           Kumu::GenRandomUUID(Info.ContextID);
738           Info.EncryptedEssence = true;
739
740           if ( Options.key_id_flag )
741             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
742           else
743             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
744
745           Context = new AESEncContext;
746           result = Context->InitKey(Options.key_value);
747
748           if ( ASDCP_SUCCESS(result) )
749             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
750
751           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
752             {
753               Info.UsesHMAC = true;
754               HMAC = new HMACContext;
755               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
756             }
757         }
758
759       if ( ASDCP_SUCCESS(result) )
760         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
761     }
762
763   if ( ASDCP_SUCCESS(result) )
764     {
765       ui32_t duration = 0;
766       result = Parser.Reset();
767
768       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
769         {
770           if ( ! Options.do_repeat || duration == 1 )
771             {
772               result = Parser.ReadFrame(FrameBuffer);
773
774               if ( ASDCP_SUCCESS(result) )
775                 {
776                   if ( Options.verbose_flag )
777                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
778                   
779                   if ( Options.encrypt_header_flag )
780                     FrameBuffer.PlaintextOffset(0);
781                 }
782             }
783
784           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
785             {
786               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
787
788               // The Writer class will forward the last block of ciphertext
789               // to the encryption context for use as the IV for the next
790               // frame. If you want to use non-sequitur IV values, un-comment
791               // the following  line of code.
792               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
793               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
794             }
795         }
796
797       if ( result == RESULT_ENDOFFILE )
798         result = RESULT_OK;
799     }
800
801   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
802     result = Writer.Finalize();
803
804   return result;
805 }
806
807 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
808 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
809 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
810 //
811 Result_t
812 read_JP2K_file(CommandOptions& Options)
813 {
814   AESDecContext*     Context = 0;
815   HMACContext*       HMAC = 0;
816   JP2K::MXFReader    Reader;
817   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
818   ui32_t             frame_count = 0;
819
820   Result_t result = Reader.OpenRead(Options.filenames[0]);
821
822   if ( ASDCP_SUCCESS(result) )
823     {
824       JP2K::PictureDescriptor PDesc;
825       Reader.FillPictureDescriptor(PDesc);
826
827       frame_count = PDesc.ContainerDuration;
828
829       if ( Options.verbose_flag )
830         {
831           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
832           JP2K::PictureDescriptorDump(PDesc);
833         }
834     }
835
836   if ( ASDCP_SUCCESS(result) && Options.key_flag )
837     {
838       Context = new AESDecContext;
839       result = Context->InitKey(Options.key_value);
840
841       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
842         {
843           WriterInfo Info;
844           Reader.FillWriterInfo(Info);
845
846           if ( Info.UsesHMAC )
847             {
848               HMAC = new HMACContext;
849               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
850             }
851           else
852             {
853               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
854             }
855         }
856     }
857
858   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
859   if ( last_frame > frame_count )
860     last_frame = frame_count;
861
862   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
863     {
864       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
865
866       if ( ASDCP_SUCCESS(result) )
867         {
868           Kumu::FileWriter OutFile;
869           char filename[256];
870           ui32_t write_count;
871           snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
872           result = OutFile.OpenWrite(filename);
873
874           if ( ASDCP_SUCCESS(result) )
875             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
876
877           if ( Options.verbose_flag )
878             FrameBuffer.Dump(stderr, Options.fb_dump_size);
879         }
880     }
881
882   return result;
883 }
884
885 //------------------------------------------------------------------------------------------
886 // PCM essence
887
888
889 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
890 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
891 //
892 Result_t
893 write_PCM_file(CommandOptions& Options)
894 {
895   AESEncContext*    Context = 0;
896   HMACContext*      HMAC = 0;
897   PCMParserList     Parser;
898   PCM::MXFWriter    Writer;
899   PCM::FrameBuffer  FrameBuffer;
900   PCM::AudioDescriptor ADesc;
901   Rational          PictureRate = Options.PictureRate();
902   byte_t            IV_buf[CBC_BLOCK_SIZE];
903   Kumu::FortunaRNG  RNG;
904
905   // set up essence parser
906   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
907
908   // set up MXF writer
909   if ( ASDCP_SUCCESS(result) )
910     {
911       Parser.FillAudioDescriptor(ADesc);
912
913       ADesc.SampleRate = PictureRate;
914       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
915
916       if ( Options.verbose_flag )
917         {
918           fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
919                   Options.szPictureRate(),
920                   PCM::CalcSamplesPerFrame(ADesc));
921           fputs("AudioDescriptor:\n", stderr);
922           PCM::AudioDescriptorDump(ADesc);
923         }
924     }
925
926   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
927     {
928       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
929       Kumu::GenRandomUUID(Info.AssetUUID);
930
931       if ( Options.use_smpte_labels )
932         {
933           Info.LabelSetType = LS_MXF_SMPTE;
934           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
935         }
936
937       // configure encryption
938       if( Options.key_flag )
939         {
940           Kumu::GenRandomUUID(Info.ContextID);
941           Info.EncryptedEssence = true;
942
943           if ( Options.key_id_flag )
944             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
945           else
946             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
947
948           Context = new AESEncContext;
949           result = Context->InitKey(Options.key_value);
950
951           if ( ASDCP_SUCCESS(result) )
952             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
953
954           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
955             {
956               Info.UsesHMAC = true;
957               HMAC = new HMACContext;
958               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
959             }
960         }
961
962       if ( ASDCP_SUCCESS(result) )
963         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
964     }
965
966   if ( ASDCP_SUCCESS(result) )
967     {
968       result = Parser.Reset();
969       ui32_t duration = 0;
970
971       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
972         {
973           result = Parser.ReadFrame(FrameBuffer);
974
975           if ( ASDCP_SUCCESS(result) )
976             {
977               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
978                 {
979                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
980                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
981                   result = RESULT_ENDOFFILE;
982                   continue;
983                 }
984
985               if ( Options.verbose_flag )
986                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
987
988               if ( ! Options.no_write_flag )
989                 {
990                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
991
992                   // The Writer class will forward the last block of ciphertext
993                   // to the encryption context for use as the IV for the next
994                   // frame. If you want to use non-sequitur IV values, un-comment
995                   // the following  line of code.
996                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
997                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
998                 }
999             }
1000         }
1001
1002       if ( result == RESULT_ENDOFFILE )
1003         result = RESULT_OK;
1004     }
1005
1006   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1007     result = Writer.Finalize();
1008
1009   return result;
1010 }
1011
1012 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1013 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1014 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1015 //
1016 Result_t
1017 read_PCM_file(CommandOptions& Options)
1018 {
1019   AESDecContext*     Context = 0;
1020   HMACContext*       HMAC = 0;
1021   PCM::MXFReader     Reader;
1022   PCM::FrameBuffer   FrameBuffer;
1023   WavFileWriter      OutWave;
1024   PCM::AudioDescriptor ADesc;
1025   ui32_t last_frame = 0;
1026
1027   Result_t result = Reader.OpenRead(Options.filenames[0]);
1028
1029   if ( ASDCP_SUCCESS(result) )
1030     {
1031       Reader.FillAudioDescriptor(ADesc);
1032
1033       if ( ADesc.SampleRate != EditRate_23_98
1034            && ADesc.SampleRate != EditRate_24
1035            && ADesc.SampleRate != EditRate_48 )
1036         ADesc.SampleRate = Options.PictureRate();
1037
1038       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1039
1040       if ( Options.verbose_flag )
1041         PCM::AudioDescriptorDump(ADesc);
1042     }
1043
1044   if ( ASDCP_SUCCESS(result) )
1045     {
1046       last_frame = ADesc.ContainerDuration;
1047
1048       if ( Options.duration > 0 && Options.duration < last_frame )
1049         last_frame = Options.duration;
1050
1051       if ( Options.start_frame > 0 )
1052         {
1053           if ( Options.start_frame > ADesc.ContainerDuration )
1054             {
1055               fprintf(stderr, "Start value greater than file duration.\n");
1056               return RESULT_FAIL;
1057             }
1058
1059           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1060         }
1061
1062       ADesc.ContainerDuration = last_frame - Options.start_frame;
1063       OutWave.OpenWrite(ADesc, Options.file_root,
1064                         ( Options.split_wav ? WavFileWriter::ST_STEREO : 
1065                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1066     }
1067
1068   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1069     {
1070       Context = new AESDecContext;
1071       result = Context->InitKey(Options.key_value);
1072
1073       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1074         {
1075           WriterInfo Info;
1076           Reader.FillWriterInfo(Info);
1077
1078           if ( Info.UsesHMAC )
1079             {
1080               HMAC = new HMACContext;
1081               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1082             }
1083           else
1084             {
1085               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1086             }
1087         }
1088     }
1089
1090   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1091     {
1092       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1093
1094       if ( ASDCP_SUCCESS(result) )
1095         {
1096           if ( Options.verbose_flag )
1097             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1098
1099           result = OutWave.WriteFrame(FrameBuffer);
1100         }
1101     }
1102
1103   return result;
1104 }
1105
1106
1107 #ifdef ASDCP_WITH_TIMED_TEXT
1108
1109 //------------------------------------------------------------------------------------------
1110 // TimedText essence
1111
1112
1113 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1114 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1115 //
1116 Result_t
1117 write_timed_text_file(CommandOptions& Options)
1118 {
1119   AESEncContext*    Context = 0;
1120   HMACContext*      HMAC = 0;
1121   TimedText::DCSubtitleParser  Parser;
1122   TimedText::MXFWriter    Writer;
1123   TimedText::FrameBuffer  FrameBuffer;
1124   TimedText::TimedTextDescriptor TDesc;
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.filenames[0]);
1130
1131   // set up MXF writer
1132   if ( ASDCP_SUCCESS(result) )
1133     {
1134       Parser.FillDescriptor(TDesc);
1135       FrameBuffer.Capacity(2*Kumu::Megabyte);
1136
1137       if ( Options.verbose_flag )
1138         {
1139           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1140           TimedText::DescriptorDump(TDesc);
1141         }
1142     }
1143
1144   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1145     {
1146       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1147       Kumu::GenRandomUUID(Info.AssetUUID);
1148
1149       if ( Options.use_smpte_labels )
1150         {
1151           Info.LabelSetType = LS_MXF_SMPTE;
1152           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1153         }
1154
1155       // configure encryption
1156       if( Options.key_flag )
1157         {
1158           Kumu::GenRandomUUID(Info.ContextID);
1159           Info.EncryptedEssence = true;
1160
1161           if ( Options.key_id_flag )
1162             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1163           else
1164             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1165
1166           Context = new AESEncContext;
1167           result = Context->InitKey(Options.key_value);
1168
1169           if ( ASDCP_SUCCESS(result) )
1170             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1171
1172           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1173             {
1174               Info.UsesHMAC = true;
1175               HMAC = new HMACContext;
1176               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1177             }
1178         }
1179
1180       if ( ASDCP_SUCCESS(result) )
1181         result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1182     }
1183
1184   if ( ASDCP_FAILURE(result) )
1185     return result;
1186
1187   std::string XMLDoc;
1188   TimedText::ResourceList_t::const_iterator ri;
1189
1190   result = Parser.ReadTimedTextResource(XMLDoc);
1191
1192   if ( ASDCP_SUCCESS(result) )
1193     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1194
1195   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1196     {
1197       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1198
1199       if ( ASDCP_SUCCESS(result) )
1200         {
1201           if ( Options.verbose_flag )
1202             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1203
1204           if ( ! Options.no_write_flag )
1205             {
1206               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1207               
1208               // The Writer class will forward the last block of ciphertext
1209               // to the encryption context for use as the IV for the next
1210               // frame. If you want to use non-sequitur IV values, un-comment
1211               // the following  line of code.
1212               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1213               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1214             }
1215         }
1216
1217       if ( result == RESULT_ENDOFFILE )
1218         result = RESULT_OK;
1219     }
1220
1221   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1222     result = Writer.Finalize();
1223
1224   return result;
1225 }
1226
1227
1228 // Read one or more timed text streams from a plaintext ASDCP file
1229 // Read one or more timed text streams from a ciphertext ASDCP file
1230 // Read one or more timed text streams from a ciphertext ASDCP file
1231 //
1232 Result_t
1233 read_timed_text_file(CommandOptions& Options)
1234 {
1235   AESDecContext*     Context = 0;
1236   HMACContext*       HMAC = 0;
1237   TimedText::MXFReader     Reader;
1238   TimedText::FrameBuffer   FrameBuffer;
1239   TimedText::TimedTextDescriptor TDesc;
1240
1241   Result_t result = Reader.OpenRead(Options.filenames[0]);
1242
1243   if ( ASDCP_SUCCESS(result) )
1244     {
1245       Reader.FillDescriptor(TDesc);
1246       FrameBuffer.Capacity(2*Kumu::Megabyte);
1247
1248       if ( Options.verbose_flag )
1249         TimedText::DescriptorDump(TDesc);
1250     }
1251
1252   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1253     {
1254       Context = new AESDecContext;
1255       result = Context->InitKey(Options.key_value);
1256
1257       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1258         {
1259           WriterInfo Info;
1260           Reader.FillWriterInfo(Info);
1261
1262           if ( Info.UsesHMAC )
1263             {
1264               HMAC = new HMACContext;
1265               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1266             }
1267           else
1268             {
1269               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1270             }
1271         }
1272     }
1273
1274   if ( ASDCP_FAILURE(result) )
1275     return result;
1276
1277   std::string XMLDoc;
1278   TimedText::ResourceList_t::const_iterator ri;
1279
1280   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1281
1282   // do something with the XML here
1283   fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1284
1285   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1286     {
1287       result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1288
1289       if ( ASDCP_SUCCESS(result) )
1290         {
1291           //      if ( Options.verbose_flag )
1292             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1293
1294           // do something with the resource data here
1295         }
1296     }
1297
1298   return result;
1299 }
1300 #endif // ASDCP_WITH_TIMED_TEXT
1301
1302 //------------------------------------------------------------------------------------------
1303 //
1304
1305 //
1306 // These classes wrap the irregular names in the asdcplib API
1307 // so that I can use a template to simplify the implementation
1308 // of show_file_info()
1309
1310 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1311 {
1312  public:
1313   void FillDescriptor(MPEG2::MXFReader& Reader) {
1314     Reader.FillVideoDescriptor(*this);
1315   }
1316
1317   void Dump(FILE* stream) {
1318     MPEG2::VideoDescriptorDump(*this, stream);
1319   }
1320 };
1321
1322 class MyPictureDescriptor : public JP2K::PictureDescriptor
1323 {
1324  public:
1325   void FillDescriptor(JP2K::MXFReader& Reader) {
1326     Reader.FillPictureDescriptor(*this);
1327   }
1328
1329   void Dump(FILE* stream) {
1330     JP2K::PictureDescriptorDump(*this, stream);
1331   }
1332 };
1333
1334 class MyAudioDescriptor : public PCM::AudioDescriptor
1335 {
1336  public:
1337   void FillDescriptor(PCM::MXFReader& Reader) {
1338     Reader.FillAudioDescriptor(*this);
1339   }
1340
1341   void Dump(FILE* stream) {
1342     PCM::AudioDescriptorDump(*this, stream);
1343   }
1344 };
1345
1346 #ifdef ASDCP_WITH_TIMED_TEXT
1347 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1348 {
1349  public:
1350   void FillDescriptor(TimedText::MXFReader& Reader) {
1351     Reader.FillDescriptor(*this);
1352   }
1353
1354   void Dump(FILE* stream) {
1355     TimedText::DescriptorDump(*this, stream);
1356   }
1357 };
1358 #endif
1359
1360 // MSVC didn't like the function template, so now it's a static class method
1361 template<class ReaderT, class DescriptorT>
1362 class FileInfoWrapper
1363 {
1364 public:
1365   static void file_info(CommandOptions& Options, FILE* stream = 0)
1366   {
1367     if ( stream == 0 )
1368       stream = stdout;
1369
1370     if ( Options.verbose_flag || Options.showheader_flag )
1371       {
1372         ReaderT     Reader;
1373         Result_t result = Reader.OpenRead(Options.filenames[0]);
1374
1375         if ( ASDCP_SUCCESS(result) )
1376           {
1377             if ( Options.showheader_flag )
1378               Reader.DumpHeaderMetadata(stream);
1379
1380             WriterInfo WI;
1381             Reader.FillWriterInfo(WI);
1382             WriterInfoDump(WI, stream);
1383
1384             DescriptorT Desc;
1385             Desc.FillDescriptor(Reader);
1386             Desc.Dump(stream);
1387
1388             if ( Options.showindex_flag )
1389               Reader.DumpIndex(stream);
1390           }
1391         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1392           {
1393             Reader.DumpHeaderMetadata(stream);
1394           }
1395       }
1396   }
1397 };
1398
1399 // Read header metadata from an ASDCP file
1400 //
1401 Result_t
1402 show_file_info(CommandOptions& Options)
1403 {
1404   EssenceType_t EssenceType;
1405   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1406
1407   if ( ASDCP_FAILURE(result) )
1408     return result;
1409
1410   if ( EssenceType == ESS_MPEG2_VES )
1411     {
1412       fputs("File essence type is MPEG2 video.\n", stdout);
1413       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1414     }
1415   else if ( EssenceType == ESS_PCM_24b_48k )
1416     {
1417       fputs("File essence type is PCM audio.\n", stdout);
1418       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1419     }
1420   else if ( EssenceType == ESS_JPEG_2000 )
1421     {
1422       fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1423       FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1424     }
1425 #ifdef ASDCP_WITH_TIMED_TEXT
1426   else if ( EssenceType == ESS_TIMED_TEXT )
1427     {
1428       fputs("File essence type is Timed Text.\n", stdout);
1429       FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options);
1430     }
1431 #endif
1432   else
1433     {
1434       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1435       Kumu::FileReader   Reader;
1436       MXF::OPAtomHeader TestHeader;
1437
1438       result = Reader.OpenRead(Options.filenames[0]);
1439
1440       if ( ASDCP_SUCCESS(result) )
1441         result = TestHeader.InitFromFile(Reader); // test UL and OP
1442
1443       if ( ASDCP_SUCCESS(result) )
1444         {
1445           TestHeader.Partition::Dump();
1446
1447           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1448             ID->Dump();
1449           else
1450             fputs("File contains no Identification object.\n", stdout);
1451
1452           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1453             SP->Dump();
1454           else
1455             fputs("File contains no SourcePackage object.\n", stdout);
1456         }
1457       else
1458         {
1459           fputs("File is not MXF.\n", stdout);
1460         }
1461     }
1462
1463   return result;
1464 }
1465
1466
1467 //
1468 Result_t
1469 digest_file(const char* filename)
1470 {
1471   using namespace Kumu;
1472
1473   ASDCP_TEST_NULL_STR(filename);
1474   FileReader Reader;
1475   SHA_CTX Ctx;
1476   SHA1_Init(&Ctx);
1477   ByteString Buf(8192);
1478
1479   Result_t result = Reader.OpenRead(filename);
1480
1481   while ( ASDCP_SUCCESS(result) )
1482     {
1483       ui32_t read_count = 0;
1484       result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1485
1486       if ( result == RESULT_ENDOFFILE )
1487         {
1488           result = RESULT_OK;
1489           break;
1490         }
1491
1492       if ( ASDCP_SUCCESS(result) )
1493         SHA1_Update(&Ctx, Buf.Data(), read_count);
1494     }
1495
1496   if ( ASDCP_SUCCESS(result) )
1497     {
1498       const ui32_t sha_len = 20;
1499       byte_t bin_buf[sha_len];
1500       char sha_buf[64];
1501       SHA1_Final(bin_buf, &Ctx);
1502
1503       fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1504     }
1505
1506   return result;
1507 }
1508
1509 //
1510 int
1511 main(int argc, const char** argv)
1512 {
1513   Result_t result = RESULT_OK;
1514   CommandOptions Options(argc, argv);
1515
1516   if ( Options.version_flag )
1517     banner();
1518
1519   if ( Options.help_flag )
1520     usage();
1521
1522   if ( Options.version_flag || Options.help_flag )
1523     return 0;
1524
1525   if ( Options.error_flag )
1526     {
1527       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1528       return 3;
1529     }
1530
1531   if ( Options.mode == MMT_INFO )
1532     {
1533       result = show_file_info(Options);
1534     }
1535   else if ( Options.mode == MMT_GOP_START )
1536     {
1537       result = gop_start_test(Options);
1538     }
1539   else if ( Options.mode == MMT_GEN_KEY )
1540     {
1541       Kumu::FortunaRNG RNG;
1542       byte_t bin_buf[KeyLen];
1543       char   str_buf[40];
1544
1545       RNG.FillRandom(bin_buf, KeyLen);
1546       printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1547     }
1548   else if ( Options.mode == MMT_GEN_ID )
1549     {
1550       UUID TmpID;
1551       Kumu::GenRandomValue(TmpID);
1552       char   str_buf[40];
1553       printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1554     }
1555   else if ( Options.mode == MMT_DIGEST )
1556     {
1557       for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1558         result = digest_file(Options.filenames[i]);
1559     }
1560   else if ( Options.mode == MMT_EXTRACT )
1561     {
1562       EssenceType_t EssenceType;
1563       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1564
1565       if ( ASDCP_SUCCESS(result) )
1566         {
1567           switch ( EssenceType )
1568             {
1569             case ESS_MPEG2_VES:
1570               result = read_MPEG2_file(Options);
1571               break;
1572
1573             case ESS_JPEG_2000:
1574               result = read_JP2K_file(Options);
1575               break;
1576
1577             case ESS_PCM_24b_48k:
1578               result = read_PCM_file(Options);
1579               break;
1580
1581             case ESS_TIMED_TEXT:
1582 #ifdef ASDCP_WITH_TIMED_TEXT
1583               result = read_timed_text_file(Options);
1584               break;
1585 #else
1586               fprintf(stderr, "asdcplib compiled without timed text support.\n");
1587               return 7;
1588 #endif
1589             default:
1590               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1591               return 5;
1592             }
1593         }
1594     }
1595   else if ( Options.mode == MMT_CREATE )
1596     {
1597       if ( Options.do_repeat && ! Options.duration_flag )
1598         {
1599           fputs("Option -R requires -d <duration>\n", stderr);
1600           return RESULT_FAIL;
1601         }
1602
1603       EssenceType_t EssenceType;
1604       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1605
1606       if ( ASDCP_SUCCESS(result) )
1607         {
1608           switch ( EssenceType )
1609             {
1610             case ESS_MPEG2_VES:
1611               result = write_MPEG2_file(Options);
1612               break;
1613
1614             case ESS_JPEG_2000:
1615               result = write_JP2K_file(Options);
1616               break;
1617
1618             case ESS_PCM_24b_48k:
1619               result = write_PCM_file(Options);
1620               break;
1621
1622             case ESS_TIMED_TEXT:
1623 #ifdef ASDCP_WITH_TIMED_TEXT
1624               result = write_timed_text_file(Options);
1625               break;
1626 #else
1627               fprintf(stderr, "asdcplib compiled without timed text support.\n");
1628               return 7;
1629 #endif
1630
1631             default:
1632               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1633                       Options.filenames[0]);
1634               return 5;
1635             }
1636         }
1637     }
1638
1639   if ( ASDCP_FAILURE(result) )
1640     {
1641       fputs("Program stopped on error.\n", stderr);
1642
1643       if ( result != RESULT_FAIL )
1644         {
1645           fputs(result, stderr);
1646           fputc('\n', stderr);
1647         }
1648
1649       return 1;
1650     }
1651
1652   return 0;
1653 }
1654
1655
1656 //
1657 // end asdcp-test.cpp
1658 //