3d802e9bf8c0d3663f4b1feebf155689c0118659
[asdcplib.git] / src / asdcp-test.cpp
1 /*
2 Copyright (c) 2003-2006, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    asdcp-test.cpp
28     \version $Id$       
29     \brief   AS-DCP file manipulation utility
30
31   This program provides command line access to the major features of the asdcplib
32   library, and serves as a library unit test which provides the functionality of
33   the supported use cases.
34
35   For more information about asdcplib, please refer to the header file AS_DCP.h
36
37   WARNING: While the asdcplib library attempts to provide a complete and secure
38   implementation of the cryptographic features of the AS-DCP file formats, this
39   unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40   production environment without some modification.
41
42   In particular, this program uses weak IV generation and externally generated
43   plaintext keys. These shortcomings exist because cryptographic-quality
44   random number generation and key management are outside the scope of the
45   asdcplib library. Developers using asdcplib for commercial implementations
46   claiming SMPTE conformance are expected to provide proper implementations of
47   these features.
48 */
49
50 #include <iostream>
51 #include <assert.h>
52
53 #include <KM_fileio.h>
54 #include <KM_prng.h>
55 #include <PCMParserList.h>
56 #include <WavFileWriter.h>
57 #include <MXF.h>
58 #include <Metadata.h>
59 #include <openssl/sha.h>
60
61 using namespace ASDCP;
62
63 const ui32_t FRAME_BUFFER_SIZE = 4*1024*1024;
64
65 //------------------------------------------------------------------------------------------
66 //
67 // command line option parser class
68
69 static const char* PACKAGE = "asdcp-test";  // program name for messages
70 const ui32_t MAX_IN_FILES = 16;             // maximum number of input files handled by
71                                             //   the command option parser
72
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
75 {
76 public:
77   MyInfo()
78   {
79       static byte_t default_ProductUUID_Data[UUIDlen] =
80       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
82       
83       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84       CompanyName = "WidgetCo";
85       ProductName = "asdcp-test";
86
87       char s_buf[128];
88       snprintf(s_buf, 128, "%u.%u.%u", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89       ProductVersion = s_buf;
90   }
91 } s_MyInfo;
92
93
94
95 // Increment the iterator, test for an additional non-option command line argument.
96 // Causes the caller to return if there are no remaining arguments or if the next
97 // argument begins with '-'.
98 #define TEST_EXTRA_ARG(i,c)    if ( ++i >= argc || argv[(i)][0] == '-' ) \
99                                  { \
100                                    fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
101                                    return; \
102                                  }
103 //
104 void
105 banner(FILE* stream = stdout)
106 {
107   fprintf(stream, "\n\
108 %s (asdcplib %s)\n\n\
109 Copyright (c) 2003-2006 John Hurst\n\n\
110 asdcplib may be copied only under the terms of the license found at\n\
111 the top of every file in the asdcplib distribution kit.\n\n\
112 Specify the -h (help) option for further information about %s\n\n",
113           PACKAGE, ASDCP::Version(), PACKAGE);
114 }
115
116 //
117 void
118 usage(FILE* stream = stdout)
119 {
120   fprintf(stream, "\
121 USAGE: %s -c <output-file> [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
122        [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
123        [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
124        <input-file> [<input-file2> ...]\n\
125 \n\
126        %s [-h|-help] [-V]\n\
127 \n\
128        %s -i [-H] [-n] [-v] <input-file>\n\
129 \n\
130        %s -g | -u\n\
131 \n\
132        %s -G [-v] <input-file>\n\
133 \n\
134        %s -t <input-file>\n\
135 \n\
136        %s -x <file-prefix> [-b <buffer-size>] [-d <duration>]\n\
137        [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
138        [-v] [-W] <input-file>\n\
139 \n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
140
141   fprintf(stream, "\
142 Major modes:\n\
143   -c <output-file>  - Create AS-DCP track file from input(s)\n\
144   -g                - Generate a random 16 byte value to stdout\n\
145   -G                - Perform GOP start lookup test on MXF+Interop MPEG file\n\
146   -h | -help        - Show help\n\
147   -i                - Show file info\n\
148   -t                - Calculate message digest of input file\n\
149   -u                - Generate a random UUID value to stdout\n\
150   -V                - Show version information\n\
151   -x <root-name>    - Extract essence from AS-DCP file to named file(s)\n\
152 \n");
153
154   fprintf(stream, "\
155 Security Options:\n\
156   -e                - Encrypt MPEG or JP2K headers (default)\n\
157   -E                - Do not encrypt MPEG or JP2K headers\n\
158   -j <key-id-str>   - Write key ID instead of creating a random value\n\
159   -k <key-string>   - Use key for ciphertext operations\n\
160   -m                - verify HMAC values when reading\n\
161   -M                - Do not create HMAC values when writing\n\
162 \n");
163
164   fprintf(stream, "\
165 Read/Write Options:\n\
166   -b <buffer-size>  - Specify size in bytes of picture frame buffer.\n\
167                       Defaults to 4,194,304 (4MB)\n\
168   -d <duration>     - Number of frames to process, default all\n\
169   -f <start-frame>  - Starting frame number, default 0\n\
170   -L                - Write SMPTE UL values instead of MXF Interop\n\
171   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
172                       Use one of [23|24|48], 24 is default\n\
173   -R                - Repeat the first frame over the entire file (picture\n\
174                       essence only, requires -c, -d)\n\
175   -S                - Split Wave essence to stereo WAV files during extract.\n\
176                       Default is multichannel WAV\n\
177   -1                - Split Wave essence to mono WAV files during extract.\n\
178                       Default is multichannel WAV\n\
179   -W                - Read input file only, do not write source file\n\
180 \n");
181
182   fprintf(stream, "\
183 Info Options:\n\
184   -H                - Show MXF header metadata, used with option -i\n\
185   -n                - Show index, used with option -i\n\
186 \n\
187 Other Options:\n\
188   -s <num>          - Number of bytes of frame buffer to be dumped as hex to\n\
189                       stderr, used with option -v\n\
190   -v                - Verbose, prints informative messages to stderr\n\
191 \n\
192   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
193          o All option arguments must be separated from the option by whitespace.\n\
194          o An argument of \"23\" to the -p option will be interpreted\n\
195            as 23000/1001 fps.\n\
196 \n");
197 }
198
199 //
200 enum MajorMode_t
201 {
202   MMT_NONE,
203   MMT_INFO,
204   MMT_CREATE,
205   MMT_EXTRACT,
206   MMT_GEN_ID,
207   MMT_GEN_KEY,
208   MMT_GOP_START,
209   MMT_DIGEST
210 };
211
212
213 //
214 //
215 class CommandOptions
216 {
217   CommandOptions();
218
219 public:
220   MajorMode_t mode;
221   bool   error_flag;     // true if the given options are in error or not complete
222   bool   key_flag;       // true if an encryption key was given
223   bool   key_id_flag;    // true if a key ID was given
224   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
225   bool   write_hmac;     // true if HMAC values are to be generated and written
226   bool   read_hmac;      // true if HMAC values are to be validated
227   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
228   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
229   bool   verbose_flag;   // true if the verbose option was selected
230   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
231   bool   showindex_flag; // true if index is to be displayed
232   bool   showheader_flag; // true if MXF file header is to be displayed
233   bool   no_write_flag;  // true if no output files are to be written
234   bool   version_flag;   // true if the version display option was selected
235   bool   help_flag;      // true if the help display option was selected
236   ui32_t start_frame;    // frame number to begin processing
237   ui32_t duration;       // number of frames to be processed
238   bool   duration_flag;  // true if duration argument given
239   bool   do_repeat;      // if true and -c -d, repeat first input frame
240   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
241   ui32_t picture_rate;   // fps of picture when wrapping PCM
242   ui32_t fb_size;        // size of picture frame buffer
243   ui32_t file_count;     // number of elements in filenames[]
244   const char* file_root; // filename pre for files written by the extract mode
245   const char* out_file;  // name of mxf file created by create mode
246   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
247   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
248   const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
249
250   //
251   Rational PictureRate()
252   {
253     if ( picture_rate == 23 ) return EditRate_23_98;
254     if ( picture_rate == 48 ) return EditRate_48;
255     return EditRate_24;
256   }
257
258   //
259   const char* szPictureRate()
260   {
261     if ( picture_rate == 23 ) return "23.976";
262     if ( picture_rate == 48 ) return "48";
263     return "24";
264   }
265
266   //
267   CommandOptions(int argc, const char** argv) :
268     mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
269     write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
270     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
271     no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
272     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
273     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
274   {
275     memset(key_value, 0, KeyLen);
276     memset(key_id_value, 0, UUIDlen);
277
278     for ( int i = 1; i < argc; i++ )
279       {
280
281         if ( (strcmp( argv[i], "-help") == 0) )
282           {
283             help_flag = true;
284             continue;
285           }
286          
287         if ( argv[i][0] == '-'
288              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
289              && argv[i][2] == 0 )
290           {
291             switch ( argv[i][1] )
292               {
293               case '1': mono_wav = true; break;
294               case '2': split_wav = true; break;
295               case 'i': mode = MMT_INFO;        break;
296               case 'G': mode = MMT_GOP_START; break;
297               case 'W': no_write_flag = true; break;
298               case 'n': showindex_flag = true; break;
299               case 'H': showheader_flag = true; break;
300               case 'R': do_repeat = true; break;
301               case 'S': split_wav = true; break;
302               case 'V': version_flag = true; break;
303               case 'h': help_flag = true; break;
304               case 'v': verbose_flag = true; break;
305               case 'g': mode = MMT_GEN_KEY; break;
306               case 'u': mode = MMT_GEN_ID; break;
307               case 'e': encrypt_header_flag = true; break;
308               case 'E': encrypt_header_flag = false; break;
309               case 'M': write_hmac = false; break;
310               case 'm': read_hmac = true; break;
311               case 'L': use_smpte_labels = true; break;
312
313               case 'c':
314                 TEST_EXTRA_ARG(i, 'c');
315                 mode = MMT_CREATE;
316                 out_file = argv[i];
317                 break;
318
319               case 'x':
320                 TEST_EXTRA_ARG(i, 'x');
321                 mode = MMT_EXTRACT;
322                 file_root = argv[i];
323                 break;
324
325               case 'k': key_flag = true;
326                 TEST_EXTRA_ARG(i, 'k');
327                 {
328                   ui32_t length;
329                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
330
331                   if ( length != KeyLen )
332                     {
333                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", KeyLen, length);
334                       return;
335                     }
336                 }
337                 break;
338
339               case 'j': key_id_flag = true;
340                 TEST_EXTRA_ARG(i, 'j');
341                 {
342                   ui32_t length;
343                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
344
345                   if ( length != UUIDlen )
346                     {
347                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", UUIDlen, length);
348                       return;
349                     }
350                 }
351                 break;
352
353               case 'f':
354                 TEST_EXTRA_ARG(i, 'f');
355                 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
356                 break;
357
358               case 'd':
359                 TEST_EXTRA_ARG(i, 'd');
360                 duration_flag = true;
361                 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
362                 break;
363
364               case 'p':
365                 TEST_EXTRA_ARG(i, 'p');
366                 picture_rate = atoi(argv[i]);
367                 break;
368
369               case 's':
370                 TEST_EXTRA_ARG(i, 's');
371                 fb_dump_size = atoi(argv[i]);
372                 break;
373
374               case 't': mode = MMT_DIGEST; break;
375
376               case 'b':
377                 TEST_EXTRA_ARG(i, 'b');
378                 fb_size = atoi(argv[i]);
379
380                 if ( verbose_flag )
381                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
382
383                 break;
384
385               default:
386                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
387                 return;
388               }
389           }
390         else
391           {
392
393             if ( argv[i][0] != '-' )
394               {
395                 filenames[file_count++] = argv[i];
396               }
397             else
398               {
399                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
400                 return;
401               }
402
403             if ( file_count >= MAX_IN_FILES )
404               {
405                 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
406                 return;
407               }
408           }
409       }
410
411     if ( help_flag || version_flag )
412       return;
413     
414     if ( ( mode == MMT_INFO
415            || mode == MMT_CREATE
416            || mode == MMT_EXTRACT
417            || mode == MMT_GOP_START
418            || mode == MMT_DIGEST ) && file_count == 0 )
419       {
420         fputs("Option requires at least one filename argument.\n", stderr);
421         return;
422       }
423
424     if ( mode == MMT_NONE && ! help_flag && ! version_flag )
425       {
426         fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
427         return;
428       }
429
430     error_flag = false;
431   }
432 };
433
434 //------------------------------------------------------------------------------------------
435 // MPEG2 essence
436
437 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
438 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
439 //
440 Result_t
441 write_MPEG2_file(CommandOptions& Options)
442 {
443   AESEncContext*     Context = 0;
444   HMACContext*       HMAC = 0;
445   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
446   MPEG2::Parser      Parser;
447   MPEG2::MXFWriter   Writer;
448   MPEG2::VideoDescriptor VDesc;
449   byte_t             IV_buf[CBC_BLOCK_SIZE];
450   Kumu::FortunaRNG   RNG;
451
452   // set up essence parser
453   Result_t result = Parser.OpenRead(Options.filenames[0]);
454
455   // set up MXF writer
456   if ( ASDCP_SUCCESS(result) )
457     {
458       Parser.FillVideoDescriptor(VDesc);
459
460       if ( Options.verbose_flag )
461         {
462           fputs("MPEG-2 Pictures\n", stderr);
463           fputs("VideoDescriptor:\n", stderr);
464           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
465           MPEG2::VideoDescriptorDump(VDesc);
466         }
467     }
468
469   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
470     {
471       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
472       Kumu::GenRandomUUID(Info.AssetUUID);
473
474       if ( Options.use_smpte_labels )
475         {
476           Info.LabelSetType = LS_MXF_SMPTE;
477           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
478         }
479
480       // configure encryption
481       if( Options.key_flag )
482         {
483           Kumu::GenRandomUUID(Info.ContextID);
484           Info.EncryptedEssence = true;
485
486           if ( Options.key_id_flag )
487             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
488           else
489             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
490
491           Context = new AESEncContext;
492           result = Context->InitKey(Options.key_value);
493
494           if ( ASDCP_SUCCESS(result) )
495             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
496
497           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
498             {
499               Info.UsesHMAC = true;
500               HMAC = new HMACContext;
501               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
502             }
503         }
504
505       if ( ASDCP_SUCCESS(result) )
506         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
507     }
508
509   if ( ASDCP_SUCCESS(result) )
510     // loop through the frames
511     {
512       result = Parser.Reset();
513       ui32_t duration = 0;
514
515       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
516         {
517           if ( ! Options.do_repeat || duration == 1 )
518             {
519               result = Parser.ReadFrame(FrameBuffer);
520
521               if ( ASDCP_SUCCESS(result) )
522                 {
523                   if ( Options.verbose_flag )
524                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
525                   
526                   if ( Options.encrypt_header_flag )
527                     FrameBuffer.PlaintextOffset(0);
528                 }
529             }
530
531           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
532             {
533               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
534
535               // The Writer class will forward the last block of ciphertext
536               // to the encryption context for use as the IV for the next
537               // frame. If you want to use non-sequitur IV values, un-comment
538               // the following  line of code.
539               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
540               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
541             }
542         }
543
544       if ( result == RESULT_ENDOFFILE )
545         result = RESULT_OK;
546     }
547
548   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
549     result = Writer.Finalize();
550
551   return result;
552 }
553
554 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
555 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
556 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
557 //
558 Result_t
559 read_MPEG2_file(CommandOptions& Options)
560 {
561   AESDecContext*     Context = 0;
562   HMACContext*       HMAC = 0;
563   MPEG2::MXFReader   Reader;
564   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
565   Kumu::FileWriter   OutFile;
566   ui32_t             frame_count = 0;
567
568   Result_t result = Reader.OpenRead(Options.filenames[0]);
569
570   if ( ASDCP_SUCCESS(result) )
571     {
572       MPEG2::VideoDescriptor VDesc;
573       Reader.FillVideoDescriptor(VDesc);
574       frame_count = VDesc.ContainerDuration;
575
576       if ( Options.verbose_flag )
577         {
578           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
579           MPEG2::VideoDescriptorDump(VDesc);
580         }
581     }
582
583   if ( ASDCP_SUCCESS(result) )
584     {
585       char filename[256];
586       snprintf(filename, 256, "%s.ves", Options.file_root);
587       result = OutFile.OpenWrite(filename);
588     }
589
590   if ( ASDCP_SUCCESS(result) && Options.key_flag )
591     {
592       Context = new AESDecContext;
593       result = Context->InitKey(Options.key_value);
594
595       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
596         {
597           WriterInfo Info;
598           Reader.FillWriterInfo(Info);
599
600           if ( Info.UsesHMAC )
601             {
602               HMAC = new HMACContext;
603               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
604             }
605           else
606             {
607               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
608             }
609         }
610     }
611
612   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
613   if ( last_frame > frame_count )
614     last_frame = frame_count;
615
616   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
617     {
618       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
619
620       if ( ASDCP_SUCCESS(result) )
621         {
622           if ( Options.verbose_flag )
623             FrameBuffer.Dump(stderr, Options.fb_dump_size);
624
625           ui32_t write_count = 0;
626           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
627         }
628     }
629
630   return result;
631 }
632
633
634 //
635 Result_t
636 gop_start_test(CommandOptions& Options)
637 {
638   using namespace ASDCP::MPEG2;
639
640   MXFReader   Reader;
641   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
642   ui32_t      frame_count = 0;
643
644   Result_t result = Reader.OpenRead(Options.filenames[0]);
645
646   if ( ASDCP_SUCCESS(result) )
647     {
648       MPEG2::VideoDescriptor VDesc;
649       Reader.FillVideoDescriptor(VDesc);
650       frame_count = VDesc.ContainerDuration;
651
652       if ( Options.verbose_flag )
653         {
654           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
655           MPEG2::VideoDescriptorDump(VDesc);
656         }
657     }
658
659   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
660   if ( last_frame > frame_count )
661     last_frame = frame_count;
662
663   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
664     {
665       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
666
667       if ( ASDCP_SUCCESS(result) )
668         {
669           if ( Options.verbose_flag )
670             FrameBuffer.Dump(stderr, Options.fb_dump_size);
671
672           if ( FrameBuffer.FrameType() != FRAME_I )
673             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
674
675           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
676         }
677     }
678
679   return result;
680 }
681
682 //------------------------------------------------------------------------------------------
683 // JPEG 2000 essence
684
685 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
686 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
687 //
688 Result_t
689 write_JP2K_file(CommandOptions& Options)
690 {
691   AESEncContext*          Context = 0;
692   HMACContext*            HMAC = 0;
693   JP2K::MXFWriter         Writer;
694   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
695   JP2K::PictureDescriptor PDesc;
696   JP2K::SequenceParser    Parser;
697   byte_t                  IV_buf[CBC_BLOCK_SIZE];
698   Kumu::FortunaRNG        RNG;
699
700   // set up essence parser
701   Result_t result = Parser.OpenRead(Options.filenames[0]);
702
703   // set up MXF writer
704   if ( ASDCP_SUCCESS(result) )
705     {
706       Parser.FillPictureDescriptor(PDesc);
707       PDesc.EditRate = Options.PictureRate();
708
709       if ( Options.verbose_flag )
710         {
711           fprintf(stderr, "JPEG 2000 pictures\n");
712           fputs("PictureDescriptor:\n", stderr);
713           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
714           JP2K::PictureDescriptorDump(PDesc);
715         }
716     }
717
718   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
719     {
720       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
721       Kumu::GenRandomUUID(Info.AssetUUID);
722
723       if ( Options.use_smpte_labels )
724         {
725           Info.LabelSetType = LS_MXF_SMPTE;
726           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
727         }
728
729       // configure encryption
730       if( Options.key_flag )
731         {
732           Kumu::GenRandomUUID(Info.ContextID);
733           Info.EncryptedEssence = true;
734
735           if ( Options.key_id_flag )
736             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
737           else
738             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
739
740           Context = new AESEncContext;
741           result = Context->InitKey(Options.key_value);
742
743           if ( ASDCP_SUCCESS(result) )
744             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
745
746           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
747             {
748               Info.UsesHMAC = true;
749               HMAC = new HMACContext;
750               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
751             }
752         }
753
754       if ( ASDCP_SUCCESS(result) )
755         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
756     }
757
758   if ( ASDCP_SUCCESS(result) )
759     {
760       ui32_t duration = 0;
761       result = Parser.Reset();
762
763       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
764         {
765           if ( ! Options.do_repeat || duration == 1 )
766             {
767               result = Parser.ReadFrame(FrameBuffer);
768
769               if ( ASDCP_SUCCESS(result) )
770                 {
771                   if ( Options.verbose_flag )
772                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
773                   
774                   if ( Options.encrypt_header_flag )
775                     FrameBuffer.PlaintextOffset(0);
776                 }
777             }
778
779           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
780             {
781               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
782
783               // The Writer class will forward the last block of ciphertext
784               // to the encryption context for use as the IV for the next
785               // frame. If you want to use non-sequitur IV values, un-comment
786               // the following  line of code.
787               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
788               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
789             }
790         }
791
792       if ( result == RESULT_ENDOFFILE )
793         result = RESULT_OK;
794     }
795
796   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
797     result = Writer.Finalize();
798
799   return result;
800 }
801
802 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
803 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
804 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
805 //
806 Result_t
807 read_JP2K_file(CommandOptions& Options)
808 {
809   AESDecContext*     Context = 0;
810   HMACContext*       HMAC = 0;
811   JP2K::MXFReader    Reader;
812   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
813   ui32_t             frame_count = 0;
814
815   Result_t result = Reader.OpenRead(Options.filenames[0]);
816
817   if ( ASDCP_SUCCESS(result) )
818     {
819       JP2K::PictureDescriptor PDesc;
820       Reader.FillPictureDescriptor(PDesc);
821
822       frame_count = PDesc.ContainerDuration;
823
824       if ( Options.verbose_flag )
825         {
826           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
827           JP2K::PictureDescriptorDump(PDesc);
828         }
829     }
830
831   if ( ASDCP_SUCCESS(result) && Options.key_flag )
832     {
833       Context = new AESDecContext;
834       result = Context->InitKey(Options.key_value);
835
836       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
837         {
838           WriterInfo Info;
839           Reader.FillWriterInfo(Info);
840
841           if ( Info.UsesHMAC )
842             {
843               HMAC = new HMACContext;
844               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
845             }
846           else
847             {
848               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
849             }
850         }
851     }
852
853   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
854   if ( last_frame > frame_count )
855     last_frame = frame_count;
856
857   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
858     {
859       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
860
861       if ( ASDCP_SUCCESS(result) )
862         {
863           Kumu::FileWriter OutFile;
864           char filename[256];
865           ui32_t write_count;
866           snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
867           result = OutFile.OpenWrite(filename);
868
869           if ( ASDCP_SUCCESS(result) )
870             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
871
872           if ( Options.verbose_flag )
873             FrameBuffer.Dump(stderr, Options.fb_dump_size);
874         }
875     }
876
877   return result;
878 }
879
880 //------------------------------------------------------------------------------------------
881 // PCM essence
882
883
884 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
885 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
886 //
887 Result_t
888 write_PCM_file(CommandOptions& Options)
889 {
890   AESEncContext*    Context = 0;
891   HMACContext*      HMAC = 0;
892   PCMParserList     Parser;
893   PCM::MXFWriter    Writer;
894   PCM::FrameBuffer  FrameBuffer;
895   PCM::AudioDescriptor ADesc;
896   Rational          PictureRate = Options.PictureRate();
897   byte_t            IV_buf[CBC_BLOCK_SIZE];
898   Kumu::FortunaRNG  RNG;
899
900   // set up essence parser
901   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
902
903   // set up MXF writer
904   if ( ASDCP_SUCCESS(result) )
905     {
906       Parser.FillAudioDescriptor(ADesc);
907
908       ADesc.SampleRate = PictureRate;
909       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
910
911       if ( Options.verbose_flag )
912         {
913           fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
914                   Options.szPictureRate(),
915                   PCM::CalcSamplesPerFrame(ADesc));
916           fputs("AudioDescriptor:\n", stderr);
917           PCM::AudioDescriptorDump(ADesc);
918         }
919     }
920
921   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
922     {
923       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
924       Kumu::GenRandomUUID(Info.AssetUUID);
925
926       if ( Options.use_smpte_labels )
927         {
928           Info.LabelSetType = LS_MXF_SMPTE;
929           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
930         }
931
932       // configure encryption
933       if( Options.key_flag )
934         {
935           Kumu::GenRandomUUID(Info.ContextID);
936           Info.EncryptedEssence = true;
937
938           if ( Options.key_id_flag )
939             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
940           else
941             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
942
943           Context = new AESEncContext;
944           result = Context->InitKey(Options.key_value);
945
946           if ( ASDCP_SUCCESS(result) )
947             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
948
949           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
950             {
951               Info.UsesHMAC = true;
952               HMAC = new HMACContext;
953               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
954             }
955         }
956
957       if ( ASDCP_SUCCESS(result) )
958         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
959     }
960
961   if ( ASDCP_SUCCESS(result) )
962     {
963       result = Parser.Reset();
964       ui32_t duration = 0;
965
966       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
967         {
968           result = Parser.ReadFrame(FrameBuffer);
969
970           if ( ASDCP_SUCCESS(result) )
971             {
972               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
973                 {
974                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
975                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
976                   result = RESULT_ENDOFFILE;
977                   continue;
978                 }
979
980               if ( Options.verbose_flag )
981                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
982
983               if ( ! Options.no_write_flag )
984                 {
985                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
986
987                   // The Writer class will forward the last block of ciphertext
988                   // to the encryption context for use as the IV for the next
989                   // frame. If you want to use non-sequitur IV values, un-comment
990                   // the following  line of code.
991                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
992                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
993                 }
994             }
995         }
996
997       if ( result == RESULT_ENDOFFILE )
998         result = RESULT_OK;
999     }
1000
1001   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1002     result = Writer.Finalize();
1003
1004   return result;
1005 }
1006
1007 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1008 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1009 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1010 //
1011 Result_t
1012 read_PCM_file(CommandOptions& Options)
1013 {
1014   AESDecContext*     Context = 0;
1015   HMACContext*       HMAC = 0;
1016   PCM::MXFReader     Reader;
1017   PCM::FrameBuffer   FrameBuffer;
1018   WavFileWriter      OutWave;
1019   PCM::AudioDescriptor ADesc;
1020   ui32_t last_frame = 0;
1021
1022   Result_t result = Reader.OpenRead(Options.filenames[0]);
1023
1024   if ( ASDCP_SUCCESS(result) )
1025     {
1026       Reader.FillAudioDescriptor(ADesc);
1027
1028       if ( ADesc.SampleRate != EditRate_23_98
1029            && ADesc.SampleRate != EditRate_24
1030            && ADesc.SampleRate != EditRate_48 )
1031         ADesc.SampleRate = Options.PictureRate();
1032
1033       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1034
1035       if ( Options.verbose_flag )
1036         PCM::AudioDescriptorDump(ADesc);
1037     }
1038
1039   if ( ASDCP_SUCCESS(result) )
1040     {
1041       last_frame = ADesc.ContainerDuration;
1042
1043       if ( Options.duration > 0 && Options.duration < last_frame )
1044         last_frame = Options.duration;
1045
1046       if ( Options.start_frame > 0 )
1047         {
1048           if ( Options.start_frame > ADesc.ContainerDuration )
1049             {
1050               fprintf(stderr, "Start value greater than file duration.\n");
1051               return RESULT_FAIL;
1052             }
1053
1054           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1055         }
1056
1057       ADesc.ContainerDuration = last_frame - Options.start_frame;
1058       OutWave.OpenWrite(ADesc, Options.file_root,
1059                         ( Options.split_wav ? WavFileWriter::ST_STEREO : 
1060                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1061     }
1062
1063   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1064     {
1065       Context = new AESDecContext;
1066       result = Context->InitKey(Options.key_value);
1067
1068       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1069         {
1070           WriterInfo Info;
1071           Reader.FillWriterInfo(Info);
1072
1073           if ( Info.UsesHMAC )
1074             {
1075               HMAC = new HMACContext;
1076               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1077             }
1078           else
1079             {
1080               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1081             }
1082         }
1083     }
1084
1085   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1086     {
1087       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1088
1089       if ( ASDCP_SUCCESS(result) )
1090         {
1091           if ( Options.verbose_flag )
1092             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1093
1094           result = OutWave.WriteFrame(FrameBuffer);
1095         }
1096     }
1097
1098   return result;
1099 }
1100
1101
1102 //------------------------------------------------------------------------------------------
1103 //
1104
1105 //
1106 // These classes wrap the irregular names in the asdcplib API
1107 // so that I can use a template to simplify the implementation
1108 // of show_file_info()
1109
1110 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1111 {
1112  public:
1113   void FillDescriptor(MPEG2::MXFReader& Reader) {
1114     Reader.FillVideoDescriptor(*this);
1115   }
1116
1117   void Dump(FILE* stream) {
1118     MPEG2::VideoDescriptorDump(*this, stream);
1119   }
1120 };
1121
1122 class MyPictureDescriptor : public JP2K::PictureDescriptor
1123 {
1124  public:
1125   void FillDescriptor(JP2K::MXFReader& Reader) {
1126     Reader.FillPictureDescriptor(*this);
1127   }
1128
1129   void Dump(FILE* stream) {
1130     JP2K::PictureDescriptorDump(*this, stream);
1131   }
1132 };
1133
1134 class MyAudioDescriptor : public PCM::AudioDescriptor
1135 {
1136  public:
1137   void FillDescriptor(PCM::MXFReader& Reader) {
1138     Reader.FillAudioDescriptor(*this);
1139   }
1140
1141   void Dump(FILE* stream) {
1142     PCM::AudioDescriptorDump(*this, stream);
1143   }
1144 };
1145
1146
1147 // MSVC didn't like the function template, so now it's a static class method
1148 template<class ReaderT, class DescriptorT>
1149 class FileInfoWrapper
1150 {
1151 public:
1152   static void file_info(CommandOptions& Options, FILE* stream = 0)
1153   {
1154     if ( stream == 0 )
1155       stream = stdout;
1156
1157     if ( Options.verbose_flag || Options.showheader_flag )
1158       {
1159         ReaderT     Reader;
1160         Result_t result = Reader.OpenRead(Options.filenames[0]);
1161
1162         if ( ASDCP_SUCCESS(result) )
1163           {
1164             if ( Options.showheader_flag )
1165               Reader.DumpHeaderMetadata(stream);
1166
1167             WriterInfo WI;
1168             Reader.FillWriterInfo(WI);
1169             WriterInfoDump(WI, stream);
1170
1171             DescriptorT Desc;
1172             Desc.FillDescriptor(Reader);
1173             Desc.Dump(stream);
1174
1175             if ( Options.showindex_flag )
1176               Reader.DumpIndex(stream);
1177           }
1178         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1179           {
1180             Reader.DumpHeaderMetadata(stream);
1181           }
1182       }
1183   }
1184 };
1185
1186 // Read header metadata from an ASDCP file
1187 //
1188 Result_t
1189 show_file_info(CommandOptions& Options)
1190 {
1191   EssenceType_t EssenceType;
1192   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1193
1194   if ( ASDCP_FAILURE(result) )
1195     return result;
1196
1197   if ( EssenceType == ESS_MPEG2_VES )
1198     {
1199       fputs("File essence type is MPEG2 video.\n", stdout);
1200       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1201     }
1202   else if ( EssenceType == ESS_PCM_24b_48k )
1203     {
1204       fputs("File essence type is PCM audio.\n", stdout);
1205       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1206     }
1207   else if ( EssenceType == ESS_JPEG_2000 )
1208     {
1209       fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1210       FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1211     }
1212   else
1213     {
1214       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1215       Kumu::FileReader   Reader;
1216       MXF::OPAtomHeader TestHeader;
1217
1218       result = Reader.OpenRead(Options.filenames[0]);
1219
1220       if ( ASDCP_SUCCESS(result) )
1221         result = TestHeader.InitFromFile(Reader); // test UL and OP
1222
1223       if ( ASDCP_SUCCESS(result) )
1224         {
1225           TestHeader.Partition::Dump();
1226
1227           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1228             ID->Dump();
1229           else
1230             fputs("File contains no Identification object.\n", stdout);
1231
1232           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1233             SP->Dump();
1234           else
1235             fputs("File contains no SourcePackage object.\n", stdout);
1236         }
1237       else
1238         {
1239           fputs("File is not MXF.\n", stdout);
1240         }
1241     }
1242
1243   return result;
1244 }
1245
1246
1247 //
1248 Result_t
1249 digest_file(const char* filename)
1250 {
1251   using namespace Kumu;
1252
1253   ASDCP_TEST_NULL_STR(filename);
1254   FileReader Reader;
1255   SHA_CTX Ctx;
1256   SHA1_Init(&Ctx);
1257   ByteString Buf(8192);
1258
1259   Result_t result = Reader.OpenRead(filename);
1260
1261   while ( ASDCP_SUCCESS(result) )
1262     {
1263       ui32_t read_count = 0;
1264       result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1265
1266       if ( result == RESULT_ENDOFFILE )
1267         {
1268           result = RESULT_OK;
1269           break;
1270         }
1271
1272       if ( ASDCP_SUCCESS(result) )
1273         SHA1_Update(&Ctx, Buf.Data(), read_count);
1274     }
1275
1276   if ( ASDCP_SUCCESS(result) )
1277     {
1278       const ui32_t sha_len = 20;
1279       byte_t bin_buf[sha_len];
1280       char sha_buf[64];
1281       SHA1_Final(bin_buf, &Ctx);
1282
1283       fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1284     }
1285
1286   return result;
1287 }
1288
1289 //
1290 int
1291 main(int argc, const char** argv)
1292 {
1293   Result_t result = RESULT_OK;
1294   CommandOptions Options(argc, argv);
1295
1296   if ( Options.version_flag )
1297     banner();
1298
1299   if ( Options.help_flag )
1300     usage();
1301
1302   if ( Options.version_flag || Options.help_flag )
1303     return 0;
1304
1305   if ( Options.error_flag )
1306     {
1307       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1308       return 3;
1309     }
1310
1311   if ( Options.mode == MMT_INFO )
1312     {
1313       result = show_file_info(Options);
1314     }
1315   else if ( Options.mode == MMT_GOP_START )
1316     {
1317       result = gop_start_test(Options);
1318     }
1319   else if ( Options.mode == MMT_GEN_KEY )
1320     {
1321       Kumu::FortunaRNG RNG;
1322       byte_t bin_buf[KeyLen];
1323       char   str_buf[40];
1324
1325       RNG.FillRandom(bin_buf, KeyLen);
1326       printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1327     }
1328   else if ( Options.mode == MMT_GEN_ID )
1329     {
1330       UUID TmpID;
1331       Kumu::GenRandomValue(TmpID);
1332       char   str_buf[40];
1333       printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1334     }
1335   else if ( Options.mode == MMT_DIGEST )
1336     {
1337       for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1338         result = digest_file(Options.filenames[i]);
1339     }
1340   else if ( Options.mode == MMT_EXTRACT )
1341     {
1342       EssenceType_t EssenceType;
1343       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1344
1345       if ( ASDCP_SUCCESS(result) )
1346         {
1347           switch ( EssenceType )
1348             {
1349             case ESS_MPEG2_VES:
1350               result = read_MPEG2_file(Options);
1351               break;
1352
1353             case ESS_JPEG_2000:
1354               result = read_JP2K_file(Options);
1355               break;
1356
1357             case ESS_PCM_24b_48k:
1358               result = read_PCM_file(Options);
1359               break;
1360
1361             default:
1362               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1363               return 5;
1364             }
1365         }
1366     }
1367   else if ( Options.mode == MMT_CREATE )
1368     {
1369       if ( Options.do_repeat && ! Options.duration_flag )
1370         {
1371           fputs("Option -R requires -d <duration>\n", stderr);
1372           return RESULT_FAIL;
1373         }
1374
1375       EssenceType_t EssenceType;
1376       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1377
1378       if ( ASDCP_SUCCESS(result) )
1379         {
1380           switch ( EssenceType )
1381             {
1382             case ESS_MPEG2_VES:
1383               result = write_MPEG2_file(Options);
1384               break;
1385
1386             case ESS_JPEG_2000:
1387               result = write_JP2K_file(Options);
1388               break;
1389
1390             case ESS_PCM_24b_48k:
1391               result = write_PCM_file(Options);
1392               break;
1393
1394             default:
1395               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1396                       Options.filenames[0]);
1397               return 5;
1398             }
1399         }
1400     }
1401
1402   if ( ASDCP_FAILURE(result) )
1403     {
1404       fputs("Program stopped on error.\n", stderr);
1405
1406       if ( result != RESULT_FAIL )
1407         {
1408           fputs(result, stderr);
1409           fputc('\n', stderr);
1410         }
1411
1412       return 1;
1413     }
1414
1415   return 0;
1416 }
1417
1418
1419 //
1420 // end asdcp-test.cpp
1421 //