release candidate
[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.EditRate = 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.EditRate != EditRate_23_98
1349            && ADesc.EditRate != EditRate_24
1350            && ADesc.EditRate != EditRate_25
1351            && ADesc.EditRate != EditRate_48
1352            && ADesc.EditRate != EditRate_50
1353            && ADesc.EditRate != EditRate_60 )
1354         ADesc.EditRate = Options.PictureRate();
1355
1356       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1357
1358       if ( Options.verbose_flag )
1359         PCM::AudioDescriptorDump(ADesc);
1360     }
1361
1362   if ( ASDCP_SUCCESS(result) )
1363     {
1364       last_frame = ADesc.ContainerDuration;
1365
1366       if ( Options.duration > 0 && Options.duration < last_frame )
1367         last_frame = Options.duration;
1368
1369       if ( Options.start_frame > 0 )
1370         {
1371           if ( Options.start_frame > ADesc.ContainerDuration )
1372             {
1373               fprintf(stderr, "Start value greater than file duration.\n");
1374               return RESULT_FAIL;
1375             }
1376
1377           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1378         }
1379
1380       ADesc.ContainerDuration = last_frame - Options.start_frame;
1381       OutWave.OpenWrite(ADesc, Options.file_root,
1382                         ( Options.split_wav ? WavFileWriter::ST_STEREO : 
1383                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1384     }
1385
1386   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1387     {
1388       Context = new AESDecContext;
1389       result = Context->InitKey(Options.key_value);
1390
1391       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1392         {
1393           WriterInfo Info;
1394           Reader.FillWriterInfo(Info);
1395
1396           if ( Info.UsesHMAC )
1397             {
1398               HMAC = new HMACContext;
1399               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1400             }
1401           else
1402             {
1403               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1404             }
1405         }
1406     }
1407
1408   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1409     {
1410       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1411
1412       if ( ASDCP_SUCCESS(result) )
1413         {
1414           if ( Options.verbose_flag )
1415             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1416
1417           result = OutWave.WriteFrame(FrameBuffer);
1418         }
1419     }
1420
1421   return result;
1422 }
1423
1424
1425 //------------------------------------------------------------------------------------------
1426 // TimedText essence
1427
1428
1429 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1430 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1431 //
1432 Result_t
1433 write_timed_text_file(CommandOptions& Options)
1434 {
1435   AESEncContext*    Context = 0;
1436   HMACContext*      HMAC = 0;
1437   TimedText::DCSubtitleParser  Parser;
1438   TimedText::MXFWriter    Writer;
1439   TimedText::FrameBuffer  FrameBuffer;
1440   TimedText::TimedTextDescriptor TDesc;
1441   byte_t            IV_buf[CBC_BLOCK_SIZE];
1442   Kumu::FortunaRNG  RNG;
1443
1444   // set up essence parser
1445   Result_t result = Parser.OpenRead(Options.filenames[0]);
1446
1447   // set up MXF writer
1448   if ( ASDCP_SUCCESS(result) )
1449     {
1450       Parser.FillTimedTextDescriptor(TDesc);
1451       FrameBuffer.Capacity(2*Kumu::Megabyte);
1452
1453       if ( Options.verbose_flag )
1454         {
1455           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1456           TimedText::DescriptorDump(TDesc);
1457         }
1458     }
1459
1460   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1461     {
1462       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1463       if ( Options.asset_id_flag )
1464         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1465       else
1466         Kumu::GenRandomUUID(Info.AssetUUID);
1467
1468       if ( Options.use_smpte_labels )
1469         {
1470           Info.LabelSetType = LS_MXF_SMPTE;
1471           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1472         }
1473
1474       // configure encryption
1475       if( Options.key_flag )
1476         {
1477           Kumu::GenRandomUUID(Info.ContextID);
1478           Info.EncryptedEssence = true;
1479
1480           if ( Options.key_id_flag )
1481             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1482           else
1483             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1484
1485           Context = new AESEncContext;
1486           result = Context->InitKey(Options.key_value);
1487
1488           if ( ASDCP_SUCCESS(result) )
1489             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1490
1491           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1492             {
1493               Info.UsesHMAC = true;
1494               HMAC = new HMACContext;
1495               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1496             }
1497         }
1498
1499       if ( ASDCP_SUCCESS(result) )
1500         result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1501     }
1502
1503   if ( ASDCP_FAILURE(result) )
1504     return result;
1505
1506   std::string XMLDoc;
1507   TimedText::ResourceList_t::const_iterator ri;
1508
1509   result = Parser.ReadTimedTextResource(XMLDoc);
1510
1511   if ( ASDCP_SUCCESS(result) )
1512     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1513
1514   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1515     {
1516       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1517
1518       if ( ASDCP_SUCCESS(result) )
1519         {
1520           if ( Options.verbose_flag )
1521             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1522
1523           if ( ! Options.no_write_flag )
1524             {
1525               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1526               
1527               // The Writer class will forward the last block of ciphertext
1528               // to the encryption context for use as the IV for the next
1529               // frame. If you want to use non-sequitur IV values, un-comment
1530               // the following  line of code.
1531               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1532               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1533             }
1534         }
1535
1536       if ( result == RESULT_ENDOFFILE )
1537         result = RESULT_OK;
1538     }
1539
1540   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1541     result = Writer.Finalize();
1542
1543   return result;
1544 }
1545
1546
1547 // Read one or more timed text streams from a plaintext ASDCP file
1548 // Read one or more timed text streams from a ciphertext ASDCP file
1549 // Read one or more timed text streams from a ciphertext ASDCP file
1550 //
1551 Result_t
1552 read_timed_text_file(CommandOptions& Options)
1553 {
1554   AESDecContext*     Context = 0;
1555   HMACContext*       HMAC = 0;
1556   TimedText::MXFReader     Reader;
1557   TimedText::FrameBuffer   FrameBuffer;
1558   TimedText::TimedTextDescriptor TDesc;
1559
1560   Result_t result = Reader.OpenRead(Options.filenames[0]);
1561
1562   if ( ASDCP_SUCCESS(result) )
1563     {
1564       Reader.FillTimedTextDescriptor(TDesc);
1565       FrameBuffer.Capacity(2*Kumu::Megabyte);
1566
1567       if ( Options.verbose_flag )
1568         TimedText::DescriptorDump(TDesc);
1569     }
1570
1571   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1572     {
1573       Context = new AESDecContext;
1574       result = Context->InitKey(Options.key_value);
1575
1576       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1577         {
1578           WriterInfo Info;
1579           Reader.FillWriterInfo(Info);
1580
1581           if ( Info.UsesHMAC )
1582             {
1583               HMAC = new HMACContext;
1584               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1585             }
1586           else
1587             {
1588               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1589             }
1590         }
1591     }
1592
1593   if ( ASDCP_FAILURE(result) )
1594     return result;
1595
1596   std::string XMLDoc;
1597   TimedText::ResourceList_t::const_iterator ri;
1598
1599   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1600
1601   // do something with the XML here
1602   fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1603
1604   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1605     {
1606       result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1607
1608       if ( ASDCP_SUCCESS(result) )
1609         {
1610           //      if ( Options.verbose_flag )
1611             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1612
1613           // do something with the resource data here
1614         }
1615     }
1616
1617   return result;
1618 }
1619
1620 //------------------------------------------------------------------------------------------
1621 //
1622
1623 //
1624 // These classes wrap the irregular names in the asdcplib API
1625 // so that I can use a template to simplify the implementation
1626 // of show_file_info()
1627
1628 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1629 {
1630  public:
1631   void FillDescriptor(MPEG2::MXFReader& Reader) {
1632     Reader.FillVideoDescriptor(*this);
1633   }
1634
1635   void Dump(FILE* stream) {
1636     MPEG2::VideoDescriptorDump(*this, stream);
1637   }
1638 };
1639
1640 class MyPictureDescriptor : public JP2K::PictureDescriptor
1641 {
1642  public:
1643   void FillDescriptor(JP2K::MXFReader& Reader) {
1644     Reader.FillPictureDescriptor(*this);
1645   }
1646
1647   void Dump(FILE* stream) {
1648     JP2K::PictureDescriptorDump(*this, stream);
1649   }
1650 };
1651
1652 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1653 {
1654  public:
1655   void FillDescriptor(JP2K::MXFSReader& Reader) {
1656     Reader.FillPictureDescriptor(*this);
1657   }
1658
1659   void Dump(FILE* stream) {
1660     JP2K::PictureDescriptorDump(*this, stream);
1661   }
1662 };
1663
1664 class MyAudioDescriptor : public PCM::AudioDescriptor
1665 {
1666  public:
1667   void FillDescriptor(PCM::MXFReader& Reader) {
1668     Reader.FillAudioDescriptor(*this);
1669   }
1670
1671   void Dump(FILE* stream) {
1672     PCM::AudioDescriptorDump(*this, stream);
1673   }
1674 };
1675
1676 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1677 {
1678  public:
1679   void FillDescriptor(TimedText::MXFReader& Reader) {
1680     Reader.FillTimedTextDescriptor(*this);
1681   }
1682
1683   void Dump(FILE* stream) {
1684     TimedText::DescriptorDump(*this, stream);
1685   }
1686 };
1687
1688 // MSVC didn't like the function template, so now it's a static class method
1689 template<class ReaderT, class DescriptorT>
1690 class FileInfoWrapper
1691 {
1692 public:
1693   static Result_t
1694   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1695   {
1696     assert(type_string);
1697     if ( stream == 0 )
1698       stream = stdout;
1699
1700     Result_t result = RESULT_OK;
1701
1702     if ( Options.verbose_flag || Options.showheader_flag )
1703       {
1704         ReaderT     Reader;
1705         result = Reader.OpenRead(Options.filenames[0]);
1706
1707         if ( ASDCP_SUCCESS(result) )
1708           {
1709             fprintf(stdout, "File essence type is %s.\n", type_string);
1710
1711             if ( Options.showheader_flag )
1712               Reader.DumpHeaderMetadata(stream);
1713
1714             WriterInfo WI;
1715             Reader.FillWriterInfo(WI);
1716             WriterInfoDump(WI, stream);
1717
1718             DescriptorT Desc;
1719             Desc.FillDescriptor(Reader);
1720             Desc.Dump(stream);
1721
1722             if ( Options.showindex_flag )
1723               Reader.DumpIndex(stream);
1724           }
1725         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1726           {
1727             Reader.DumpHeaderMetadata(stream);
1728           }
1729       }
1730
1731     return result;
1732   }
1733 };
1734
1735 // Read header metadata from an ASDCP file
1736 //
1737 Result_t
1738 show_file_info(CommandOptions& Options)
1739 {
1740   EssenceType_t EssenceType;
1741   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1742
1743   if ( ASDCP_FAILURE(result) )
1744     return result;
1745
1746   if ( EssenceType == ESS_MPEG2_VES )
1747     result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1748
1749   else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1750     result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1751
1752   else if ( EssenceType == ESS_JPEG_2000 )
1753     {
1754       if ( Options.stereo_image_flag )
1755         result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1756         MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1757
1758       else
1759         result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1760         MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1761     }
1762   else if ( EssenceType == ESS_JPEG_2000_S )
1763     result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1764     MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1765
1766   else if ( EssenceType == ESS_TIMED_TEXT )
1767     result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1768
1769   else
1770     {
1771       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1772       Kumu::FileReader   Reader;
1773       const Dictionary* Dict = &DefaultCompositeDict();
1774       MXF::OPAtomHeader TestHeader(Dict);
1775
1776       result = Reader.OpenRead(Options.filenames[0]);
1777
1778       if ( ASDCP_SUCCESS(result) )
1779         result = TestHeader.InitFromFile(Reader); // test UL and OP
1780
1781       if ( ASDCP_SUCCESS(result) )
1782         {
1783           TestHeader.Partition::Dump(stdout);
1784
1785           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1786             ID->Dump(stdout);
1787           else
1788             fputs("File contains no Identification object.\n", stdout);
1789
1790           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1791             SP->Dump(stdout);
1792           else
1793             fputs("File contains no SourcePackage object.\n", stdout);
1794         }
1795       else
1796         {
1797           fputs("File is not MXF.\n", stdout);
1798         }
1799     }
1800
1801   return result;
1802 }
1803
1804
1805 //
1806 Result_t
1807 digest_file(const char* filename)
1808 {
1809   using namespace Kumu;
1810
1811   ASDCP_TEST_NULL_STR(filename);
1812   FileReader Reader;
1813   SHA_CTX Ctx;
1814   SHA1_Init(&Ctx);
1815   ByteString Buf(8192);
1816
1817   Result_t result = Reader.OpenRead(filename);
1818
1819   while ( ASDCP_SUCCESS(result) )
1820     {
1821       ui32_t read_count = 0;
1822       result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1823
1824       if ( result == RESULT_ENDOFFILE )
1825         {
1826           result = RESULT_OK;
1827           break;
1828         }
1829
1830       if ( ASDCP_SUCCESS(result) )
1831         SHA1_Update(&Ctx, Buf.Data(), read_count);
1832     }
1833
1834   if ( ASDCP_SUCCESS(result) )
1835     {
1836       const ui32_t sha_len = 20;
1837       byte_t bin_buf[sha_len];
1838       char sha_buf[64];
1839       SHA1_Final(bin_buf, &Ctx);
1840
1841       fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1842     }
1843
1844   return result;
1845 }
1846
1847 //
1848 int
1849 main(int argc, const char** argv)
1850 {
1851   Result_t result = RESULT_OK;
1852   char     str_buf[64];
1853   CommandOptions Options(argc, argv);
1854
1855   if ( Options.version_flag )
1856     banner();
1857
1858   if ( Options.help_flag )
1859     usage();
1860
1861   if ( Options.version_flag || Options.help_flag )
1862     return 0;
1863
1864   if ( Options.error_flag )
1865     {
1866       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1867       return 3;
1868     }
1869
1870   if ( Options.mode == MMT_INFO )
1871     {
1872       result = show_file_info(Options);
1873     }
1874   else if ( Options.mode == MMT_GOP_START )
1875     {
1876       result = gop_start_test(Options);
1877     }
1878   else if ( Options.mode == MMT_GEN_KEY )
1879     {
1880       Kumu::FortunaRNG RNG;
1881       byte_t bin_buf[KeyLen];
1882
1883       RNG.FillRandom(bin_buf, KeyLen);
1884       printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1885     }
1886   else if ( Options.mode == MMT_GEN_ID )
1887     {
1888       UUID TmpID;
1889       Kumu::GenRandomValue(TmpID);
1890       printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1891     }
1892   else if ( Options.mode == MMT_DIGEST )
1893     {
1894       for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1895         result = digest_file(Options.filenames[i]);
1896     }
1897   else if ( Options.mode == MMT_UL_LIST )
1898     {
1899       if ( Options.use_smpte_labels )
1900         DefaultSMPTEDict().Dump(stdout);
1901       else
1902         DefaultInteropDict().Dump(stdout);
1903     }
1904   else if ( Options.mode == MMT_EXTRACT )
1905     {
1906       EssenceType_t EssenceType;
1907       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1908
1909       if ( ASDCP_SUCCESS(result) )
1910         {
1911           switch ( EssenceType )
1912             {
1913             case ESS_MPEG2_VES:
1914               result = read_MPEG2_file(Options);
1915               break;
1916
1917             case ESS_JPEG_2000:
1918               if ( Options.stereo_image_flag )
1919                 result = read_JP2K_S_file(Options);
1920               else
1921                 result = read_JP2K_file(Options);
1922               break;
1923
1924             case ESS_JPEG_2000_S:
1925               result = read_JP2K_S_file(Options);
1926               break;
1927
1928             case ESS_PCM_24b_48k:
1929             case ESS_PCM_24b_96k:
1930               result = read_PCM_file(Options);
1931               break;
1932
1933             case ESS_TIMED_TEXT:
1934               result = read_timed_text_file(Options);
1935               break;
1936
1937             default:
1938               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1939               return 5;
1940             }
1941         }
1942     }
1943   else if ( Options.mode == MMT_CREATE )
1944     {
1945       if ( Options.do_repeat && ! Options.duration_flag )
1946         {
1947           fputs("Option -R requires -d <duration>\n", stderr);
1948           return RESULT_FAIL;
1949         }
1950
1951       EssenceType_t EssenceType;
1952       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1953
1954       if ( ASDCP_SUCCESS(result) )
1955         {
1956           switch ( EssenceType )
1957             {
1958             case ESS_MPEG2_VES:
1959               result = write_MPEG2_file(Options);
1960               break;
1961
1962             case ESS_JPEG_2000:
1963               if ( Options.stereo_image_flag )
1964                 result = write_JP2K_S_file(Options);
1965
1966               else
1967                 result = write_JP2K_file(Options);
1968
1969               break;
1970
1971             case ESS_PCM_24b_48k:
1972             case ESS_PCM_24b_96k:
1973               result = write_PCM_file(Options);
1974               break;
1975
1976             case ESS_TIMED_TEXT:
1977               result = write_timed_text_file(Options);
1978               break;
1979
1980             default:
1981               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1982                       Options.filenames[0]);
1983               return 5;
1984             }
1985         }
1986     }
1987   else
1988     {
1989       fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
1990       return 6;
1991     }
1992
1993   if ( ASDCP_FAILURE(result) )
1994     {
1995       fputs("Program stopped on error.\n", stderr);
1996
1997       if ( result == RESULT_SFORMAT )
1998         {
1999           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2000         }
2001       else if ( result != RESULT_FAIL )
2002         {
2003           fputs(result, stderr);
2004           fputc('\n', stderr);
2005         }
2006
2007       return 1;
2008     }
2009
2010   return 0;
2011 }
2012
2013
2014 //
2015 // end asdcp-test.cpp
2016 //