Allow fractional frames per second when computing Time from frames.
[libdcp.git] / asdcplib / src / asdcp-wrap.cpp
1 /*
2 Copyright (c) 2003-2012, 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-wrap.cpp
28     \version $Id: asdcp-wrap.cpp,v 1.5 2012/03/07 18:47:02 mikey Exp $       
29     \brief   AS-DCP file manipulation utility
30
31   This program wraps d-cinema essence (picture, sound or text) in t an AS-DCP
32   MXF file.
33
34   For more information about asdcplib, please refer to the header file AS_DCP.h
35
36   WARNING: While the asdcplib library attempts to provide a complete and secure
37   implementation of the cryptographic features of the AS-DCP file formats, this
38   unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
39   production environment without some modification.
40
41   In particular, this program uses weak IV generation and externally generated
42   plaintext keys. These shortcomings exist because cryptographic-quality
43   random number generation and key management are outside the scope of the
44   asdcplib library. Developers using asdcplib for commercial implementations
45   claiming SMPTE conformance are expected to provide proper implementations of
46   these features.
47 */
48
49 #include <KM_fileio.h>
50 #include <KM_prng.h>
51 #include <AS_DCP.h>
52 #include <PCMParserList.h>
53 #include <Metadata.h>
54
55 using namespace ASDCP;
56
57 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
58
59 //------------------------------------------------------------------------------------------
60 //
61 // command line option parser class
62
63 static const char* PROGRAM_NAME = "asdcp-wrap";  // program name for messages
64
65 // local program identification info written to file headers
66 class MyInfo : public WriterInfo
67 {
68 public:
69   MyInfo()
70   {
71       static byte_t default_ProductUUID_Data[UUIDlen] =
72       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
73         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
74       
75       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
76       CompanyName = "WidgetCo";
77       ProductName = "asdcp-wrap";
78       ProductVersion = ASDCP::Version();
79   }
80 } s_MyInfo;
81
82
83
84 // Increment the iterator, test for an additional non-option command line argument.
85 // Causes the caller to return if there are no remaining arguments or if the next
86 // argument begins with '-'.
87 #define TEST_EXTRA_ARG(i,c)                                             \
88   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
89     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
90     return;                                                             \
91   }
92
93 //
94 void
95 banner(FILE* stream = stdout)
96 {
97   fprintf(stream, "\n\
98 %s (asdcplib %s)\n\n\
99 Copyright (c) 2003-2012 John Hurst\n\n\
100 asdcplib may be copied only under the terms of the license found at\n\
101 the top of every file in the asdcplib distribution kit.\n\n\
102 Specify the -h (help) option for further information about %s\n\n",
103           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
104 }
105
106 //
107 void
108 usage(FILE* stream = stdout)
109 {
110   fprintf(stream, "\
111 USAGE: %s [-h|-help] [-V]\n\
112 \n\
113        %s [-3] [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
114           [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
115           [-l <label>] [-L] [-M] [-p <frame-rate>] [-s <num>] [-v] [-W]\n\
116           [-z|-Z] <input-file>+ <output-file>\n\n",
117           PROGRAM_NAME, PROGRAM_NAME);
118
119   fprintf(stream, "\
120 Options:\n\
121   -3                - Create a stereoscopic image file. Expects two\n\
122                       directories of JP2K codestreams (directories must have\n\
123                       an equal number of frames; left eye is first).\n\
124   -C <UL>           - Set ChannelAssignment UL value\n\
125   -h | -help        - Show help\n\
126   -V                - Show version information\n\
127   -e                - Encrypt MPEG or JP2K headers (default)\n\
128   -E                - Do not encrypt MPEG or JP2K headers\n\
129   -j <key-id-str>   - Write key ID instead of creating a random value\n\
130   -k <key-string>   - Use key for ciphertext operations\n\
131   -M                - Do not create HMAC values when writing\n\
132   -a <UUID>         - Specify the Asset ID of a file (with -c)\n\
133   -b <buffer-size>  - Specify size in bytes of picture frame buffer.\n\
134                       Defaults to 4,194,304 (4MB)\n\
135   -d <duration>     - Number of frames to process, default all\n\
136   -f <start-frame>  - Starting frame number, default 0\n\
137   -l <label>        - Use given channel format label when writing MXF sound\n\
138                       files. SMPTE 429-2 labels: '5.1', '6.1', '7.1',\n\
139                       '7.1DS', 'WTF'\n\
140                       Default is no label (valid for Interop only).\n\
141   -L                - Write SMPTE UL values instead of MXF Interop\n\
142   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
143                       Use one of [23|24|25|30|48|50|60], 24 is default\n\
144   -v                - Verbose, prints informative messages to stderr\n\
145   -W                - Read input file only, do not write source file\n\
146   -z                - Fail if j2c inputs have unequal parameters (default)\n\
147   -Z                - Ignore unequal parameters in j2c inputs\n\
148 \n\
149   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
150          o All option arguments must be separated from the option by whitespace.\n\
151          o An argument of \"23\" to the -p option will be interpreted\n\
152            as 24000/1001 fps.\n\
153 \n");
154 }
155
156 //
157 PCM::ChannelFormat_t
158 decode_channel_fmt(const std::string& label_name)
159 {
160   if ( label_name == "5.1" )
161     return PCM::CF_CFG_1;
162
163   else if ( label_name == "6.1" )
164     return PCM::CF_CFG_2;
165   
166   else if ( label_name == "7.1" )
167     return PCM::CF_CFG_3;
168
169   else if ( label_name == "WTF" )
170     return PCM::CF_CFG_4;
171
172   else if ( label_name == "7.1DS" )
173     return PCM::CF_CFG_5;
174
175   fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
176   fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
177   return PCM::CF_NONE;
178 }
179
180 //
181 //
182 class CommandOptions
183 {
184   CommandOptions();
185
186 public:
187   bool   error_flag;     // true if the given options are in error or not complete
188   bool   key_flag;       // true if an encryption key was given
189   bool   asset_id_flag;  // true if an asset ID was given
190   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
191   bool   write_hmac;     // true if HMAC values are to be generated and written
192   ///  bool   read_hmac;      // true if HMAC values are to be validated
193   ///  bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
194   ///  bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
195   bool   verbose_flag;   // true if the verbose option was selected
196   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
197   ///  bool   showindex_flag; // true if index is to be displayed
198   ///  bool   showheader_flag; // true if MXF file header is to be displayed
199   bool   no_write_flag;  // true if no output files are to be written
200   bool   version_flag;   // true if the version display option was selected
201   bool   help_flag;      // true if the help display option was selected
202   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
203   ///  ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
204   ui32_t start_frame;    // frame number to begin processing
205   ui32_t duration;       // number of frames to be processed
206   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
207   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
208   ui32_t picture_rate;   // fps of picture when wrapping PCM
209   ui32_t fb_size;        // size of picture frame buffer
210   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
211   bool   key_id_flag;    // true if a key ID was given
212   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
213   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
214   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
215   std::string out_file; //
216   bool show_ul_values;    /// if true, dump the UL table before going tp work.
217   Kumu::PathList_t filenames;  // list of filenames to be processed
218   UL channel_assignment;
219
220   //
221   Rational PictureRate()
222   {
223     if ( picture_rate == 23 ) return EditRate_23_98;
224     if ( picture_rate == 24 ) return EditRate_24;
225     if ( picture_rate == 25 ) return EditRate_25;
226     if ( picture_rate == 30 ) return EditRate_30;
227     if ( picture_rate == 48 ) return EditRate_48;
228     if ( picture_rate == 50 ) return EditRate_50;
229     if ( picture_rate == 60 ) return EditRate_60;
230     if ( picture_rate == 96 ) return EditRate_96;
231     if ( picture_rate == 100 ) return EditRate_100;
232     if ( picture_rate == 120 ) return EditRate_120;
233     return EditRate_24;
234   }
235
236   //
237   const char* szPictureRate()
238   {
239     if ( picture_rate == 23 ) return "23.976";
240     if ( picture_rate == 24 ) return "24";
241     if ( picture_rate == 25 ) return "25";
242     if ( picture_rate == 30 ) return "30";
243     if ( picture_rate == 48 ) return "48";
244     if ( picture_rate == 50 ) return "50";
245     if ( picture_rate == 60 ) return "60";
246     if ( picture_rate == 96 ) return "96";
247     if ( picture_rate == 100 ) return "100";
248     if ( picture_rate == 120 ) return "120";
249     return "24";
250   }
251
252   //
253   CommandOptions(int argc, const char** argv) :
254     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
255     encrypt_header_flag(true), write_hmac(true),
256     verbose_flag(false), fb_dump_size(0),
257     no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
258     start_frame(0),
259     duration(0xffffffff), use_smpte_labels(false), j2c_pedantic(true),
260     fb_size(FRAME_BUFFER_SIZE),
261     channel_fmt(PCM::CF_NONE),
262     show_ul_values(false)
263   {
264     memset(key_value, 0, KeyLen);
265     memset(key_id_value, 0, UUIDlen);
266
267     for ( int i = 1; i < argc; i++ )
268       {
269
270         if ( (strcmp( argv[i], "-help") == 0) )
271           {
272             help_flag = true;
273             continue;
274           }
275          
276         if ( argv[i][0] == '-'
277              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
278              && argv[i][2] == 0 )
279           {
280             switch ( argv[i][1] )
281               {
282               case '3': stereo_image_flag = true; break;
283
284               case 'a':
285                 asset_id_flag = true;
286                 TEST_EXTRA_ARG(i, 'a');
287                 {
288                   ui32_t length;
289                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
290
291                   if ( length != UUIDlen )
292                     {
293                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
294                       return;
295                     }
296                 }
297                 break;
298
299               case 'b':
300                 TEST_EXTRA_ARG(i, 'b');
301                 fb_size = abs(atoi(argv[i]));
302
303                 if ( verbose_flag )
304                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
305
306                 break;
307
308               case 'C':
309                 TEST_EXTRA_ARG(i, 'U');
310                 if ( ! channel_assignment.DecodeHex(argv[i]) )
311                   {
312                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
313                     return;
314                   }
315                 break;
316
317               case 'd':
318                 TEST_EXTRA_ARG(i, 'd');
319                 duration = abs(atoi(argv[i]));
320                 break;
321
322               case 'E': encrypt_header_flag = false; break;
323               case 'e': encrypt_header_flag = true; break;
324
325               case 'f':
326                 TEST_EXTRA_ARG(i, 'f');
327                 start_frame = abs(atoi(argv[i]));
328                 break;
329
330               case 'h': help_flag = true; break;
331
332               case 'j': key_id_flag = true;
333                 TEST_EXTRA_ARG(i, 'j');
334                 {
335                   ui32_t length;
336                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
337
338                   if ( length != UUIDlen )
339                     {
340                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
341                       return;
342                     }
343                 }
344                 break;
345
346               case 'k': key_flag = true;
347                 TEST_EXTRA_ARG(i, 'k');
348                 {
349                   ui32_t length;
350                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
351
352                   if ( length != KeyLen )
353                     {
354                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
355                       return;
356                     }
357                 }
358                 break;
359
360               case 'l':
361                 TEST_EXTRA_ARG(i, 'l');
362                 channel_fmt = decode_channel_fmt(argv[i]);
363                 break;
364
365               case 'L': use_smpte_labels = true; break;
366               case 'M': write_hmac = false; break;
367
368               case 'p':
369                 TEST_EXTRA_ARG(i, 'p');
370                 picture_rate = abs(atoi(argv[i]));
371                 break;
372
373               case 'V': version_flag = true; break;
374               case 'v': verbose_flag = true; break;
375               case 'W': no_write_flag = true; break;
376               case 'Z': j2c_pedantic = false; break;
377               case 'z': j2c_pedantic = true; break;
378
379               default:
380                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
381                 return;
382               }
383           }
384         else
385           {
386
387             if ( argv[i][0] != '-' )
388               {
389                 filenames.push_back(argv[i]);
390               }
391             else
392               {
393                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
394                 return;
395               }
396           }
397       }
398
399     if ( help_flag || version_flag )
400       return;
401     
402     if ( filenames.size() < 2 )
403       {
404         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
405         return;
406       }
407
408     out_file = filenames.back();
409     filenames.pop_back();
410     error_flag = false;
411   }
412 };
413
414 //------------------------------------------------------------------------------------------
415 // MPEG2 essence
416
417 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
418 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
419 //
420 Result_t
421 write_MPEG2_file(CommandOptions& Options)
422 {
423   AESEncContext*     Context = 0;
424   HMACContext*       HMAC = 0;
425   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
426   MPEG2::Parser      Parser;
427   MPEG2::MXFWriter   Writer;
428   MPEG2::VideoDescriptor VDesc;
429   byte_t             IV_buf[CBC_BLOCK_SIZE];
430   Kumu::FortunaRNG   RNG;
431
432   // set up essence parser
433   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
434
435   // set up MXF writer
436   if ( ASDCP_SUCCESS(result) )
437     {
438       Parser.FillVideoDescriptor(VDesc);
439
440       if ( Options.verbose_flag )
441         {
442           fputs("MPEG-2 Pictures\n", stderr);
443           fputs("VideoDescriptor:\n", stderr);
444           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
445           MPEG2::VideoDescriptorDump(VDesc);
446         }
447     }
448
449   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
450     {
451       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
452       if ( Options.asset_id_flag )
453         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
454       else
455         Kumu::GenRandomUUID(Info.AssetUUID);
456
457       if ( Options.use_smpte_labels )
458         {
459           Info.LabelSetType = LS_MXF_SMPTE;
460           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
461         }
462
463       // configure encryption
464       if( Options.key_flag )
465         {
466           Kumu::GenRandomUUID(Info.ContextID);
467           Info.EncryptedEssence = true;
468
469           if ( Options.key_id_flag )
470             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
471           else
472             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
473
474           Context = new AESEncContext;
475           result = Context->InitKey(Options.key_value);
476
477           if ( ASDCP_SUCCESS(result) )
478             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
479
480           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
481             {
482               Info.UsesHMAC = true;
483               HMAC = new HMACContext;
484               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
485             }
486         }
487
488       if ( ASDCP_SUCCESS(result) )
489         result = Writer.OpenWrite(Options.out_file.c_str(), Info, VDesc);
490     }
491
492   if ( ASDCP_SUCCESS(result) )
493     // loop through the frames
494     {
495       result = Parser.Reset();
496       ui32_t duration = 0;
497
498       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
499         {
500           if ( duration == 1 )
501             {
502               result = Parser.ReadFrame(FrameBuffer);
503
504               if ( ASDCP_SUCCESS(result) )
505                 {
506                   if ( Options.verbose_flag )
507                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
508                   
509                   if ( Options.encrypt_header_flag )
510                     FrameBuffer.PlaintextOffset(0);
511                 }
512             }
513
514           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
515             {
516               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
517
518               // The Writer class will forward the last block of ciphertext
519               // to the encryption context for use as the IV for the next
520               // frame. If you want to use non-sequitur IV values, un-comment
521               // the following  line of code.
522               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
523               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
524             }
525         }
526
527       if ( result == RESULT_ENDOFFILE )
528         result = RESULT_OK;
529     }
530
531   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
532     result = Writer.Finalize();
533
534   return result;
535 }
536
537
538 //------------------------------------------------------------------------------------------
539 // JPEG 2000 essence
540
541 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
542 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
543 //
544 Result_t
545 write_JP2K_S_file(CommandOptions& Options)
546 {
547   AESEncContext*          Context = 0;
548   HMACContext*            HMAC = 0;
549   JP2K::MXFSWriter        Writer;
550   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
551   JP2K::PictureDescriptor PDesc;
552   JP2K::SequenceParser    ParserLeft, ParserRight;
553   byte_t                  IV_buf[CBC_BLOCK_SIZE];
554   Kumu::FortunaRNG        RNG;
555
556   if ( Options.filenames.size() != 2 )
557     {
558       fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
559       return RESULT_FAIL;
560     }
561
562   // set up essence parser
563   Result_t result = ParserLeft.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
564
565   if ( ASDCP_SUCCESS(result) )
566     {
567       Options.filenames.pop_front();
568       result = ParserRight.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
569     }
570
571   // set up MXF writer
572   if ( ASDCP_SUCCESS(result) )
573     {
574       ParserLeft.FillPictureDescriptor(PDesc);
575       PDesc.EditRate = Options.PictureRate();
576
577       if ( Options.verbose_flag )
578         {
579           fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
580           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
581           JP2K::PictureDescriptorDump(PDesc);
582         }
583     }
584
585   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
586     {
587       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
588       if ( Options.asset_id_flag )
589         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
590       else
591         Kumu::GenRandomUUID(Info.AssetUUID);
592
593       if ( Options.use_smpte_labels )
594         {
595           Info.LabelSetType = LS_MXF_SMPTE;
596           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
597         }
598
599       // configure encryption
600       if( Options.key_flag )
601         {
602           Kumu::GenRandomUUID(Info.ContextID);
603           Info.EncryptedEssence = true;
604
605           if ( Options.key_id_flag )
606             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
607           else
608             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
609
610           Context = new AESEncContext;
611           result = Context->InitKey(Options.key_value);
612
613           if ( ASDCP_SUCCESS(result) )
614             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
615
616           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
617             {
618               Info.UsesHMAC = true;
619               HMAC = new HMACContext;
620               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
621             }
622         }
623
624       if ( ASDCP_SUCCESS(result) )
625         result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
626     }
627
628   if ( ASDCP_SUCCESS(result) )
629     {
630       ui32_t duration = 0;
631       result = ParserLeft.Reset();
632       if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
633
634       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
635         {
636           result = ParserLeft.ReadFrame(FrameBuffer);
637
638           if ( ASDCP_SUCCESS(result) )
639             {
640               if ( Options.verbose_flag )
641                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
642                   
643               if ( Options.encrypt_header_flag )
644                 FrameBuffer.PlaintextOffset(0);
645             }
646
647           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
648             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
649
650           if ( ASDCP_SUCCESS(result) )
651             result = ParserRight.ReadFrame(FrameBuffer);
652
653           if ( ASDCP_SUCCESS(result) )
654             {
655               if ( Options.verbose_flag )
656                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
657                   
658               if ( Options.encrypt_header_flag )
659                 FrameBuffer.PlaintextOffset(0);
660             }
661
662           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
663             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
664         }
665
666       if ( result == RESULT_ENDOFFILE )
667         result = RESULT_OK;
668     }
669
670   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
671     result = Writer.Finalize();
672
673   return result;
674 }
675
676 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
677 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
678 //
679 Result_t
680 write_JP2K_file(CommandOptions& Options)
681 {
682   AESEncContext*          Context = 0;
683   HMACContext*            HMAC = 0;
684   JP2K::MXFWriter         Writer;
685   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
686   JP2K::PictureDescriptor PDesc;
687   JP2K::SequenceParser    Parser;
688   byte_t                  IV_buf[CBC_BLOCK_SIZE];
689   Kumu::FortunaRNG        RNG;
690
691   // set up essence parser
692   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
693
694   // set up MXF writer
695   if ( ASDCP_SUCCESS(result) )
696     {
697       Parser.FillPictureDescriptor(PDesc);
698       PDesc.EditRate = Options.PictureRate();
699
700       if ( Options.verbose_flag )
701         {
702           fprintf(stderr, "JPEG 2000 pictures\n");
703           fputs("PictureDescriptor:\n", stderr);
704           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
705           JP2K::PictureDescriptorDump(PDesc);
706         }
707     }
708
709   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
710     {
711       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
712       if ( Options.asset_id_flag )
713         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
714       else
715         Kumu::GenRandomUUID(Info.AssetUUID);
716
717       if ( Options.use_smpte_labels )
718         {
719           Info.LabelSetType = LS_MXF_SMPTE;
720           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
721         }
722
723       // configure encryption
724       if( Options.key_flag )
725         {
726           Kumu::GenRandomUUID(Info.ContextID);
727           Info.EncryptedEssence = true;
728
729           if ( Options.key_id_flag )
730             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
731           else
732             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
733
734           Context = new AESEncContext;
735           result = Context->InitKey(Options.key_value);
736
737           if ( ASDCP_SUCCESS(result) )
738             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
739
740           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
741             {
742               Info.UsesHMAC = true;
743               HMAC = new HMACContext;
744               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
745             }
746         }
747
748       if ( ASDCP_SUCCESS(result) )
749         result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
750     }
751
752   if ( ASDCP_SUCCESS(result) )
753     {
754       ui32_t duration = 0;
755       result = Parser.Reset();
756
757       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
758         {
759           if ( duration == 1 )
760             {
761               result = Parser.ReadFrame(FrameBuffer);
762
763               if ( ASDCP_SUCCESS(result) )
764                 {
765                   if ( Options.verbose_flag )
766                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
767                   
768                   if ( Options.encrypt_header_flag )
769                     FrameBuffer.PlaintextOffset(0);
770                 }
771             }
772
773           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
774             {
775               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
776
777               // The Writer class will forward the last block of ciphertext
778               // to the encryption context for use as the IV for the next
779               // frame. If you want to use non-sequitur IV values, un-comment
780               // the following  line of code.
781               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
782               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
783             }
784         }
785
786       if ( result == RESULT_ENDOFFILE )
787         result = RESULT_OK;
788     }
789
790   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
791     result = Writer.Finalize();
792
793   return result;
794 }
795
796 //------------------------------------------------------------------------------------------
797 // PCM essence
798
799
800 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
801 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
802 //
803 Result_t
804 write_PCM_file(CommandOptions& Options)
805 {
806   AESEncContext*    Context = 0;
807   HMACContext*      HMAC = 0;
808   PCMParserList     Parser;
809   PCM::MXFWriter    Writer;
810   PCM::FrameBuffer  FrameBuffer;
811   PCM::AudioDescriptor ADesc;
812   Rational          PictureRate = Options.PictureRate();
813   byte_t            IV_buf[CBC_BLOCK_SIZE];
814   Kumu::FortunaRNG  RNG;
815
816   // set up essence parser
817   Result_t result = Parser.OpenRead(Options.filenames, PictureRate);
818
819   // set up MXF writer
820   if ( ASDCP_SUCCESS(result) )
821     {
822       Parser.FillAudioDescriptor(ADesc);
823
824       ADesc.EditRate = PictureRate;
825       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
826       ADesc.ChannelFormat = Options.channel_fmt;
827
828       if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
829         {
830           fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
831         }
832
833       if ( Options.verbose_flag )
834         {
835           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
836                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
837                   Options.szPictureRate(),
838                   PCM::CalcSamplesPerFrame(ADesc));
839           fputs("AudioDescriptor:\n", stderr);
840           PCM::AudioDescriptorDump(ADesc);
841         }
842     }
843
844   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
845     {
846       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
847       if ( Options.asset_id_flag )
848         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
849       else
850         Kumu::GenRandomUUID(Info.AssetUUID);
851
852       if ( Options.use_smpte_labels )
853         {
854           Info.LabelSetType = LS_MXF_SMPTE;
855           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
856         }
857
858       // configure encryption
859       if( Options.key_flag )
860         {
861           Kumu::GenRandomUUID(Info.ContextID);
862           Info.EncryptedEssence = true;
863
864           if ( Options.key_id_flag )
865             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
866           else
867             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
868
869           Context = new AESEncContext;
870           result = Context->InitKey(Options.key_value);
871
872           if ( ASDCP_SUCCESS(result) )
873             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
874
875           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
876             {
877               Info.UsesHMAC = true;
878               HMAC = new HMACContext;
879               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
880             }
881         }
882
883       if ( ASDCP_SUCCESS(result) )
884         result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
885
886       if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
887         {
888           MXF::WaveAudioDescriptor *descriptor = 0;
889           Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
890                                                   reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
891           descriptor->ChannelAssignment = Options.channel_assignment;
892         }
893     }
894
895   if ( ASDCP_SUCCESS(result) )
896     {
897       result = Parser.Reset();
898       ui32_t duration = 0;
899
900       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
901         {
902           result = Parser.ReadFrame(FrameBuffer);
903
904           if ( ASDCP_SUCCESS(result) )
905             {
906               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
907                 {
908                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
909                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
910                   result = RESULT_ENDOFFILE;
911                   continue;
912                 }
913
914               if ( Options.verbose_flag )
915                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
916
917               if ( ! Options.no_write_flag )
918                 {
919                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
920
921                   // The Writer class will forward the last block of ciphertext
922                   // to the encryption context for use as the IV for the next
923                   // frame. If you want to use non-sequitur IV values, un-comment
924                   // the following  line of code.
925                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
926                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
927                 }
928             }
929         }
930
931       if ( result == RESULT_ENDOFFILE )
932         result = RESULT_OK;
933     }
934
935   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
936     result = Writer.Finalize();
937
938   return result;
939 }
940
941
942 //------------------------------------------------------------------------------------------
943 // TimedText essence
944
945
946 // Write one or more plaintext timed text streams to a plaintext ASDCP file
947 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
948 //
949 Result_t
950 write_timed_text_file(CommandOptions& Options)
951 {
952   AESEncContext*    Context = 0;
953   HMACContext*      HMAC = 0;
954   TimedText::DCSubtitleParser  Parser;
955   TimedText::MXFWriter    Writer;
956   TimedText::FrameBuffer  FrameBuffer;
957   TimedText::TimedTextDescriptor TDesc;
958   byte_t            IV_buf[CBC_BLOCK_SIZE];
959   Kumu::FortunaRNG  RNG;
960
961   // set up essence parser
962   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
963
964   // set up MXF writer
965   if ( ASDCP_SUCCESS(result) )
966     {
967       Parser.FillTimedTextDescriptor(TDesc);
968       FrameBuffer.Capacity(Options.fb_size);
969
970       if ( Options.verbose_flag )
971         {
972           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
973           TimedText::DescriptorDump(TDesc);
974         }
975     }
976
977   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
978     {
979       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
980       if ( Options.asset_id_flag )
981         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
982       else
983         Kumu::GenRandomUUID(Info.AssetUUID);
984
985       if ( Options.use_smpte_labels )
986         {
987           Info.LabelSetType = LS_MXF_SMPTE;
988           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
989         }
990
991       // configure encryption
992       if( Options.key_flag )
993         {
994           Kumu::GenRandomUUID(Info.ContextID);
995           Info.EncryptedEssence = true;
996
997           if ( Options.key_id_flag )
998             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
999           else
1000             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1001
1002           Context = new AESEncContext;
1003           result = Context->InitKey(Options.key_value);
1004
1005           if ( ASDCP_SUCCESS(result) )
1006             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1007
1008           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1009             {
1010               Info.UsesHMAC = true;
1011               HMAC = new HMACContext;
1012               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1013             }
1014         }
1015
1016       if ( ASDCP_SUCCESS(result) )
1017         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
1018     }
1019
1020   if ( ASDCP_FAILURE(result) )
1021     return result;
1022
1023   std::string XMLDoc;
1024   TimedText::ResourceList_t::const_iterator ri;
1025
1026   result = Parser.ReadTimedTextResource(XMLDoc);
1027
1028   if ( ASDCP_SUCCESS(result) )
1029     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1030
1031   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1032     {
1033       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1034
1035       if ( ASDCP_SUCCESS(result) )
1036         {
1037           if ( Options.verbose_flag )
1038             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1039
1040           if ( ! Options.no_write_flag )
1041             {
1042               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1043               
1044               // The Writer class will forward the last block of ciphertext
1045               // to the encryption context for use as the IV for the next
1046               // frame. If you want to use non-sequitur IV values, un-comment
1047               // the following  line of code.
1048               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1049               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1050             }
1051         }
1052
1053       if ( result == RESULT_ENDOFFILE )
1054         result = RESULT_OK;
1055     }
1056
1057   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1058     result = Writer.Finalize();
1059
1060   return result;
1061 }
1062
1063 //
1064 int
1065 main(int argc, const char** argv)
1066 {
1067   Result_t result = RESULT_OK;
1068   char     str_buf[64];
1069   CommandOptions Options(argc, argv);
1070
1071   if ( Options.version_flag )
1072     banner();
1073
1074   if ( Options.help_flag )
1075     usage();
1076
1077   if ( Options.version_flag || Options.help_flag )
1078     return 0;
1079
1080   if ( Options.error_flag )
1081     {
1082       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1083       return 3;
1084     }
1085
1086   if ( Options.show_ul_values )
1087     {
1088       if ( Options.use_smpte_labels )
1089         DefaultSMPTEDict().Dump(stdout);
1090       else
1091         DefaultInteropDict().Dump(stdout);
1092     }
1093
1094   EssenceType_t EssenceType;
1095   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
1096
1097   if ( ASDCP_SUCCESS(result) )
1098     {
1099       switch ( EssenceType )
1100         {
1101         case ESS_MPEG2_VES:
1102           result = write_MPEG2_file(Options);
1103           break;
1104
1105         case ESS_JPEG_2000:
1106           if ( Options.stereo_image_flag )
1107             {
1108               result = write_JP2K_S_file(Options);
1109             }
1110           else
1111             {
1112               result = write_JP2K_file(Options);
1113             }
1114           break;
1115
1116         case ESS_PCM_24b_48k:
1117         case ESS_PCM_24b_96k:
1118           result = write_PCM_file(Options);
1119           break;
1120
1121         case ESS_TIMED_TEXT:
1122           result = write_timed_text_file(Options);
1123           break;
1124
1125         default:
1126           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1127                   Options.filenames.front().c_str());
1128           return 5;
1129         }
1130     }
1131
1132   if ( ASDCP_FAILURE(result) )
1133     {
1134       fputs("Program stopped on error.\n", stderr);
1135
1136       if ( result != RESULT_FAIL )
1137         {
1138           fputs(result, stderr);
1139           fputc('\n', stderr);
1140         }
1141
1142       return 1;
1143     }
1144
1145   return 0;
1146 }
1147
1148
1149 //
1150 // end asdcp-wrap.cpp
1151 //