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