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