Corrected rgba_MaxRef default from 1024 to 1023. Corrected partition_space from 10...
[asdcplib.git] / src / as-02-wrap.cpp
1 /*
2 Copyright (c) 2011-2014, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
3 John Hurst
4
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10 1. Redistributions of source code must retain the above copyright
11    notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15 3. The name of the author may not be used to endorse or promote products
16    derived from this software without specific prior written permission.
17
18 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 /*! \file    as-02-wrap.cpp
30     \version $Id$       
31     \brief   AS-02 file manipulation utility
32
33   This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
34
35   For more information about AS-02, please refer to the header file AS_02.h
36   For more information about asdcplib, please refer to the header file AS_DCP.h
37 */
38
39 #include <KM_fileio.h>
40 #include <KM_prng.h>
41 #include <AS_02.h>
42 #include <PCMParserList.h>
43 #include <Metadata.h>
44
45 using namespace ASDCP;
46
47 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
48 const ASDCP::Dictionary *g_dict = 0;
49
50
51 const char*
52 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
53 {
54   snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
55   return buf;
56 }
57
58
59
60 //------------------------------------------------------------------------------------------
61 //
62 // command line option parser class
63
64 static const char* PROGRAM_NAME = "as-02-wrap";  // program name for messages
65
66 // local program identification info written to file headers
67 class MyInfo : public WriterInfo
68 {
69 public:
70   MyInfo()
71   {
72       static byte_t default_ProductUUID_Data[UUIDlen] =
73       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
74         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
75       
76       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
77       CompanyName = "WidgetCo";
78       ProductName = "as-02-wrap";
79       ProductVersion = ASDCP::Version();
80   }
81 } s_MyInfo;
82
83
84
85 // Increment the iterator, test for an additional non-option command line argument.
86 // Causes the caller to return if there are no remaining arguments or if the next
87 // argument begins with '-'.
88 #define TEST_EXTRA_ARG(i,c)                                             \
89   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
90     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
91     return;                                                             \
92   }
93
94 //
95 void
96 banner(FILE* stream = stdout)
97 {
98   fprintf(stream, "\n\
99 %s (asdcplib %s)\n\n\
100 Copyright (c) 2011-2014, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
101 asdcplib may be copied only under the terms of the license found at\n\
102 the top of every file in the asdcplib distribution kit.\n\n\
103 Specify the -h (help) option for further information about %s\n\n",
104           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
105 }
106
107 //
108 void
109 usage(FILE* stream = stdout)
110 {
111   fprintf(stream, "\
112 USAGE: %s [-h|-help] [-V]\n\
113 \n\
114        %s [-a <uuid>] [-A <w>/<h>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
115             [-D <depth>] [-e|-E] [-i] [-j <key-id-string>] [-k <key-string>]\n\
116             [-M] [-m <expr>] [-p <ul>] [-r <n>/<d>] [-R] [-s <seconds>]\n\
117             [-t <min>] [-T <max>] [-u] [-v] [-W] [-x <int>] [-X <int>] [-Y]\n\
118             [-z|-Z] <input-file>+ <output-file>\n\n",
119           PROGRAM_NAME, PROGRAM_NAME);
120
121   fprintf(stream, "\
122 Options:\n\
123   -h | -help        - Show help\n\
124   -V                - Show version information\n\
125   -a <uuid>         - Specify the Asset ID of the file\n\
126   -A <w>/<h>        - Set aspect ratio for image (default 4/3)\n\
127   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
128                       Defaults to 4,194,304 (4MB)\n\
129   -C <ul>           - Set ChannelAssignment UL value\n\
130   -d <duration>     - Number of frames to process, default all\n\
131   -D <depth>        - Component depth for YCbCr images (default: 10)\n\
132   -e                - Encrypt JP2K headers (default)\n\
133   -E                - Do not encrypt JP2K headers\n\
134   -F (0|1)          - Set field dominance for interlaced image (default: 0)\n\
135   -i                - Indicates input essence is interlaced fields (forces -Y)\n\
136   -j <key-id-str>   - Write key ID instead of creating a random value\n\
137   -k <key-string>   - Use key for ciphertext operations\n\
138   -M                - Do not create HMAC values when writing\n\
139   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
140                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
141   -p <ul>           - Set broadcast profile\n\
142   -r <n>/<d>        - Edit Rate of the output file.  24/1 is the default\n\
143   -R                - Indicates RGB image essence (default)\n\
144   -s <seconds>      - Duration of a frame-wrapped partition (default 60)\n\
145   -t <min>          - Set RGB component minimum code value (default: 0)\n\
146   -T <max>          - Set RGB component maximum code value (default: 1023)\n\
147   -u                - Print UL catalog to stderr\n\
148   -v                - Verbose, prints informative messages to stderr\n\
149   -W                - Read input file only, do not write source file\n\
150   -x <int>          - Horizontal subsampling degree (default: 2)\n\
151   -X <int>          - Vertical subsampling degree (default: 2)\n\
152   -Y                - Indicates YCbCr image essence (default: RGB)\n\
153   -z                - Fail if j2c inputs have unequal parameters (default)\n\
154   -Z                - Ignore unequal parameters in j2c inputs\n\
155 \n\
156   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
157          o All option arguments must be separated from the option by whitespace.\n\n");
158 }
159
160 //
161 static ASDCP::Rational
162 decode_rational(const char* str_rat)
163 {
164   assert(str_rat);
165   ui32_t Num = atoi(str_rat);
166   ui32_t Den = 0;
167
168   const char* den_str = strrchr(str_rat, '/');
169   if ( den_str != 0 )
170     Den = atoi(den_str+1);
171
172   return ASDCP::Rational(Num, Den);
173 }
174
175 //
176 //
177 class CommandOptions
178 {
179   CommandOptions();
180
181 public:
182   bool   error_flag;     // true if the given options are in error or not complete
183   bool   key_flag;       // true if an encryption key was given
184   bool   asset_id_flag;  // true if an asset ID was given
185   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
186   bool   write_hmac;     // true if HMAC values are to be generated and written
187   bool   verbose_flag;   // true if the verbose option was selected
188   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
189   bool   no_write_flag;  // true if no output files are to be written
190   bool   version_flag;   // true if the version display option was selected
191   bool   help_flag;      // true if the help display option was selected
192   ui32_t duration;       // number of frames to be processed
193   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
194   bool use_cdci_descriptor; // 
195   Rational edit_rate;    // edit rate of JP2K sequence
196   ui32_t fb_size;        // size of picture frame buffer
197   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
198   bool   key_id_flag;    // true if a key ID was given
199   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
200   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
201   std::string out_file; //
202   bool show_ul_values_flag;    /// if true, dump the UL table before going tp work.
203   Kumu::PathList_t filenames;  // list of filenames to be processed
204
205   UL channel_assignment;
206   ASDCP::MXF::AS02_MCAConfigParser mca_config;
207
208   UL picture_coding;
209   ui32_t rgba_MaxRef;
210   ui32_t rgba_MinRef;
211
212   ui32_t horizontal_subsampling;
213   ui32_t vertical_subsampling;
214   ui32_t component_depth;
215   ui8_t frame_layout;
216   ASDCP::Rational aspect_ratio;
217   ui8_t field_dominance;
218   ui32_t mxf_header_size;
219
220   //new attributes for AS-02 support 
221   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
222   ui32_t partition_space; //Shim parameter partition_spacing
223
224   //
225   CommandOptions(int argc, const char** argv) :
226     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
227     encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
228     no_write_flag(false), version_flag(false), help_flag(false),
229     duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
230     show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
231     mca_config(g_dict), rgba_MaxRef(1023), rgba_MinRef(0),
232     horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
233     frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
234     mxf_header_size(16384)
235   {
236     memset(key_value, 0, KeyLen);
237     memset(key_id_value, 0, UUIDlen);
238
239     for ( int i = 1; i < argc; i++ )
240       {
241
242         if ( (strcmp( argv[i], "-help") == 0) )
243           {
244             help_flag = true;
245             continue;
246           }
247          
248         if ( argv[i][0] == '-'
249              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
250              && argv[i][2] == 0 )
251           {
252             switch ( argv[i][1] )
253               {
254               case 'A':
255                 TEST_EXTRA_ARG(i, 'A');
256                 edit_rate = decode_rational(argv[i]);
257                 break;
258
259               case 'a':
260                 asset_id_flag = true;
261                 TEST_EXTRA_ARG(i, 'a');
262                 {
263                   ui32_t length;
264                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
265
266                   if ( length != UUIDlen )
267                     {
268                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
269                       return;
270                     }
271                 }
272                 break;
273
274               case 'b':
275                 TEST_EXTRA_ARG(i, 'b');
276                 fb_size = abs(atoi(argv[i]));
277
278                 if ( verbose_flag )
279                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
280
281                 break;
282
283               case 'C':
284                 TEST_EXTRA_ARG(i, 'C');
285                 if ( ! channel_assignment.DecodeHex(argv[i]) )
286                   {
287                     fprintf(stderr, "Error decoding ChannelAssignment UL value: %s\n", argv[i]);
288                     return;
289                   }
290                 break;
291
292               case 'D':
293                 TEST_EXTRA_ARG(i, 'D');
294                 component_depth = abs(atoi(argv[i]));
295                 break;
296
297               case 'd':
298                 TEST_EXTRA_ARG(i, 'd');
299                 duration = abs(atoi(argv[i]));
300                 break;
301
302               case 'E': encrypt_header_flag = false; break;
303               case 'e': encrypt_header_flag = true; break;
304
305               case 'F':
306                 TEST_EXTRA_ARG(i, 'F');
307                 field_dominance = abs(atoi(argv[i]));
308                 if ( field_dominance > 1 )
309                   {
310                     fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
311                     return;
312                   }
313                 break;
314
315               case 'h': help_flag = true; break;
316
317               case 'i':
318                 frame_layout = 1;
319                 use_cdci_descriptor = true;
320                 break;
321
322               case 'j':
323                 key_id_flag = true;
324                 TEST_EXTRA_ARG(i, 'j');
325                 {
326                   ui32_t length;
327                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
328
329                   if ( length != UUIDlen )
330                     {
331                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
332                       return;
333                     }
334                 }
335                 break;
336
337               case 'k': key_flag = true;
338                 TEST_EXTRA_ARG(i, 'k');
339                 {
340                   ui32_t length;
341                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
342
343                   if ( length != KeyLen )
344                     {
345                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
346                       return;
347                     }
348                 }
349                 break;
350
351               case 'M': write_hmac = false; break;
352
353               case 'm':
354                 TEST_EXTRA_ARG(i, 'm');
355                 if ( ! mca_config.DecodeString(argv[i]) )
356                   {
357                     return;
358                   }
359                 break;
360
361               case 'p':
362                 TEST_EXTRA_ARG(i, 'p');
363                 if ( ! picture_coding.DecodeHex(argv[i]) )
364                   {
365                     fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
366                     return;
367                   }
368                 break;
369
370               case 'r':
371                 TEST_EXTRA_ARG(i, 'r');
372                 edit_rate = decode_rational(argv[i]);
373                 break;
374
375               case 'R':
376                 use_cdci_descriptor = false;
377                 break;
378
379               case 's':
380                 TEST_EXTRA_ARG(i, 's');
381                 partition_space = abs(atoi(argv[i]));
382                 break;
383
384               case 't':
385                 TEST_EXTRA_ARG(i, 't');
386                 rgba_MinRef = abs(atoi(argv[i]));
387                 break;
388
389               case 'T':
390                 TEST_EXTRA_ARG(i, 'T');
391                 rgba_MaxRef = abs(atoi(argv[i]));
392                 break;
393
394               case 'u': show_ul_values_flag = true; break;
395               case 'V': version_flag = true; break;
396               case 'v': verbose_flag = true; break;
397               case 'W': no_write_flag = true; break;
398
399               case 'x':
400                 TEST_EXTRA_ARG(i, 'x');
401                 horizontal_subsampling = abs(atoi(argv[i]));
402                 break;
403
404               case 'X':
405                 TEST_EXTRA_ARG(i, 'X');
406                 vertical_subsampling = abs(atoi(argv[i]));
407                 break;
408
409               case 'Y':
410                 use_cdci_descriptor = true;
411                 break;
412
413               case 'Z': j2c_pedantic = false; break;
414               case 'z': j2c_pedantic = true; break;
415
416               default:
417                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
418                 return;
419               }
420           }
421         else
422           {
423
424             if ( argv[i][0] != '-' )
425               {
426                 filenames.push_back(argv[i]);
427               }
428             else
429               {
430                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
431                 return;
432               }
433           }
434       }
435
436     if ( help_flag || version_flag )
437       return;
438     
439     if ( filenames.size() < 2 )
440       {
441         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
442         return;
443       }
444
445     out_file = filenames.back();
446     filenames.pop_back();
447
448     if ( ! picture_coding.HasValue() )
449       {
450         picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
451       }
452
453     error_flag = false;
454   }
455 };
456
457
458 //------------------------------------------------------------------------------------------
459 // JPEG 2000 essence
460
461 namespace ASDCP {
462   Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
463                             const ASDCP::Dictionary& dict,
464                             ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
465                             ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
466
467   Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
468 }
469
470 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
471 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
472 //
473 Result_t
474 write_JP2K_file(CommandOptions& Options)
475 {
476   AESEncContext*          Context = 0;
477   HMACContext*            HMAC = 0;
478   AS_02::JP2K::MXFWriter  Writer;
479   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
480   JP2K::SequenceParser    Parser;
481   byte_t                  IV_buf[CBC_BLOCK_SIZE];
482   Kumu::FortunaRNG        RNG;
483   ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
484   ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
485
486   // set up essence parser
487   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
488
489   // set up MXF writer
490   if ( ASDCP_SUCCESS(result) )
491     {
492       ASDCP::JP2K::PictureDescriptor PDesc;
493       Parser.FillPictureDescriptor(PDesc);
494       PDesc.EditRate = Options.edit_rate;
495
496       if ( Options.verbose_flag )
497         {
498           fprintf(stderr, "JPEG 2000 pictures\n");
499           fputs("PictureDescriptor:\n", stderr);
500           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
501           JP2K::PictureDescriptorDump(PDesc);
502         }
503
504       if ( Options.use_cdci_descriptor )
505         {
506           ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
507           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
508           
509           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
510                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
511                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
512
513           if ( ASDCP_SUCCESS(result) )
514             {
515               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
516               tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
517               tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
518               tmp_dscr->ComponentDepth = Options.component_depth;
519               tmp_dscr->FrameLayout = Options.frame_layout;
520               tmp_dscr->AspectRatio = Options.aspect_ratio;
521               tmp_dscr->FieldDominance = Options.field_dominance;
522               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
523             }
524         }
525       else
526         { // use RGB
527           ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
528           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
529           
530           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
531                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
532                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
533
534           if ( ASDCP_SUCCESS(result) )
535             {
536               tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
537               tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
538               tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
539               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
540             }
541         }
542     }
543
544   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
545     {
546       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
547       Info.LabelSetType = LS_MXF_SMPTE;
548
549       if ( Options.asset_id_flag )
550         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
551       else
552         Kumu::GenRandomUUID(Info.AssetUUID);
553
554       // configure encryption
555       if( Options.key_flag )
556         {
557           Kumu::GenRandomUUID(Info.ContextID);
558           Info.EncryptedEssence = true;
559
560           if ( Options.key_id_flag )
561             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
562           else
563             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
564
565           Context = new AESEncContext;
566           result = Context->InitKey(Options.key_value);
567
568           if ( ASDCP_SUCCESS(result) )
569             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
570
571           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
572             {
573               Info.UsesHMAC = true;
574               HMAC = new HMACContext;
575               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
576             }
577         }
578
579       if ( ASDCP_SUCCESS(result) )
580         {
581           result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
582                                     Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
583         }
584     }
585
586   if ( ASDCP_SUCCESS(result) )
587     {
588       ui32_t duration = 0;
589       result = Parser.Reset();
590
591       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
592         {
593           result = Parser.ReadFrame(FrameBuffer);
594           
595           if ( ASDCP_SUCCESS(result) )
596             {
597               if ( Options.verbose_flag )
598                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
599               
600               if ( Options.encrypt_header_flag )
601                 FrameBuffer.PlaintextOffset(0);
602             }
603
604           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
605             {
606               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
607
608               // The Writer class will forward the last block of ciphertext
609               // to the encryption context for use as the IV for the next
610               // frame. If you want to use non-sequitur IV values, un-comment
611               // the following  line of code.
612               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
613               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
614             }
615         }
616
617       if ( result == RESULT_ENDOFFILE )
618         result = RESULT_OK;
619     }
620
621   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
622     result = Writer.Finalize();
623
624   return result;
625 }
626
627 //------------------------------------------------------------------------------------------
628 // PCM essence
629
630
631 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
632 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
633 //
634 Result_t
635 write_PCM_file(CommandOptions& Options)
636 {
637   AESEncContext*    Context = 0;
638   HMACContext*      HMAC = 0;
639   PCMParserList     Parser;
640   AS_02::PCM::MXFWriter    Writer;
641   PCM::FrameBuffer  FrameBuffer;
642   byte_t            IV_buf[CBC_BLOCK_SIZE];
643   Kumu::FortunaRNG  RNG;
644   ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
645
646   // set up essence parser
647   Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
648
649   // set up MXF writer
650   if ( ASDCP_SUCCESS(result) )
651     {
652       ASDCP::PCM::AudioDescriptor ADesc;
653       Parser.FillAudioDescriptor(ADesc);
654
655       ADesc.EditRate = Options.edit_rate;
656       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
657
658       if ( Options.verbose_flag )
659         {
660           char buf[64];
661           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
662                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
663                   RationalToString(Options.edit_rate, buf, 64),
664                   PCM::CalcSamplesPerFrame(ADesc));
665           fputs("AudioDescriptor:\n", stderr);
666           PCM::AudioDescriptorDump(ADesc);
667         }
668
669       essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
670
671       result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
672
673       if ( Options.mca_config.empty() )
674         {
675           essence_descriptor->ChannelAssignment = Options.channel_assignment;
676         }
677       else
678         {
679           if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
680             {
681               fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
682                       Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
683               return RESULT_FAIL;
684             }
685
686           // this is the d-cinema MCA label, what is the one for IMF?
687           essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA);
688         }
689     }
690
691   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
692     {
693       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
694       Info.LabelSetType = LS_MXF_SMPTE;
695
696       if ( Options.asset_id_flag )
697         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
698       else
699         Kumu::GenRandomUUID(Info.AssetUUID);
700
701       // configure encryption
702       if( Options.key_flag )
703         {
704           Kumu::GenRandomUUID(Info.ContextID);
705           Info.EncryptedEssence = true;
706
707           if ( Options.key_id_flag )
708             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
709           else
710             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
711
712           Context = new AESEncContext;
713           result = Context->InitKey(Options.key_value);
714
715           if ( ASDCP_SUCCESS(result) )
716             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
717
718           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
719             {
720               Info.UsesHMAC = true;
721               HMAC = new HMACContext;
722               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
723             }
724         }
725
726       if ( ASDCP_SUCCESS(result) )
727         {
728           result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
729                                     Options.mca_config, Options.edit_rate);
730         }
731     }
732
733   if ( ASDCP_SUCCESS(result) )
734     {
735       result = Parser.Reset();
736       ui32_t duration = 0;
737
738       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
739         {
740           result = Parser.ReadFrame(FrameBuffer);
741
742           if ( ASDCP_SUCCESS(result) )
743             {
744               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
745                 {
746                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
747                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
748                   result = RESULT_ENDOFFILE;
749                   continue;
750                 }
751
752               if ( Options.verbose_flag )
753                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
754
755               if ( ! Options.no_write_flag )
756                 {
757                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
758
759                   // The Writer class will forward the last block of ciphertext
760                   // to the encryption context for use as the IV for the next
761                   // frame. If you want to use non-sequitur IV values, un-comment
762                   // the following  line of code.
763                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
764                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
765                 }
766             }
767         }
768
769       if ( result == RESULT_ENDOFFILE )
770         result = RESULT_OK;
771     }
772
773   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
774     result = Writer.Finalize();
775
776   return result;
777 }
778
779
780
781 #if 0
782 // NOT YET, unfinished business with ST 2052-1
783
784
785 //------------------------------------------------------------------------------------------
786 // TimedText essence
787
788
789 // Write one or more plaintext timed text streams to a plaintext AS-02 file
790 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
791 //
792 Result_t
793 write_timed_text_file(CommandOptions& Options)
794 {
795   AESEncContext*    Context = 0;
796   HMACContext*      HMAC = 0;
797   AS_02::TimedText::ST2052_TextParser  Parser;
798   AS_02::TimedText::MXFWriter    Writer;
799   TimedText::FrameBuffer  FrameBuffer;
800   TimedText::TimedTextDescriptor TDesc;
801   byte_t            IV_buf[CBC_BLOCK_SIZE];
802   Kumu::FortunaRNG  RNG;
803
804   // set up essence parser
805   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
806
807   // set up MXF writer
808   if ( ASDCP_SUCCESS(result) )
809     {
810       Parser.FillTimedTextDescriptor(TDesc);
811       FrameBuffer.Capacity(Options.fb_size);
812
813       if ( Options.verbose_flag )
814         {
815           fputs("IMF Timed-Text Descriptor:\n", stderr);
816           TimedText::DescriptorDump(TDesc);
817         }
818     }
819
820   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
821     {
822       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
823       if ( Options.asset_id_flag )
824         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
825       else
826         Kumu::GenRandomUUID(Info.AssetUUID);
827
828       // configure encryption
829       if( Options.key_flag )
830         {
831           Kumu::GenRandomUUID(Info.ContextID);
832           Info.EncryptedEssence = true;
833
834           if ( Options.key_id_flag )
835             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
836           else
837             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
838
839           Context = new AESEncContext;
840           result = Context->InitKey(Options.key_value);
841
842           if ( ASDCP_SUCCESS(result) )
843             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
844
845           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
846             {
847               Info.UsesHMAC = true;
848               HMAC = new HMACContext;
849               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
850             }
851         }
852
853       if ( ASDCP_SUCCESS(result) )
854         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
855     }
856
857   if ( ASDCP_FAILURE(result) )
858     return result;
859
860   std::string XMLDoc;
861   TimedText::ResourceList_t::const_iterator ri;
862
863   result = Parser.ReadTimedTextResource(XMLDoc);
864
865   if ( ASDCP_SUCCESS(result) )
866     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
867
868   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
869     {
870       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
871
872       if ( ASDCP_SUCCESS(result) )
873         {
874           if ( Options.verbose_flag )
875             FrameBuffer.Dump(stderr, Options.fb_dump_size);
876
877           if ( ! Options.no_write_flag )
878             {
879               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
880
881               // The Writer class will forward the last block of ciphertext
882               // to the encryption context for use as the IV for the next
883               // frame. If you want to use non-sequitur IV values, un-comment
884               // the following  line of code.
885               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
886               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
887             }
888         }
889
890       if ( result == RESULT_ENDOFFILE )
891         result = RESULT_OK;
892     }
893
894   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
895     result = Writer.Finalize();
896
897   return result;
898 }
899
900 #endif
901
902 //
903 int
904 main(int argc, const char** argv)
905 {
906   Result_t result = RESULT_OK;
907   char     str_buf[64];
908   g_dict = &ASDCP::DefaultSMPTEDict();
909   assert(g_dict);
910
911   CommandOptions Options(argc, argv);
912
913   if ( Options.version_flag )
914     banner();
915
916   if ( Options.help_flag )
917     usage();
918
919   if ( Options.show_ul_values_flag )
920     {
921       g_dict->Dump(stdout);
922     }
923
924   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
925     return 0;
926
927   if ( Options.error_flag )
928     {
929       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
930       return 3;
931     }
932
933   EssenceType_t EssenceType;
934   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
935
936   if ( ASDCP_SUCCESS(result) )
937     {
938       switch ( EssenceType )
939         {
940         case ESS_JPEG_2000:
941           result = write_JP2K_file(Options);
942           break;
943
944         case ESS_PCM_24b_48k:
945         case ESS_PCM_24b_96k:
946           result = write_PCM_file(Options);
947           break;
948
949         default:
950           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
951                   Options.filenames.front().c_str());
952           return 5;
953         }
954     }
955
956   if ( ASDCP_FAILURE(result) )
957     {
958       fputs("Program stopped on error.\n", stderr);
959
960       if ( result != RESULT_FAIL )
961         {
962           fputs(result, stderr);
963           fputc('\n', stderr);
964         }
965
966       return 1;
967     }
968
969   return 0;
970 }
971
972
973 //
974 // end as-02-wrap.cpp
975 //