Denis' bug fixes
[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]\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   -W                - Read input file only, do not write source file\n\
178 \n");
179
180   fprintf(stream, "\
181 Info Options:\n\
182   -H                - Show MXF header metadata, used with option -i\n\
183   -n                - Show index, used with option -i\n\
184 \n\
185 Other Options:\n\
186   -s <num>          - Number of bytes of frame buffer to be dumped as hex to\n\
187                       stderr, used with option -v\n\
188   -v                - Verbose, prints informative messages to stderr\n\
189 \n\
190   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
191          o All option arguments must be separated from the option by whitespace.\n\
192          o An argument of \"23\" to the -p option will be interpreted\n\
193            as 23000/1001 fps.\n\
194 \n");
195 }
196
197 //
198 enum MajorMode_t
199 {
200   MMT_NONE,
201   MMT_INFO,
202   MMT_CREATE,
203   MMT_EXTRACT,
204   MMT_GEN_ID,
205   MMT_GEN_KEY,
206   MMT_GOP_START,
207   MMT_DIGEST
208 };
209
210
211 //
212 //
213 class CommandOptions
214 {
215   CommandOptions();
216
217 public:
218   MajorMode_t mode;
219   bool   error_flag;     // true if the given options are in error or not complete
220   bool   key_flag;       // true if an encryption key was given
221   bool   key_id_flag;    // true if a key ID was given
222   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
223   bool   write_hmac;     // true if HMAC values are to be generated and written
224   bool   read_hmac;      // true if HMAC values are to be validated
225   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
226   bool   verbose_flag;   // true if the verbose option was selected
227   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
228   bool   showindex_flag; // true if index is to be displayed
229   bool   showheader_flag; // true if MXF file header is to be displayed
230   bool   no_write_flag;  // true if no output files are to be written
231   bool   version_flag;   // true if the version display option was selected
232   bool   help_flag;      // true if the help display option was selected
233   ui32_t start_frame;    // frame number to begin processing
234   ui32_t duration;       // number of frames to be processed
235   bool   duration_flag;  // true if duration argument given
236   bool   do_repeat;      // if true and -c -d, repeat first input frame
237   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
238   ui32_t picture_rate;   // fps of picture when wrapping PCM
239   ui32_t fb_size;        // size of picture frame buffer
240   ui32_t file_count;     // number of elements in filenames[]
241   const char* file_root; // filename pre for files written by the extract mode
242   const char* out_file;  // name of mxf file created by create mode
243   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
244   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
245   const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
246
247   //
248   Rational PictureRate()
249   {
250     if ( picture_rate == 23 ) return EditRate_23_98;
251     if ( picture_rate == 48 ) return EditRate_48;
252     return EditRate_24;
253   }
254
255   //
256   const char* szPictureRate()
257   {
258     if ( picture_rate == 23 ) return "23.976";
259     if ( picture_rate == 48 ) return "48";
260     return "24";
261   }
262
263   //
264   CommandOptions(int argc, const char** argv) :
265     mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
266     write_hmac(true), read_hmac(false), split_wav(false),
267     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
268     no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
269     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
270     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
271   {
272     memset(key_value, 0, KeyLen);
273     memset(key_id_value, 0, UUIDlen);
274
275     for ( int i = 1; i < argc; i++ )
276       {
277
278         if ( (strcmp( argv[i], "-help") == 0) )
279           {
280             help_flag = true;
281             continue;
282           }
283          
284         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
285           {
286             switch ( argv[i][1] )
287               {
288               case 'i': mode = MMT_INFO;        break;
289               case 'G': mode = MMT_GOP_START; break;
290               case 'W': no_write_flag = true; break;
291               case 'n': showindex_flag = true; break;
292               case 'H': showheader_flag = true; break;
293               case 'R': do_repeat = true; break;
294               case 'S': split_wav = true; break;
295               case 'V': version_flag = true; break;
296               case 'h': help_flag = true; break;
297               case 'v': verbose_flag = true; break;
298               case 'g': mode = MMT_GEN_KEY; break;
299               case 'u': mode = MMT_GEN_ID; break;
300               case 'e': encrypt_header_flag = true; break;
301               case 'E': encrypt_header_flag = false; break;
302               case 'M': write_hmac = false; break;
303               case 'm': read_hmac = true; break;
304               case 'L': use_smpte_labels = true; break;
305
306               case 'c':
307                 TEST_EXTRA_ARG(i, 'c');
308                 mode = MMT_CREATE;
309                 out_file = argv[i];
310                 break;
311
312               case 'x':
313                 TEST_EXTRA_ARG(i, 'x');
314                 mode = MMT_EXTRACT;
315                 file_root = argv[i];
316                 break;
317
318               case 'k': key_flag = true;
319                 TEST_EXTRA_ARG(i, 'k');
320                 {
321                   ui32_t length;
322                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
323
324                   if ( length != KeyLen )
325                     {
326                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", KeyLen, length);
327                       return;
328                     }
329                 }
330                 break;
331
332               case 'j': key_id_flag = true;
333                 TEST_EXTRA_ARG(i, 'j');
334                 {
335                   ui32_t length;
336                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
337
338                   if ( length != UUIDlen )
339                     {
340                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", UUIDlen, length);
341                       return;
342                     }
343                 }
344                 break;
345
346               case 'f':
347                 TEST_EXTRA_ARG(i, 'f');
348                 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
349                 break;
350
351               case 'd':
352                 TEST_EXTRA_ARG(i, 'd');
353                 duration_flag = true;
354                 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
355                 break;
356
357               case 'p':
358                 TEST_EXTRA_ARG(i, 'p');
359                 picture_rate = atoi(argv[i]);
360                 break;
361
362               case 's':
363                 TEST_EXTRA_ARG(i, 's');
364                 fb_dump_size = atoi(argv[i]);
365                 break;
366
367               case 't': mode = MMT_DIGEST; break;
368
369               case 'b':
370                 TEST_EXTRA_ARG(i, 'b');
371                 fb_size = atoi(argv[i]);
372
373                 if ( verbose_flag )
374                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
375
376                 break;
377
378               default:
379                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
380                 return;
381               }
382           }
383         else
384           {
385
386             if ( argv[i][0] != '-' )
387               {
388                 filenames[file_count++] = argv[i];
389               }
390             else
391               {
392                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
393                 return;
394               }
395
396             if ( file_count >= MAX_IN_FILES )
397               {
398                 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
399                 return;
400               }
401           }
402       }
403
404     if ( help_flag || version_flag )
405       return;
406     
407     if ( ( mode == MMT_INFO
408            || mode == MMT_CREATE
409            || mode == MMT_EXTRACT
410            || mode == MMT_GOP_START
411            || mode == MMT_DIGEST ) && file_count == 0 )
412       {
413         fputs("Option requires at least one filename argument.\n", stderr);
414         return;
415       }
416
417     if ( mode == MMT_NONE && ! help_flag && ! version_flag )
418       {
419         fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
420         return;
421       }
422
423     error_flag = false;
424   }
425 };
426
427 //------------------------------------------------------------------------------------------
428 // MPEG2 essence
429
430 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
431 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
432 //
433 Result_t
434 write_MPEG2_file(CommandOptions& Options)
435 {
436   AESEncContext*     Context = 0;
437   HMACContext*       HMAC = 0;
438   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
439   MPEG2::Parser      Parser;
440   MPEG2::MXFWriter   Writer;
441   MPEG2::VideoDescriptor VDesc;
442   byte_t             IV_buf[CBC_BLOCK_SIZE];
443   Kumu::FortunaRNG   RNG;
444
445   // set up essence parser
446   Result_t result = Parser.OpenRead(Options.filenames[0]);
447
448   // set up MXF writer
449   if ( ASDCP_SUCCESS(result) )
450     {
451       Parser.FillVideoDescriptor(VDesc);
452
453       if ( Options.verbose_flag )
454         {
455           fputs("MPEG-2 Pictures\n", stderr);
456           fputs("VideoDescriptor:\n", stderr);
457           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
458           MPEG2::VideoDescriptorDump(VDesc);
459         }
460     }
461
462   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
463     {
464       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
465       Kumu::GenRandomUUID(Info.AssetUUID);
466
467       if ( Options.use_smpte_labels )
468         {
469           Info.LabelSetType = LS_MXF_SMPTE;
470           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
471         }
472
473       // configure encryption
474       if( Options.key_flag )
475         {
476           Kumu::GenRandomUUID(Info.ContextID);
477           Info.EncryptedEssence = true;
478
479           if ( Options.key_id_flag )
480             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
481           else
482             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
483
484           Context = new AESEncContext;
485           result = Context->InitKey(Options.key_value);
486
487           if ( ASDCP_SUCCESS(result) )
488             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
489
490           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
491             {
492               Info.UsesHMAC = true;
493               HMAC = new HMACContext;
494               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
495             }
496         }
497
498       if ( ASDCP_SUCCESS(result) )
499         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
500     }
501
502   if ( ASDCP_SUCCESS(result) )
503     // loop through the frames
504     {
505       result = Parser.Reset();
506       ui32_t duration = 0;
507
508       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
509         {
510           if ( ! Options.do_repeat || duration == 1 )
511             {
512               result = Parser.ReadFrame(FrameBuffer);
513
514               if ( ASDCP_SUCCESS(result) )
515                 {
516                   if ( Options.verbose_flag )
517                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
518                   
519                   if ( Options.encrypt_header_flag )
520                     FrameBuffer.PlaintextOffset(0);
521                 }
522             }
523
524           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
525             {
526               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
527
528               // The Writer class will forward the last block of ciphertext
529               // to the encryption context for use as the IV for the next
530               // frame. If you want to use non-sequitur IV values, un-comment
531               // the following  line of code.
532               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
533               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
534             }
535         }
536
537       if ( result == RESULT_ENDOFFILE )
538         result = RESULT_OK;
539     }
540
541   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
542     result = Writer.Finalize();
543
544   return result;
545 }
546
547 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
548 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
549 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
550 //
551 Result_t
552 read_MPEG2_file(CommandOptions& Options)
553 {
554   AESDecContext*     Context = 0;
555   HMACContext*       HMAC = 0;
556   MPEG2::MXFReader   Reader;
557   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
558   Kumu::FileWriter   OutFile;
559   ui32_t             frame_count = 0;
560
561   Result_t result = Reader.OpenRead(Options.filenames[0]);
562
563   if ( ASDCP_SUCCESS(result) )
564     {
565       MPEG2::VideoDescriptor VDesc;
566       Reader.FillVideoDescriptor(VDesc);
567       frame_count = VDesc.ContainerDuration;
568
569       if ( Options.verbose_flag )
570         {
571           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
572           MPEG2::VideoDescriptorDump(VDesc);
573         }
574     }
575
576   if ( ASDCP_SUCCESS(result) )
577     {
578       char filename[256];
579       snprintf(filename, 256, "%s.ves", Options.file_root);
580       result = OutFile.OpenWrite(filename);
581     }
582
583   if ( ASDCP_SUCCESS(result) && Options.key_flag )
584     {
585       Context = new AESDecContext;
586       result = Context->InitKey(Options.key_value);
587
588       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
589         {
590           WriterInfo Info;
591           Reader.FillWriterInfo(Info);
592
593           if ( Info.UsesHMAC )
594             {
595               HMAC = new HMACContext;
596               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
597             }
598           else
599             {
600               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
601             }
602         }
603     }
604
605   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
606   if ( last_frame > frame_count )
607     last_frame = frame_count;
608
609   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
610     {
611       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
612
613       if ( ASDCP_SUCCESS(result) )
614         {
615           if ( Options.verbose_flag )
616             FrameBuffer.Dump(stderr, Options.fb_dump_size);
617
618           ui32_t write_count = 0;
619           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
620         }
621     }
622
623   return result;
624 }
625
626
627 //
628 Result_t
629 gop_start_test(CommandOptions& Options)
630 {
631   using namespace ASDCP::MPEG2;
632
633   MXFReader   Reader;
634   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
635   ui32_t      frame_count = 0;
636
637   Result_t result = Reader.OpenRead(Options.filenames[0]);
638
639   if ( ASDCP_SUCCESS(result) )
640     {
641       MPEG2::VideoDescriptor VDesc;
642       Reader.FillVideoDescriptor(VDesc);
643       frame_count = VDesc.ContainerDuration;
644
645       if ( Options.verbose_flag )
646         {
647           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
648           MPEG2::VideoDescriptorDump(VDesc);
649         }
650     }
651
652   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
653   if ( last_frame > frame_count )
654     last_frame = frame_count;
655
656   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
657     {
658       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
659
660       if ( ASDCP_SUCCESS(result) )
661         {
662           if ( Options.verbose_flag )
663             FrameBuffer.Dump(stderr, Options.fb_dump_size);
664
665           if ( FrameBuffer.FrameType() != FRAME_I )
666             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
667
668           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
669         }
670     }
671
672   return result;
673 }
674
675 //------------------------------------------------------------------------------------------
676 // JPEG 2000 essence
677
678 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
679 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
680 //
681 Result_t
682 write_JP2K_file(CommandOptions& Options)
683 {
684   AESEncContext*          Context = 0;
685   HMACContext*            HMAC = 0;
686   JP2K::MXFWriter         Writer;
687   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
688   JP2K::PictureDescriptor PDesc;
689   JP2K::SequenceParser    Parser;
690   byte_t                  IV_buf[CBC_BLOCK_SIZE];
691   Kumu::FortunaRNG        RNG;
692
693   // set up essence parser
694   Result_t result = Parser.OpenRead(Options.filenames[0]);
695
696   // set up MXF writer
697   if ( ASDCP_SUCCESS(result) )
698     {
699       Parser.FillPictureDescriptor(PDesc);
700       PDesc.EditRate = Options.PictureRate();
701
702       if ( Options.verbose_flag )
703         {
704           fprintf(stderr, "JPEG 2000 pictures\n");
705           fputs("PictureDescriptor:\n", stderr);
706           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
707           JP2K::PictureDescriptorDump(PDesc);
708         }
709     }
710
711   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
712     {
713       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
714       Kumu::GenRandomUUID(Info.AssetUUID);
715
716       if ( Options.use_smpte_labels )
717         {
718           Info.LabelSetType = LS_MXF_SMPTE;
719           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
720         }
721
722       // configure encryption
723       if( Options.key_flag )
724         {
725           Kumu::GenRandomUUID(Info.ContextID);
726           Info.EncryptedEssence = true;
727
728           if ( Options.key_id_flag )
729             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
730           else
731             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
732
733           Context = new AESEncContext;
734           result = Context->InitKey(Options.key_value);
735
736           if ( ASDCP_SUCCESS(result) )
737             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
738
739           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
740             {
741               Info.UsesHMAC = true;
742               HMAC = new HMACContext;
743               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
744             }
745         }
746
747       if ( ASDCP_SUCCESS(result) )
748         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
749     }
750
751   if ( ASDCP_SUCCESS(result) )
752     {
753       ui32_t duration = 0;
754       result = Parser.Reset();
755
756       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
757         {
758           if ( ! Options.do_repeat || duration == 1 )
759             {
760               result = Parser.ReadFrame(FrameBuffer);
761
762               if ( ASDCP_SUCCESS(result) )
763                 {
764                   if ( Options.verbose_flag )
765                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
766                   
767                   if ( Options.encrypt_header_flag )
768                     FrameBuffer.PlaintextOffset(0);
769                 }
770             }
771
772           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
773             {
774               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
775
776               // The Writer class will forward the last block of ciphertext
777               // to the encryption context for use as the IV for the next
778               // frame. If you want to use non-sequitur IV values, un-comment
779               // the following  line of code.
780               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
781               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
782             }
783         }
784
785       if ( result == RESULT_ENDOFFILE )
786         result = RESULT_OK;
787     }
788
789   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
790     result = Writer.Finalize();
791
792   return result;
793 }
794
795 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
796 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
797 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
798 //
799 Result_t
800 read_JP2K_file(CommandOptions& Options)
801 {
802   AESDecContext*     Context = 0;
803   HMACContext*       HMAC = 0;
804   JP2K::MXFReader    Reader;
805   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
806   ui32_t             frame_count = 0;
807
808   Result_t result = Reader.OpenRead(Options.filenames[0]);
809
810   if ( ASDCP_SUCCESS(result) )
811     {
812       JP2K::PictureDescriptor PDesc;
813       Reader.FillPictureDescriptor(PDesc);
814
815       frame_count = PDesc.ContainerDuration;
816
817       if ( Options.verbose_flag )
818         {
819           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
820           JP2K::PictureDescriptorDump(PDesc);
821         }
822     }
823
824   if ( ASDCP_SUCCESS(result) && Options.key_flag )
825     {
826       Context = new AESDecContext;
827       result = Context->InitKey(Options.key_value);
828
829       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
830         {
831           WriterInfo Info;
832           Reader.FillWriterInfo(Info);
833
834           if ( Info.UsesHMAC )
835             {
836               HMAC = new HMACContext;
837               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
838             }
839           else
840             {
841               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
842             }
843         }
844     }
845
846   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
847   if ( last_frame > frame_count )
848     last_frame = frame_count;
849
850   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
851     {
852       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
853
854       if ( ASDCP_SUCCESS(result) )
855         {
856           Kumu::FileWriter OutFile;
857           char filename[256];
858           ui32_t write_count;
859           snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
860           result = OutFile.OpenWrite(filename);
861
862           if ( ASDCP_SUCCESS(result) )
863             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
864
865           if ( Options.verbose_flag )
866             FrameBuffer.Dump(stderr, Options.fb_dump_size);
867         }
868     }
869
870   return result;
871 }
872
873 //------------------------------------------------------------------------------------------
874 // PCM essence
875
876
877 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
878 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
879 //
880 Result_t
881 write_PCM_file(CommandOptions& Options)
882 {
883   AESEncContext*    Context = 0;
884   HMACContext*      HMAC = 0;
885   PCMParserList     Parser;
886   PCM::MXFWriter    Writer;
887   PCM::FrameBuffer  FrameBuffer;
888   PCM::AudioDescriptor ADesc;
889   Rational          PictureRate = Options.PictureRate();
890   byte_t            IV_buf[CBC_BLOCK_SIZE];
891   Kumu::FortunaRNG  RNG;
892
893   // set up essence parser
894   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
895
896   // set up MXF writer
897   if ( ASDCP_SUCCESS(result) )
898     {
899       Parser.FillAudioDescriptor(ADesc);
900
901       ADesc.SampleRate = PictureRate;
902       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
903
904       if ( Options.verbose_flag )
905         {
906           fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
907                   Options.szPictureRate(),
908                   PCM::CalcSamplesPerFrame(ADesc));
909           fputs("AudioDescriptor:\n", stderr);
910           PCM::AudioDescriptorDump(ADesc);
911         }
912     }
913
914   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
915     {
916       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
917       Kumu::GenRandomUUID(Info.AssetUUID);
918
919       if ( Options.use_smpte_labels )
920         {
921           Info.LabelSetType = LS_MXF_SMPTE;
922           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
923         }
924
925       // configure encryption
926       if( Options.key_flag )
927         {
928           Kumu::GenRandomUUID(Info.ContextID);
929           Info.EncryptedEssence = true;
930
931           if ( Options.key_id_flag )
932             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
933           else
934             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
935
936           Context = new AESEncContext;
937           result = Context->InitKey(Options.key_value);
938
939           if ( ASDCP_SUCCESS(result) )
940             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
941
942           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
943             {
944               Info.UsesHMAC = true;
945               HMAC = new HMACContext;
946               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
947             }
948         }
949
950       if ( ASDCP_SUCCESS(result) )
951         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
952     }
953
954   if ( ASDCP_SUCCESS(result) )
955     {
956       result = Parser.Reset();
957       ui32_t duration = 0;
958
959       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
960         {
961           result = Parser.ReadFrame(FrameBuffer);
962
963           if ( ASDCP_SUCCESS(result) )
964             {
965               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
966                 {
967                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
968                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
969                   result = RESULT_ENDOFFILE;
970                   continue;
971                 }
972
973               if ( Options.verbose_flag )
974                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
975
976               if ( ! Options.no_write_flag )
977                 {
978                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
979
980                   // The Writer class will forward the last block of ciphertext
981                   // to the encryption context for use as the IV for the next
982                   // frame. If you want to use non-sequitur IV values, un-comment
983                   // the following  line of code.
984                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
985                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
986                 }
987             }
988         }
989
990       if ( result == RESULT_ENDOFFILE )
991         result = RESULT_OK;
992     }
993
994   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
995     result = Writer.Finalize();
996
997   return result;
998 }
999
1000 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1001 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1002 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1003 //
1004 Result_t
1005 read_PCM_file(CommandOptions& Options)
1006 {
1007   AESDecContext*     Context = 0;
1008   HMACContext*       HMAC = 0;
1009   PCM::MXFReader     Reader;
1010   PCM::FrameBuffer   FrameBuffer;
1011   WavFileWriter      OutWave;
1012   PCM::AudioDescriptor ADesc;
1013   ui32_t last_frame = 0;
1014
1015   Result_t result = Reader.OpenRead(Options.filenames[0]);
1016
1017   if ( ASDCP_SUCCESS(result) )
1018     {
1019       Reader.FillAudioDescriptor(ADesc);
1020
1021       if ( ADesc.SampleRate != EditRate_23_98
1022            && ADesc.SampleRate != EditRate_24
1023            && ADesc.SampleRate != EditRate_48 )
1024         ADesc.SampleRate = Options.PictureRate();
1025
1026       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1027
1028       if ( Options.verbose_flag )
1029         PCM::AudioDescriptorDump(ADesc);
1030     }
1031
1032   if ( ASDCP_SUCCESS(result) )
1033     {
1034       last_frame = ADesc.ContainerDuration;
1035
1036       if ( Options.duration > 0 && Options.duration < last_frame )
1037         last_frame = Options.duration;
1038
1039       if ( Options.start_frame > 0 )
1040         {
1041           if ( Options.start_frame > ADesc.ContainerDuration )
1042             {
1043               fprintf(stderr, "Start value greater than file duration.\n");
1044               return RESULT_FAIL;
1045             }
1046
1047           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1048         }
1049
1050       ADesc.ContainerDuration = last_frame - Options.start_frame;
1051       OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1052     }
1053
1054   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1055     {
1056       Context = new AESDecContext;
1057       result = Context->InitKey(Options.key_value);
1058
1059       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1060         {
1061           WriterInfo Info;
1062           Reader.FillWriterInfo(Info);
1063
1064           if ( Info.UsesHMAC )
1065             {
1066               HMAC = new HMACContext;
1067               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1068             }
1069           else
1070             {
1071               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1072             }
1073         }
1074     }
1075
1076   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1077     {
1078       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1079
1080       if ( ASDCP_SUCCESS(result) )
1081         {
1082           if ( Options.verbose_flag )
1083             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1084
1085           result = OutWave.WriteFrame(FrameBuffer);
1086         }
1087     }
1088
1089   return result;
1090 }
1091
1092
1093 //------------------------------------------------------------------------------------------
1094 //
1095
1096 //
1097 // These classes wrap the irregular names in the asdcplib API
1098 // so that I can use a template to simplify the implementation
1099 // of show_file_info()
1100
1101 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1102 {
1103  public:
1104   void FillDescriptor(MPEG2::MXFReader& Reader) {
1105     Reader.FillVideoDescriptor(*this);
1106   }
1107
1108   void Dump(FILE* stream) {
1109     MPEG2::VideoDescriptorDump(*this, stream);
1110   }
1111 };
1112
1113 class MyPictureDescriptor : public JP2K::PictureDescriptor
1114 {
1115  public:
1116   void FillDescriptor(JP2K::MXFReader& Reader) {
1117     Reader.FillPictureDescriptor(*this);
1118   }
1119
1120   void Dump(FILE* stream) {
1121     JP2K::PictureDescriptorDump(*this, stream);
1122   }
1123 };
1124
1125 class MyAudioDescriptor : public PCM::AudioDescriptor
1126 {
1127  public:
1128   void FillDescriptor(PCM::MXFReader& Reader) {
1129     Reader.FillAudioDescriptor(*this);
1130   }
1131
1132   void Dump(FILE* stream) {
1133     PCM::AudioDescriptorDump(*this, stream);
1134   }
1135 };
1136
1137
1138 // MSVC didn't like the function template, so now it's a static class method
1139 template<class ReaderT, class DescriptorT>
1140 class FileInfoWrapper
1141 {
1142 public:
1143   static void file_info(CommandOptions& Options, FILE* stream = 0)
1144   {
1145     if ( stream == 0 )
1146       stream = stdout;
1147
1148     if ( Options.verbose_flag || Options.showheader_flag )
1149       {
1150         ReaderT     Reader;
1151         Result_t result = Reader.OpenRead(Options.filenames[0]);
1152
1153         if ( ASDCP_SUCCESS(result) )
1154           {
1155             if ( Options.showheader_flag )
1156               Reader.DumpHeaderMetadata(stream);
1157
1158             WriterInfo WI;
1159             Reader.FillWriterInfo(WI);
1160             WriterInfoDump(WI, stream);
1161
1162             DescriptorT Desc;
1163             Desc.FillDescriptor(Reader);
1164             Desc.Dump(stream);
1165
1166             if ( Options.showindex_flag )
1167               Reader.DumpIndex(stream);
1168           }
1169         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1170           {
1171             Reader.DumpHeaderMetadata(stream);
1172           }
1173       }
1174   }
1175 };
1176
1177 // Read header metadata from an ASDCP file
1178 //
1179 Result_t
1180 show_file_info(CommandOptions& Options)
1181 {
1182   EssenceType_t EssenceType;
1183   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1184
1185   if ( ASDCP_FAILURE(result) )
1186     return result;
1187
1188   if ( EssenceType == ESS_MPEG2_VES )
1189     {
1190       fputs("File essence type is MPEG2 video.\n", stdout);
1191       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1192     }
1193   else if ( EssenceType == ESS_PCM_24b_48k )
1194     {
1195       fputs("File essence type is PCM audio.\n", stdout);
1196       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1197     }
1198   else if ( EssenceType == ESS_JPEG_2000 )
1199     {
1200       fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1201       FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1202     }
1203   else
1204     {
1205       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1206       Kumu::FileReader   Reader;
1207       MXF::OPAtomHeader TestHeader;
1208
1209       result = Reader.OpenRead(Options.filenames[0]);
1210
1211       if ( ASDCP_SUCCESS(result) )
1212         result = TestHeader.InitFromFile(Reader); // test UL and OP
1213
1214       if ( ASDCP_SUCCESS(result) )
1215         {
1216           TestHeader.Partition::Dump();
1217
1218           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1219             ID->Dump();
1220           else
1221             fputs("File contains no Identification object.\n", stdout);
1222
1223           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1224             SP->Dump();
1225           else
1226             fputs("File contains no SourcePackage object.\n", stdout);
1227         }
1228       else
1229         {
1230           fputs("File is not MXF.\n", stdout);
1231         }
1232     }
1233
1234   return result;
1235 }
1236
1237
1238 //
1239 Result_t
1240 digest_file(const char* filename)
1241 {
1242   using namespace Kumu;
1243
1244   ASDCP_TEST_NULL_STR(filename);
1245   FileReader Reader;
1246   SHA_CTX Ctx;
1247   SHA1_Init(&Ctx);
1248   ByteString Buf(8192);
1249
1250   Result_t result = Reader.OpenRead(filename);
1251
1252   while ( ASDCP_SUCCESS(result) )
1253     {
1254       ui32_t read_count = 0;
1255       result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1256
1257       if ( result == RESULT_ENDOFFILE )
1258         {
1259           result = RESULT_OK;
1260           break;
1261         }
1262
1263       if ( ASDCP_SUCCESS(result) )
1264         SHA1_Update(&Ctx, Buf.Data(), read_count);
1265     }
1266
1267   if ( ASDCP_SUCCESS(result) )
1268     {
1269       const ui32_t sha_len = 20;
1270       byte_t bin_buf[sha_len];
1271       char sha_buf[64];
1272       SHA1_Final(bin_buf, &Ctx);
1273
1274       fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1275     }
1276
1277   return result;
1278 }
1279
1280 //
1281 int
1282 main(int argc, const char** argv)
1283 {
1284   Result_t result = RESULT_OK;
1285   CommandOptions Options(argc, argv);
1286
1287   if ( Options.version_flag )
1288     banner();
1289
1290   if ( Options.help_flag )
1291     usage();
1292
1293   if ( Options.version_flag || Options.help_flag )
1294     return 0;
1295
1296   if ( Options.error_flag )
1297     {
1298       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1299       return 3;
1300     }
1301
1302   if ( Options.mode == MMT_INFO )
1303     {
1304       result = show_file_info(Options);
1305     }
1306   else if ( Options.mode == MMT_GOP_START )
1307     {
1308       result = gop_start_test(Options);
1309     }
1310   else if ( Options.mode == MMT_GEN_KEY )
1311     {
1312       Kumu::FortunaRNG RNG;
1313       byte_t bin_buf[KeyLen];
1314       char   str_buf[40];
1315
1316       RNG.FillRandom(bin_buf, KeyLen);
1317       printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1318     }
1319   else if ( Options.mode == MMT_GEN_ID )
1320     {
1321       UUID TmpID;
1322       Kumu::GenRandomValue(TmpID);
1323       char   str_buf[40];
1324       printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1325     }
1326   else if ( Options.mode == MMT_DIGEST )
1327     {
1328       for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1329         result = digest_file(Options.filenames[i]);
1330     }
1331   else if ( Options.mode == MMT_EXTRACT )
1332     {
1333       EssenceType_t EssenceType;
1334       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1335
1336       if ( ASDCP_SUCCESS(result) )
1337         {
1338           switch ( EssenceType )
1339             {
1340             case ESS_MPEG2_VES:
1341               result = read_MPEG2_file(Options);
1342               break;
1343
1344             case ESS_JPEG_2000:
1345               result = read_JP2K_file(Options);
1346               break;
1347
1348             case ESS_PCM_24b_48k:
1349               result = read_PCM_file(Options);
1350               break;
1351
1352             default:
1353               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1354               return 5;
1355             }
1356         }
1357     }
1358   else if ( Options.mode == MMT_CREATE )
1359     {
1360       if ( Options.do_repeat && ! Options.duration_flag )
1361         {
1362           fputs("Option -R requires -d <duration>\n", stderr);
1363           return RESULT_FAIL;
1364         }
1365
1366       EssenceType_t EssenceType;
1367       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1368
1369       if ( ASDCP_SUCCESS(result) )
1370         {
1371           switch ( EssenceType )
1372             {
1373             case ESS_MPEG2_VES:
1374               result = write_MPEG2_file(Options);
1375               break;
1376
1377             case ESS_JPEG_2000:
1378               result = write_JP2K_file(Options);
1379               break;
1380
1381             case ESS_PCM_24b_48k:
1382               result = write_PCM_file(Options);
1383               break;
1384
1385             default:
1386               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1387                       Options.filenames[0]);
1388               return 5;
1389             }
1390         }
1391     }
1392
1393   if ( ASDCP_FAILURE(result) )
1394     {
1395       fputs("Program stopped on error.\n", stderr);
1396
1397       if ( result != RESULT_FAIL )
1398         {
1399           fputs(result, stderr);
1400           fputc('\n', stderr);
1401         }
1402
1403       return 1;
1404     }
1405
1406   return 0;
1407 }
1408
1409
1410 //
1411 // end asdcp-test.cpp
1412 //