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