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