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